Terraform State 관리 심화

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=tfplanapply tfplan — plan과 apply 사이 State 변경을 방지
  • production apply는 수동 승인 게이트 추가
  • CI 전용 IAM Role에 최소 권한 부여

Docker 네트워크·볼륨 심화와 마찬가지로 인프라 코드도 보안이 핵심입니다. State 파일 접근 권한을 최소화하세요.

정리

Terraform State 관리의 핵심은 “안전하게 저장, 확실하게 잠금, 적절히 분리”입니다. S3 + DynamoDB Remote Backend로 팀 협업 기반을 만들고, 계층별 State 분리로 변경 범위를 최소화하며, S3 버전 관리로 복구 가능성을 확보하세요. state mv, state rm, import 명령어는 운영 중 반드시 필요한 도구입니다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux