Terraform State란?
Terraform State는 인프라의 현재 상태를 기록하는 JSON 파일(terraform.tfstate)입니다. Terraform은 이 파일을 기준으로 “현재 상태”와 “원하는 상태(코드)”를 비교하여 변경 계획(plan)을 생성합니다. State 없이는 Terraform이 어떤 리소스를 관리하고 있는지 알 수 없습니다.
로컬 State 파일은 팀 협업과 보안에 치명적입니다. 이 글에서는 Remote Backend 설정부터 State 분리, 잠금, 마이그레이션, 복구까지 운영에 필요한 모든 패턴을 다룹니다.
Remote Backend: S3 + DynamoDB
가장 널리 쓰이는 조합은 S3(저장) + DynamoDB(잠금)입니다.
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/network/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true # SSE-S3 암호화
dynamodb_table = "terraform-state-lock" # 동시 실행 방지
}
}
# DynamoDB 잠금 테이블 (한 번만 생성)
resource "aws_dynamodb_table" "terraform_lock" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
필수 설정:
encrypt = true— State에는 비밀번호, IP, ARN 등 민감 정보가 포함됩니다dynamodb_table— 두 사람이 동시에 apply하면 State가 손상됩니다. 잠금은 필수입니다- S3 버킷에 버전 관리(Versioning)를 활성화하세요. State 손상 시 이전 버전으로 복구할 수 있습니다
State 분리 전략: 모놀리스 vs 분할
모든 인프라를 하나의 State에 넣으면 plan/apply가 느려지고, 하나의 실수가 전체 인프라에 영향을 줍니다.
| 전략 | 장점 | 단점 |
|---|---|---|
| 모놀리스 (단일 State) | 단순함, 리소스 간 참조 쉬움 | 느린 plan, 높은 위험, 잠금 충돌 |
| 환경별 분리 | dev/staging/prod 독립 관리 | 코드 중복 가능 |
| 계층별 분리 | 변경 범위 최소화, 빠른 plan | State 간 데이터 전달 필요 |
권장: 계층별 분리
infrastructure/
├── network/ # VPC, Subnet, NAT — 거의 변경 안 됨
│ ├── main.tf
│ ├── outputs.tf # vpc_id, subnet_ids 출력
│ └── backend.tf # key = "prod/network/terraform.tfstate"
├── database/ # RDS, ElastiCache — 민감, 변경 드묾
│ ├── main.tf
│ ├── data.tf # network State에서 데이터 읽기
│ └── backend.tf # key = "prod/database/terraform.tfstate"
├── compute/ # ECS, EKS, ASG — 자주 변경
│ ├── main.tf
│ └── backend.tf # key = "prod/compute/terraform.tfstate"
└── monitoring/ # CloudWatch, Grafana
└── backend.tf # key = "prod/monitoring/terraform.tfstate"
terraform_remote_state: State 간 데이터 공유
분리된 State 간에 데이터를 전달하려면 terraform_remote_state 데이터 소스를 사용합니다.
# network/outputs.tf — 출력 정의
output "vpc_id" {
value = aws_vpc.main.id
}
output "private_subnet_ids" {
value = aws_subnet.private[*].id
}
# database/data.tf — 다른 State의 출력 읽기
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "my-terraform-state"
key = "prod/network/terraform.tfstate"
region = "ap-northeast-2"
}
}
# database/main.tf — 참조
resource "aws_db_subnet_group" "main" {
name = "main"
subnet_ids = data.terraform_remote_state.network.outputs.private_subnet_ids
}
대안: terraform_remote_state 대신 aws_vpc, aws_subnet 등의 data source로 태그 기반 조회도 가능합니다. State 간 결합도를 낮추는 장점이 있습니다.
State 잠금과 충돌 해결
잠금이 풀리지 않을 때
# 잠금 정보 확인
aws dynamodb get-item
--table-name terraform-state-lock
--key '{"LockID":{"S":"my-terraform-state/prod/network/terraform.tfstate-md5"}}'
# 강제 잠금 해제 (주의: 다른 사람이 실행 중이 아닌지 반드시 확인)
terraform force-unlock LOCK_ID
잠금이 남는 원인:
- CI/CD 파이프라인이 중간에 타임아웃
terraform apply중 Ctrl+C로 강제 종료- 네트워크 단절
terraform state 명령어 실전
state mv — 리소스 이동/리네임
# 리소스 이름 변경 (코드에서 rename 후)
terraform state mv aws_instance.old_name aws_instance.new_name
# 모듈로 이동
terraform state mv aws_s3_bucket.logs module.logging.aws_s3_bucket.logs
# 다른 State로 이동 (State 분리 시)
terraform state mv -state-out=../database/terraform.tfstate
aws_db_instance.main aws_db_instance.main
state rm — State에서 제거 (리소스는 유지)
# Terraform 관리에서 제외 (실제 리소스는 삭제하지 않음)
terraform state rm aws_instance.legacy
# 사용 사례:
# - 수동 관리로 전환할 때
# - 다른 Terraform 프로젝트로 이관할 때
# - import 잘못했을 때
state import — 기존 리소스 가져오기
# Terraform 1.5+ import 블록 (권장)
import {
to = aws_s3_bucket.existing
id = "my-existing-bucket"
}
# 또는 CLI
terraform import aws_s3_bucket.existing my-existing-bucket
terraform import aws_db_instance.main my-rds-instance
# import 후 반드시 plan으로 diff 확인
terraform plan
# 변경 사항이 없어야 정상 (코드와 실제 상태 일치)
State 복구 시나리오
시나리오 1: State 파일 손상
# S3 버전 관리로 이전 버전 복구
aws s3api list-object-versions
--bucket my-terraform-state
--prefix prod/network/terraform.tfstate
# 특정 버전 다운로드
aws s3api get-object
--bucket my-terraform-state
--key prod/network/terraform.tfstate
--version-id "VERSION_ID"
recovered.tfstate
# 복구된 State 업로드
aws s3 cp recovered.tfstate
s3://my-terraform-state/prod/network/terraform.tfstate
시나리오 2: State와 실제 인프라 불일치
# 현재 State 새로고침 (실제 인프라 상태 반영)
terraform refresh # deprecated
terraform apply -refresh-only # Terraform 0.15.4+
# plan으로 차이점 확인
terraform plan
# 수동 변경된 리소스가 있으면:
# 1. 코드를 실제 상태에 맞추거나
# 2. apply로 코드 상태를 강제 적용
Workspace vs 디렉토리 분리
Terraform Workspace는 같은 코드로 여러 환경을 관리하는 기능이지만, 실무에서는 디렉토리 분리가 더 안전합니다.
# Workspace 방식 (간단하지만 위험)
terraform workspace new staging
terraform workspace new production
terraform workspace select production
terraform apply # 실수로 staging에서 apply할 위험
# 디렉토리 분리 방식 (권장)
environments/
├── dev/
│ ├── main.tf
│ ├── terraform.tfvars # dev 전용 변수
│ └── backend.tf # key = "dev/terraform.tfstate"
├── staging/
│ └── ...
└── production/
└── ... # 물리적으로 분리되어 실수 방지
Workspace는 같은 구조, 다른 설정값인 경우(예: 멀티 리전)에만 사용하세요. dev/prod처럼 인프라 구조가 다른 환경에는 디렉토리 분리가 적합합니다. 이 원칙은 GitHub Actions CI/CD와 연계할 때도 동일하게 적용됩니다.
CI/CD에서의 State 관리
# GitHub Actions 예시
jobs:
terraform:
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::role/terraform-ci
- run: terraform init
- run: terraform plan -out=tfplan
- run: terraform apply tfplan # plan 파일로 apply — 중간 변경 방지
CI/CD 필수 규칙:
plan -out=tfplan→apply tfplan— plan과 apply 사이 State 변경을 방지- production apply는 수동 승인 게이트 추가
- CI 전용 IAM Role에 최소 권한 부여
Docker 네트워크·볼륨 심화와 마찬가지로 인프라 코드도 보안이 핵심입니다. State 파일 접근 권한을 최소화하세요.
정리
Terraform State 관리의 핵심은 “안전하게 저장, 확실하게 잠금, 적절히 분리”입니다. S3 + DynamoDB Remote Backend로 팀 협업 기반을 만들고, 계층별 State 분리로 변경 범위를 최소화하며, S3 버전 관리로 복구 가능성을 확보하세요. state mv, state rm, import 명령어는 운영 중 반드시 필요한 도구입니다.