Terraform/OpenTofu 기초 · init 과 의존성 잠금 파일 · 이론
init 과 잠금 파일이 지키는 것
한 줄 요약
.terraform.lock.hcl 은 "무엇을 허용했고 무엇을 골랐으며 그것이 진짜 그것인가" 를 적은 계약서이고, init 은 매번 이 계약서를 확인하는 명령이다.
왜 이 파일이 필요했나
프로바이더는 설정 파일 안에 없다. 이름과 버전 제약만 적혀 있고, 실물은 init 이 받아 온다. 그러니 같은 저장소를 받은 두 사람이 서로 다른 시점에 init 을 돌리면 서로 다른 꾸러미를 손에 쥘 수 있다. 그 사이에 프로바이더가 새 버전을 내면 한 사람은 옛 버전, 다른 사람은 새 버전으로 계획을 세운다. 계획이 다르면 리뷰가 의미를 잃는다.
버전 제약을 적으면 되지 않느냐고 할 수 있는데, 제약만으로는 부족하다. 제약은 보통 범위를 허용한다. 범위 안에서 어느 것이 골라졌는지는 제약에 안 적히고, 고른 꾸러미가 어제 받은 그 꾸러미와 같은 바이트인지도 알 수 없다. 그래서 세 가지를 따로 적는 파일이 생겼다.
provider "registry.opentofu.org/hashicorp/local" { version = "2.9.0" constraints = "2.9.0" hashes = [ "h1:rxomJjDwOo+YZ+WIPc25FqEgsz9orh/2MCyUcZmFjvw=", ]}version 은 고른 것, constraints 는 허용한 것, hashes 는 그 꾸러미의 지문이다. constraints 줄은 설정에 버전 제약을 적었을 때만 나타난다 — 제약을 안 적은 프로바이더의 블록에는 아예 없다.
어떻게 동작하나
init 은 순서대로 이렇게 움직인다. 설정에서 필요한 프로바이더와 제약을 모으고, 잠금 파일에 이미 고른 것이 있으면 그것을 그대로 쓰려 하고, 없거나 제약에 어긋나면 새로 고른다. 고른 꾸러미를 설치하기 전에 잠금 파일의 해시와 견주고, 맞지 않으면 설치를 거절한다.
거절은 이렇게 보인다.
Error: Failed to install providerError while installing hashicorp/local 2.9.0: the current package forregistry.opentofu.org/hashicorp/local 2.9.0 doesn't match any of thechecksums previously recorded in the dependency lock file이 오류가 나면 -upgrade 를 붙여도 풀리지 않는다. 버전 선택이 그대로면 같은 검사에 다시 걸리기 때문이다. 손댄 해시가 원인이라면 계약서를 버리고 다시 만드는 것이 정직한 복구다.
버전 제약을 좁히는 쪽에는 사람들이 잘 모르는 구멍이 하나 있다. 잠금 파일의 constraints 줄은 그 항목이 처음 만들어질 때 적힌다. 이미 고른 버전이 새 제약에도 여전히 맞으면 init 은 다시 고를 이유가 없으므로 잠금 파일을 아예 쓰지 않고, 그래서 뒤늦게 붙인 제약은 계약서에 나타나지 않는다. -upgrade 를 붙여도 선택이 그대로면 마찬가지다. 허용 범위를 계약서에 반영하려면 그 항목을 새로 만들게 해야 한다 — 잠금 파일을 지우고 다시 만드는 쪽이 정직하다.
범위에 맞는 것이 하나도 없으면 얘기가 다르다. 그때는 다시 고르려 하고, 고를 것이 없다고 말한다.
Could not resolve provider hashicorp/local: no available releases match thegiven constraints 2.5.0제약을 쓰는 방식은 두 가지가 흔하다. 하나는 정확한 버전을 못 박는 것이고, 다른 하나는 패치 버전만 열어 두는 물결 화살표 연산자다.
version = "2.9.0" # 정확히 이것만version = "~> 2.9" # 2.x 안에서 2.9 이상, 3.0 미만init 이 만드는 또 하나는 .terraform/providers/ 디렉터리다. 여기에는 레지스트리 주소·네임스페이스·이름·버전·플랫폼 순으로 깊어지는 경로에 실제 바이너리가 놓인다. 이것은 플랫폼마다 다른 실행 파일이라 커밋하지 않는다. init 이 언제든 다시 만들어 주기 때문에 잃어도 손해가 없다. 반대로 잠금 파일은 사람이 리뷰해야 하는 변경이라 반드시 커밋한다 — 프로바이더 버전이 올라간 것은 코드 변경만큼 중요한 사건이다.
잠금 파일을 init 없이 만들거나 고치는 명령도 있다. tofu providers lock 은 꾸러미를 설치하지 않고 체크섬만 계산해 적는다. 사내 미러만 보는 환경이라면 -fs-mirror 로 그 미러를 가리키면 되고, 여러 플랫폼에서 같은 저장소를 쓰는 팀은 -platform 을 여러 번 주어 플랫폼별 해시를 미리 채워 둔다. 리눅스에서만 init 한 잠금 파일을 맥에서 쓰면 "이 플랫폼의 해시가 없다" 며 막히는데, 그 예방책이 이것이다.
현장에서 만나는 모습
가장 흔한 사고는 잠금 파일을 .gitignore 에 넣어 둔 저장소다. 각자 다른 버전을 받게 되고, 어느 날 프로바이더가 기본값을 바꾸면 한 사람의 apply 만 자원을 교체한다. 두 번째는 병합 충돌이 났을 때 잠금 파일을 손으로 고치는 것이다. 해시 줄을 사람이 편집하면 십중팔구 어긋나고, 그다음부터 그 브랜치는 아무도 init 을 못 한다. 충돌은 손으로 풀지 말고 한쪽을 고른 뒤 다시 만드는 것이 정석이다.
세 번째는 CI 만 실패하는 경우다. 개발자 노트북에는 캐시가 남아 있어 잠금 파일이 조금 어긋나도 넘어가지만, 매번 빈 작업 공간에서 시작하는 CI 는 곧바로 막힌다. 그래서 잠금 파일이 바뀐 커밋은 반드시 CI 를 한 바퀴 돌려 보고 병합한다.
다음 실습에서 할 것
파드의 오프라인 미러로 여덟 단계를 돕니다. 첫 init 이 만든 잠금 파일을 열어 버전과 해시 수를 읽고, 받은 버전을 제약으로 못 박아 constraints 줄이 생기는 것을 확인하고, 설치된 꾸러미의 실제 경로를 찾습니다. 이어서 잠금 파일 없이 계획을 세워 보고, 미러에 없는 버전을 못 박아 거절당해 보고, init 없이 잠금 파일만 다시 만들어 봅니다. 마지막 두 단계에서는 해시를 일부러 깨뜨려 설치가 막히는 것을 확인하고 복구한 뒤, 제약이 없는 프로바이더를 찾아내는 점검 스크립트를 직접 만듭니다.