왜 모듈 버저닝이 문제가 되는가
여러 팀이 공용 모듈을 쓰기 시작하면 곧 문제가 드러난다. 모듈 소스를 Git 브랜치로 참조하면, 모듈 저장소에 머지된 변경이 참조하는 모든 스택에 즉시 반영된다. 어제까지 terraform plan이 깨끗했는데 오늘 예상치 못한 리소스 교체가 뜨는 이유가 대개 이것이다. 재현 가능한 인프라의 전제는 "같은 코드 + 같은 모듈 버전 = 같은 결과"인데, 브랜치 참조는 이 전제를 깨뜨린다.
따라서 모듈은 애플리케이션 라이브러리와 동일하게 불변(immutable) 버전으로 고정해야 한다. 태그 기반 릴리스와 시맨틱 버저닝(SemVer)이 출발점이다.
소스 참조 방식 비교
| 참조 방식 | 재현성 | 버전 제약 | 권장 |
|---|---|---|---|
| Git 브랜치 | 낮음 | 불가 | 비권장 |
| Git 태그(ref) | 높음 | 수동 고정 | 제한적 |
| 레지스트리(registry) | 높음 | ~> 등 범위 지정 | 권장 |
레지스트리 프로토콜을 쓰면 version 인자로 SemVer 제약을 걸 수 있고, 태그를 별도 관리하지 않아도 최신 패치를 안전하게 수용할 수 있다.
시맨틱 버저닝 규칙 정하기
모듈에서 MAJOR/MINOR/PATCH의 기준을 팀 규칙으로 명문화해야 혼란이 없다.
- MAJOR: 변수/출력 이름 변경, 리소스 재생성을 강제하는 변경
- MINOR: 하위호환 변수 추가, 신규 리소스(기본값 유지)
- PATCH: 태그·설명 등 동작에 영향 없는 수정
호출 측은 아래처럼 비관적 제약 연산자로 MAJOR만 고정하고 MINOR/PATCH는 자동 수용한다.
module "vpc" {
source = "app.terraform.io/int4/vpc/aws"
version = "~> 3.2" # 3.2.0 이상, 4.0.0 미만
name = "prod"
cidr = "10.0.0.0/16"
}
프라이빗 레지스트리 선택지
Terraform 레지스트리 프로토콜은 단순한 HTTP API다. HCP Terraform/Enterprise를 쓰거나, S3 + 정적 JSON, 또는 GitLab/Artifactory 내장 레지스트리를 쓸 수 있다. 핵심은 /.well-known/terraform.json으로 서비스 디스커버리를 노출하고, modules.v1 엔드포인트가 버전 목록과 다운로드 URL을 반환하면 된다는 점이다. 별도 제품 없이도 사내 오브젝트 스토리지 앞에 얇은 API를 두면 동작한다.
릴리스 자동화 파이프라인
태그를 사람이 손으로 붙이면 규칙이 깨진다. CI에서 검증 통과 시에만 태그를 생성하고 레지스트리에 게시하도록 한다. 아래는 GitLab CI 예시다.
validate:
stage: test
script:
- terraform init -backend=false
- terraform validate
- terraform fmt -check -recursive
- tflint --recursive
publish:
stage: deploy
rules:
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
script:
- VERSION=${CI_COMMIT_TAG#v}
- curl -sf --header "PRIVATE-TOKEN: $REGISTRY_TOKEN" \
--upload-file module.tar.gz \
"$REGISTRY_URL/int4/vpc/aws/$VERSION"
태그 형식을 정규식으로 강제해 v1.2.3 형태만 게시되게 하고, 게시 전 validate 스테이지를 반드시 통과하도록 파이프라인 의존성을 건다.
업그레이드와 폐기 전략
MAJOR 릴리스에는 마이그레이션 노트를 함께 제공한다. 변수명이 바뀌면 moved 블록이나 terraform state mv로 상태 이전 경로를 문서화해야 사용 팀이 리소스 삭제 없이 넘어올 수 있다. 오래된 MAJOR 라인은 보안 패치만 일정 기간 백포트하고 폐기 일정을 공지한다. 어떤 스택이 어떤 버전을 쓰는지 파악하려면 각 스택의 lock 상태를 주기적으로 수집해 대시보드로 관리하는 것이 좋다.
운영 시 주의점
- 레지스트리 인증 토큰은 CLI의
credentials블록이나TF_TOKEN_*환경변수로 주입하고 코드에 넣지 않는다. ~> 3.2같은 범위 제약은 편하지만, CI 파이프라인에서는.terraform.lock.hcl과 명시적 버전으로 재현성을 한 번 더 못 박는다.- 모듈 삭제/재게시(같은 버전 덮어쓰기)를 금지한다. 불변성이 깨지면 레지스트리를 쓰는 의미가 사라진다.
- 모듈 자체에도
examples/와 테스트(terraform test)를 두어, 게시 전 실제 apply가 검증되도록 한다.
정리하면, 모듈을 버전이 붙은 아티팩트로 다루고 게시를 자동화하며 불변성을 지키는 것이 프라이빗 레지스트리 운영의 핵심이다.