Terraform 상태 파일 운영 실전: Remote Backend·State Locking·Drift Detection으로 인프라를 안전하게 관리하기

인프라가 "코드"라면, 그 코드의 상태를 누가 책임지는가
Terraform을 처음 도입한 팀이 가장 빨리 맞닥뜨리는 위기는 terraform apply를 두 사람이 동시에 실행하는 순간입니다. 우리 팀의 플랫폼 팀에서 정확히 이 상황이 벌어졌습니다. SRE 한 명이 EKS 노드 그룹 스케일아웃을 적용하는 동안, 다른 팀원이 보안 그룹 규칙을 추가했습니다. 두 작업 모두 성공한 것처럼 보였지만, 이후 terraform plan을 실행하자 누가 만든 것인지 모를 리소스 diff가 수십 줄씩 쏟아졌습니다.
이 글은 그 경험에서 출발합니다. terraform.tfstate가 무엇인지, 왜 로컬에 두면 안 되는지, S3와 DynamoDB를 결합한 Remote Backend가 어떻게 이 문제를 해결하는지 설명합니다. GitOps 워크플로와 연결하는 부분은 ArgoCD App of Apps 구조 설계 글에서 더 자세히 다루고 있습니다.
1. terraform.tfstate 구조와 의미
terraform.tfstate는 Terraform이 관리하는 리소스의 현재 상태를 기록한 JSON 파일입니다. Terraform은 이 파일을 기준으로 실제 인프라와의 차이를 계산합니다.
상태 파일의 구조를 이해하면 왜 이것이 민감한 데이터인지 알 수 있습니다. 최상위 키는 version, terraform_version, serial, lineage, outputs, resources로 구성됩니다. serial은 상태 파일이 변경될 때마다 1씩 증가하는 정수로, 상태 충돌 감지에 사용됩니다.
resources 배열 안에는 각 리소스의 모든 속성 값이 그대로 저장됩니다. RDS 인스턴스라면 마스터 비밀번호, VPC ID, 서브넷 구성, 엔드포인트 주소까지 평문으로 들어갑니다. IAM 역할이라면 정책 내용이, ElastiCache라면 인증 토큰이 남습니다. Terraform 공식 문서는 이 파일을 민감 정보로 취급하고 안전하게 저장하도록 명시합니다.
2. 로컬 상태의 위험: 단일 장애 지점, 시크릿 평문 저장
terraform.tfstate를 로컬 파일시스템에 두면 네 가지 위험이 동시에 생깁니다.
단일 장애 지점: 상태 파일이 특정 개발자의 노트북에만 있다면 그 사람이 휴가 중이거나 노트북이 고장나면 인프라 변경 자체가 불가능해집니다.
협업 불가능: 여러 명이 같은 .tfstate 파일을 각자 로컬에 가지면 어느 파일이 진실인지 알 수 없습니다.
시크릿 평문 저장: 상태 파일에는 리소스의 모든 속성이 그대로 담깁니다. 이 파일이 Git 저장소에 올라가는 순간, 마스터 DB 비밀번호와 API 키가 히스토리에 영구 기록됩니다. 시크릿 관리 전략이 궁금하다면 프로덕션 시크릿 관리와 Vault 로테이션 글을 참고하기 바랍니다.
동시 실행 충돌: 두 사람이 같은 상태 파일을 기반으로 동시에 apply를 실행하면 서로의 변경이 충돌합니다.
3. S3 + DynamoDB Remote Backend 설정
AWS 환경에서 가장 많이 사용되는 Remote Backend 조합은 S3와 DynamoDB입니다. 공식 S3 Backend 문서에 두 서비스의 필수 권한과 권장 설정이 정리되어 있습니다.
terraform {
backend "s3" {
bucket = "my-company-terraform-state"
key = "prod/eks-cluster/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
kms_key_id = "arn:aws:kms:ap-northeast-2:123456789012:key/mrk-abc1234"
dynamodb_table = "terraform-state-lock"
}
}
S3 버킷은 반드시 버전 관리(Versioning)를 활성화해야 합니다. 상태 파일이 의도치 않게 손상되거나 잘못된 apply가 적용됐을 때 이전 버전으로 복구하는 유일한 방법입니다.
DynamoDB 테이블은 파티션 키를 LockID(String 타입)로 생성합니다. 상태 잠금 중 DynamoDB 쓰로틀링이 발생하면 apply가 실패하므로 온디맨드 과금 모드를 권장합니다.
4. State Locking 원리: 동시 수정 충돌 방지
State Locking은 terraform apply 또는 terraform plan이 실행되는 동안 다른 프로세스가 같은 상태 파일에 쓰지 못하도록 잠금을 거는 메커니즘입니다.
잠금 흐름: terraform apply가 시작되면 DynamoDB에 LockID가 상태 파일 경로인 항목을 조건부로 삽입합니다. 항목이 이미 존재하면 삽입이 실패하고 Terraform은 오류를 반환합니다. 성공하면 잠금 정보가 기록되고 apply가 진행됩니다.
잠금이 해제되지 않는 상황도 발생합니다. 프로세스가 비정상 종료되거나 네트워크가 끊기면 DynamoDB의 Lock 항목이 남습니다. 이 경우 terraform force-unlock <lock-id>로 수동 해제할 수 있습니다.
CI/CD 파이프라인에서도 Locking이 작동합니다. GitHub Actions나 GitLab CI에서 terraform apply를 실행하는 잡이 여러 개 동시에 트리거되면, 첫 번째 잡만 잠금을 획득하고 나머지는 대기하거나 실패합니다.
5. Workspace vs 디렉터리 분리 전략
실무에서는 Workspace가 아닌 디렉터리 분리를 권장합니다.
infra/
├── modules/
│ ├── eks/
│ ├── rds/
│ └── vpc/
├── environments/
│ ├── dev/
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ └── variables.tf
│ ├── staging/
│ └── prod/
이 구조에서 prod/ 디렉터리를 작업하려면 명시적으로 cd environments/prod를 실행해야 합니다. 실수할 여지가 줄어들고, 환경별 IAM 역할과 AWS 프로필을 다르게 가져갈 수 있습니다.
| 기준 | Workspace | 디렉터리 분리 |
|---|---|---|
| 실수로 잘못된 환경 조작 | 발생 가능 | 낮음 |
| 환경별 독립 설정 | 변수 파일로 분리 | 각 디렉터리가 독립 |
| 코드 재사용 | 동일 코드 그대로 | 모듈로 공유 |
| 환경별 접근 권한 분리 | 어려움 | IAM 역할 분리 가능 |
| 대규모 팀 운영 | 권장하지 않음 | 권장 |
6. terraform import: 기존 리소스 온보딩
# 기존 S3 버킷을 Terraform 상태로 가져오기
terraform import aws_s3_bucket.terraform_state my-company-terraform-state
# RDS 인스턴스 import
terraform import aws_db_instance.primary mydb-prod-instance
# VPC import
terraform import aws_vpc.main vpc-0a1b2c3d4e5f67890
# import 후 반드시 plan으로 상태와 코드의 일치 여부를 확인합니다
terraform plan
Terraform 1.5부터는 import 블록을 코드 안에 선언하는 방식이 추가됐습니다. terraform plan이 import를 포함한 실행 계획을 미리 보여주므로 검토 후 apply를 결정할 수 있습니다.
7. State 수술: mv, rm, taint, replace
상태 파일을 직접 조작해야 하는 상황은 운영 중에 반드시 발생합니다. 모든 State 조작은 반드시 백업 후 실행해야 합니다.
# 리소스 이름 변경: 모듈 리팩터링 시 자주 사용
terraform state mv \
'aws_security_group.old_sg' \
'aws_security_group.new_sg'
# 모듈로 이동하는 경우
terraform state mv \
'aws_iam_role.node_role' \
'module.eks_cluster.aws_iam_role.node_role'
# State에서 리소스 제거 (실제 삭제하지 않음)
terraform state rm 'aws_instance.legacy_server'
# 리소스 강제 교체 (Terraform 1.2+ replace 옵션 권장)
terraform apply -replace='aws_instance.app_server'
# 모든 state 목록 확인
terraform state list
# 특정 리소스의 상세 상태 확인
terraform state show 'aws_db_instance.primary'
Terraform 0.x 시절 자주 쓰던 terraform taint는 1.2 버전부터 -replace 플래그로 대체됐습니다.
8. Drift Detection과 자동 알림
Drift는 Terraform이 관리하는 리소스가 상태 파일과 실제 클라우드 인프라 사이에서 의도치 않게 달라지는 현상입니다.
name: Terraform Drift Detection
on:
schedule:
- cron: '0 0 * * 1-5'
workflow_dispatch:
jobs:
drift-detect:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-terraform-ro
aws-region: ap-northeast-2
- uses: hashicorp/setup-terraform@v3
- name: Terraform Plan (Drift Check)
working-directory: environments/prod
run: |
terraform init -input=false
terraform plan -detailed-exitcode -input=false
continue-on-error: true
id: plan
- name: Notify Slack on Drift
if: steps.plan.outputs.exit_code == '2'
run: |
curl -X POST -H 'Content-type: application/json' \
--data '{"text":":warning: Terraform Drift 감지"}' \
${{ secrets.SLACK_WEBHOOK }}
terraform plan의 종료 코드: 0은 변경사항 없음, 1은 오류, 2는 변경사항 있음(Drift 포함)을 의미합니다.
9. Terraform Cloud vs OpenTofu 비교
2026년 현재 Terraform 생태계는 두 개의 주요 경로로 나뉩니다. Terraform Cloud와 OpenTofu입니다.
| 기준 | Terraform Cloud | OpenTofu |
|---|---|---|
| 라이선스 | BSL 1.1 | MPL 2.0 |
| State 관리 | 내장 | 자체 구성 |
| Policy as Code | Sentinel | OPA 연동 |
| 커뮤니티 | HashiCorp | CNCF |
| HCL 호환성 | 기준 | Terraform 1.5+ 호환 |
두 도구 모두 .tf 코드는 거의 동일하게 동작합니다. 마이그레이션 비용보다 라이선스 정책과 운영 복잡도가 선택 기준이 됩니다.
10. 팀 협업 베스트 프랙티스
코드 리뷰에 Plan 출력을 포함합니다. Pull Request가 머지되기 전에 terraform plan 결과가 PR 코멘트에 자동으로 첨부되도록 CI를 구성합니다.
Apply는 CI/CD에서만 실행합니다. 개발자 로컬에서 terraform apply를 직접 실행하는 것을 팀 규칙으로 금지합니다.
IAM 최소 권한을 역할별로 분리합니다. Plan 역할은 읽기 권한만 부여하고, Apply 역할은 별도 승인 단계를 거쳐 사용됩니다.
모듈 버전을 고정합니다. source = "terraform-aws-modules/eks/aws" 처럼 버전을 생략하면 모듈 업데이트가 예고 없이 적용될 수 있습니다.
상태 파일을 직접 편집하지 않습니다. 가능하면 terraform state mv, terraform state rm, terraform import 명령으로 처리합니다.
결론
Terraform 상태 파일 운영은 단순한 설정 작업이 아닙니다.
- Remote Backend 구성 여부:
terraform.tfstate가 로컬 또는 Git 저장소에 있다면 즉시 S3로 이전한다. - State Locking 활성화 여부: DynamoDB로 잠금이 구성되어 있는지 확인한다.
- 환경별 상태 분리 여부: dev, staging, prod 상태 파일이 별도 경로에 있는지 확인한다.
- Drift Detection 자동화 여부: 정기 스케줄로
terraform plan이 실행되는 파이프라인이 있는지 확인한다. - Apply 권한 분리 여부: CI/CD 외의 경로로
terraform apply가 실행 가능한지 점검한다.