왜 GitFlow는 인프라에서 실패하는가
Source: Dev.to
TL;DR
Terraform에 GitFlow(장기 유지되는 feature 혹은 environment 브랜치)를 적용하면 state drift와 파손되기 쉬운 파이프라인이 발생합니다. 애플리케이션 코드와 달리 인프라스트럭처 코드(IaC)에는 State라는 세 번째 차원이 존재하며, 이는 git merge로 병합할 수 없습니다.
승리 전략: Trunk‑Based Development를 사용합니다. main을 유일한 진실의 원천으로 삼고, 환경‑별 변수(.tfvars)를 주입해 같은 코드 커밋을 Dev → Stage → Prod 순으로 승격시킵니다. 환경 브랜치 간 코드를 병합하지 않습니다.
The Core Problem: The “Third Dimension”
표준 애플리케이션 개발에서는 두 가지 주요 차원을 관리합니다.
- 코드: Git에 저장된 로직.
- 빌드: 서버에서 실행되는 아티팩트.
코드가 Git에 있으면 일반적으로 빌드에서도 동작합니다.
Terraform에서는 세 번째, 그리고 가장 중요한 차원이 존재합니다: State (terraform.tfstate).
State는 Git에 있는 설정을 AWS/Azure/GCP와 같은 실제 클라우드 API에 매핑합니다. 상태를 원격(S3, Terraform Cloud)으로 저장해 JSON 병합 충돌을 피하더라도, 논리적 분기는 Git만으로 해결할 수 없습니다.
GitFlow와 Terraform을 함께 쓰면 코드와 State가 분리됩니다.
The GitFlow Trap: “State Stomping” and “Phantoms”
일반적인 안티패턴은 Git 브랜치를 환경에 매핑하는 것입니다.
feature/new-db→ Sandboxdev→ Developmentmain→ Production
The Scenario
두 명의 DevOps 엔지니어, Alice와 Bob이 각각 별도 기능을 작업한다고 가정합니다.
- Alice는
develop에서feature/add-redis브랜치를 만들고 Redis 클러스터를 추가한 뒤 Sandbox 환경에 배포해 테스트합니다. - Bob은
develop에서feature/resize-vpc브랜치를 만들고 VPC CIDR을 변경한 뒤 같은 Sandbox(또는 다른) 환경에 배포합니다.
Terraform은 리소스를 state 파일 내 주소로 추적하므로, Alice와 Bob은 이제 레이스 컨디션에 처합니다.
The Consequence
Alice 혹은 Bob이 최종적으로 develop에 병합하면, 실제로는 텍스트 파일만 병합됩니다. Git은 실제 인프라 상태를 병합할 수 없습니다. 깔끔한 Git 히스토리는 클라우드 제공자에 존재하는 엉망인 현실과 모순을 이루게 되며, 다음 배포 시 실패를 초래할 가능성이 높습니다.
- Shared Environment Risk (State Stomping): 상태 파일이 브랜치와 동기화되지 않아 서로의 락을 밟거나 리소스를 덮어씁니다.
- Separate Environment Risk (Phantom Infrastructure): feature 브랜치가 동적 환경에 리소스를 생성하고, 병합 후
terraform destroy없이 브랜치를 삭제하면 해당 리소스는 클라우드에 남아 “고아”가 됩니다. 매달 청구가 발생하지만 코드베이스에는 존재하지 않게 됩니다.
The Solution: Trunk‑Based Development
Trunk‑Based Development(TBD)에서는 main에 푸시되는 모든 커밋이 배포 가능해야 합니다. 장기 유지되는 브랜치를 두지 않습니다.
The Workflow
코드를 브랜치 간에 이동시켜 승격하는 대신(예: dev를 prod에 병합) 아티팩트를 승격합니다. Terraform에서 “아티팩트”는 특정 커밋 SHA와 결합된 모듈 코드 자체입니다.
모든 환경에 동일한 코드를 사용하고, 입력 변수만 다르게 지정합니다.
The Pipeline Architecture
Practical Implementation
코드(논리 인프라)와 환경 설정(변수)을 명확히 구분하도록 레포지토리를 구성합니다.
Directory Structure
/my-infra
/modules
/vpc
/k8s
main.tf # 공통 엔트리 포인트
variables.tf # 변수 정의만 포함
config/
dev.tfvars # Dev‑전용 값 (예: instance_type = "t3.micro")
prod.tfvars # Prod‑전용 값 (예: instance_type = "m5.large")
The CI/CD Command Logic
Dev stage
# 백엔드 설정(보통 부분 설정)으로 초기화
terraform init -backend-config="bucket=my-tf-state-dev"
# 해당 환경 전용 변수 파일을 사용해 플랜 생성
terraform plan -var-file="config/dev.tfvars" -out=tfplan
# 플랜된 내용 그대로 적용
terraform apply tfplan
Prod promotion
# 동일한 코드, 다른 백엔드(상태)와 변수 사용
terraform init -backend-config="bucket=my-tf-state-prod"
terraform plan -var-file="config/prod.tfvars" -out=tfplan
terraform apply tfplan
Why This Is Safer
- 불변성: Dev에서 테스트한 정확한 Terraform 코드가 그대로 Prod에서도 실행됩니다. Dev와 Prod 브랜치 간 “잘못된 병합”이 없습니다.
- State 격리: Dev와 Prod는 백엔드 설정에 의해 완전히 별개의 state 파일을 사용합니다. 서로 간섭하지 않습니다.
- 빠른 피드백: Dev에서 커밋이 실패하면 파이프라인이 중단되고 Prod에 도달하지 않으므로 위험이 최소화됩니다.
The Exception: Shared Modules
Terraform에서 Semantic Versioning을 적용해야 하는 특정 영역이 존재합니다… (content continues)