LLM WikiAccess-protected knowledge portal
← 스터디 홈
2편 · 약 19분

State 심화 관리와 원격 백엔드

State가 하는 일

1편에서 terraform.tfstate가 인프라의 현재 상태를 JSON으로 기록한다고 설명했다. State 파일은 단순한 스냅샷 이상의 역할을 한다.

  • 리소스 매핑: 코드의 aws_instance.web이 실제로 어느 EC2 인스턴스(i-0abcd1234)에 대응하는지 기록한다. 이 매핑 없이는 Terraform이 기존 리소스를 재활용할지 새로 만들지 알 수 없다.
  • 메타데이터: 리소스 간 의존 순서, Provider 버전, 마지막 apply 시점을 저장한다.
  • 성능 캐시: 대규모 인프라에서 plan 실행 시 API 호출을 줄이기 위해 이전 상태를 캐시로 활용한다. terraform plan -refresh=false로 API 재조회 없이 캐시만 쓸 수도 있다.

State 파일 내부

State 파일은 JSON 텍스트다. 직접 편집해서는 안 되지만, 구조를 이해하면 트러블슈팅에 도움된다.

{
  "version": 4,
  "terraform_version": "1.10.0",
  "serial": 12,
  "lineage": "a3b4c5d6-...",
  "resources": [
    {
      "mode": "managed",
      "type": "aws_instance",
      "name": "web",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "id": "i-0abcd1234ef567890",
            "ami": "ami-0c2d3e23e757b5d84",
            "instance_type": "t3.micro",
            "public_ip": "54.180.1.2"
          }
        }
      ]
    }
  ]
}

serial은 State가 변경될 때마다 1씩 증가하는 버전 번호다. 두 사람이 동시에 apply하면 serial이 충돌한다. State 잠금이 이 충돌을 사전에 막는다.

lineage는 State 파일의 고유 식별자다. 다른 lineage를 가진 State는 병합하지 않는다. 백엔드를 마이그레이션할 때 lineage가 보존되어야 동일한 인프라를 계속 추적할 수 있다.

원격 백엔드 선택

로컬 State는 혼자 쓸 때는 문제없지만 팀 작업에는 맞지 않는다. 원격 백엔드는 State를 공유 저장소에 두고 잠금 메커니즘을 제공한다.

백엔드특징
S3AWS 환경 사실상 표준. 버저닝·암호화 내장. 1.10부터 S3 네이티브 잠금 지원
GCSGoogle Cloud 환경. 네이티브 잠금 지원
Azure BlobAzure 환경. 임차 기반 잠금
Terraform Cloud / HCPHashiCorp 관리형 백엔드. 원격 실행·감사 로그·팀 관리 포함

가장 많이 쓰이는 S3 백엔드를 기준으로 설명한다.

S3 백엔드 심화 설정

terraform {
  backend "s3" {
    bucket       = "my-tf-state-prod"
    key          = "services/api/terraform.tfstate"
    region       = "ap-northeast-2"
    encrypt      = true          # 서버 사이드 암호화(SSE-S3 기본, SSE-KMS 권장)

    # Terraform 1.10 이전: DynamoDB 잠금
    dynamodb_table = "terraform-lock"

    # Terraform 1.10+: S3 네이티브 잠금 (권장)
    # use_lockfile = true
  }
}

S3 버킷 설정 체크리스트:

설정이유
버저닝 활성화State를 실수로 덮어써도 이전 버전으로 복구 가능
퍼블릭 액세스 차단State 파일에 민감 정보(비밀번호 등)가 포함될 수 있음
SSE-KMS 암호화AWS KMS 키로 State 암호화. 키 접근 권한을 IAM으로 세밀하게 제어
Access Logging누가 State를 읽고 썼는지 감사 기록

Terraform 1.10: S3 네이티브 잠금

Terraform 1.10부터 S3 백엔드가 잠금 파일을 S3 자체에 기록하는 S3 네이티브 잠금을 지원한다. DynamoDB 없이도 동시 apply를 막을 수 있다. 기존 DynamoDB 기반 잠금은 향후 버전에서 제거(deprecated) 예정이다.

terraform {
  backend "s3" {
    bucket       = "my-tf-state-prod"
    key          = "services/api/terraform.tfstate"
    region       = "ap-northeast-2"
    encrypt      = true
    use_lockfile = true    # S3 네이티브 잠금 (1.10+)
  }
}

잠금 파일은 <key>.tflock 경로로 같은 버킷에 저장된다. apply가 완료되면 자동으로 삭제된다.

개발자 A terraform apply → 잠금 획득 개발자 B terraform apply → 잠금 대기 S3 Remote Backend terraform.tfstate terraform.tfstate.tflock AWS Cloud EC2, RDS, VPC … 리소스 생성·수정·삭제 State 업데이트 → 잠금 해제 apply 중 .tflock 파일 생성 → 완료 시 삭제 (Terraform 1.10+ S3 네이티브 잠금)
S3 원격 백엔드 워크플로우

terraform state 명령어

Terraform은 State를 직접 조작하는 명령어 세트를 제공한다. 리소스 이름 변경, 더 이상 관리하지 않을 리소스 분리, 원격 State 확인 등에 사용한다.

terraform state list

현재 State에 기록된 모든 리소스 주소를 나열한다.

$ terraform state list
aws_instance.web
aws_security_group.web_sg
aws_s3_bucket.assets
module.vpc.aws_vpc.main
module.vpc.aws_subnet.public[0]
module.vpc.aws_subnet.public[1]

모듈 내 리소스는 module.<이름>.<리소스_주소> 형태로 나타난다.

terraform state show

특정 리소스의 상세 속성을 확인한다.

$ terraform state show aws_instance.web
# aws_instance.web:
resource "aws_instance" "web" {
    id            = "i-0abcd1234ef567890"
    ami           = "ami-0c2d3e23e757b5d84"
    instance_type = "t3.micro"
    public_ip     = "54.180.1.2"
    ...
}

plan/apply 없이도 현재 리소스 속성(IP 주소, ARN 등)을 빠르게 확인할 수 있다.

terraform state mv

리소스 주소를 변경한다. 리소스를 삭제하고 재생성하지 않고, 코드에서 이름만 바꿀 때 사용한다.

# 리소스 이름 변경: web → api
terraform state mv aws_instance.web aws_instance.api

# 일반 리소스를 모듈로 이동
terraform state mv aws_instance.web module.compute.aws_instance.web

mv 없이 코드에서만 이름을 바꾸면 Terraform은 기존 리소스를 삭제하고 새로운 이름으로 리소스를 다시 만든다. 운영 중인 EC2 인스턴스를 이런 식으로 날리면 큰일이다.

terraform state rm

리소스를 State에서 제거한다. 실제 클라우드 리소스는 삭제되지 않는다. Terraform의 추적 범위에서만 빠진다. 이 명령어 후 plan을 실행하면 해당 리소스를 "새로 만들어야 할 것"으로 표시한다.

# 수동으로 만든 리소스를 Terraform 관리 범위에서 제외
terraform state rm aws_instance.legacy_web

마이그레이션 과정에서 특정 리소스의 관리 권한을 다른 Terraform 코드베이스로 이전할 때 흔히 사용한다.

terraform state pull / push

원격 백엔드의 State를 로컬로 내려받거나 로컬 State를 원격으로 업로드한다.

# 원격 State를 stdout으로 출력 (백업·검사용)
terraform state pull > backup.tfstate

# 로컬 State를 원격 백엔드에 강제 업로드 (주의: serial 검사 우회)
terraform state push backup.tfstate

push는 강제 업로드이므로 주의해서 사용해야 한다. 잘못된 State를 밀어 넣으면 Terraform이 인프라를 잘못 인식한다. 비상 복구(corrupt State 복원) 상황에서만 쓴다.

기존 리소스 가져오기: terraform import

수동으로 만든 리소스나 다른 도구가 관리하던 리소스를 Terraform State에 등록하는 방법이다.

방법 1: CLI import (Terraform 1.4 이전 방식)

terraform import aws_instance.web i-0abcd1234ef567890

CLI import는 State에만 등록하며, 해당 리소스의 HCL 코드는 직접 작성해야 한다. State 등록 후 terraform plan으로 코드와 State가 일치하는지 확인한다.

방법 2: import 블록 (Terraform 1.5+, 권장)

import {
  id = "i-0abcd1234ef567890"
  to = aws_instance.web
}

resource "aws_instance" "web" {
  ami           = "ami-0c2d3e23e757b5d84"
  instance_type = "t3.micro"
}

terraform plan을 실행하면 import 블록이 처리되고, -generate-config-out=generated.tf 플래그를 추가하면 기존 리소스의 HCL 코드를 자동 생성해 준다.

terraform plan -generate-config-out=generated.tf

import 블록 방식은 코드 리뷰가 가능하고, plan 단계에서 무엇이 import될지 사전 확인할 수 있다.

환경별 State 분리 전략

같은 코드베이스로 개발·스테이징·프로덕션 환경을 관리할 때 State를 어떻게 분리하느냐에 따라 운영 복잡도가 달라진다.

전략 1: 디렉터리 분리

envs/
├── dev/
│   ├── main.tf
│   └── backend.tf    # bucket key = "dev/terraform.tfstate"
├── staging/
│   ├── main.tf
│   └── backend.tf    # bucket key = "staging/terraform.tfstate"
└── prod/
    ├── main.tf
    └── backend.tf    # bucket key = "prod/terraform.tfstate"

각 환경이 완전히 독립된 디렉터리를 가진다. 환경 간 설정 차이가 크거나 프로덕션에 다른 AWS 계정을 쓸 때 적합하다. 가장 명확하고 실수가 적다.

전략 2: terraform workspace

동일 디렉터리에서 terraform workspace new dev, terraform workspace new prod로 환경을 분리한다. State는 env:/dev/terraform.tfstate, env:/prod/terraform.tfstate에 저장된다.

환경 수가 적고 설정 차이가 작을 때 편리하다. 그러나 규모가 커지면 환경 간 코드 분기가 복잡해지고, 프로덕션에 실수로 잘못된 워크스페이스로 apply하는 위험이 있다.

대다수 팀은 규모가 커지면 디렉터리 분리로 전환한다.

백엔드 마이그레이션

로컬 백엔드에서 S3로, 또는 S3에서 Terraform Cloud로 이전할 때 terraform init -migrate-state 명령을 사용한다.

# 1. backend 블록을 새 설정으로 변경 (코드 수정)
# 2. terraform init -migrate-state 실행
terraform init -migrate-state

Terraform이 기존 State를 읽고 새 백엔드에 복사한다. lineage는 보존된다. 마이그레이션 전후 terraform state list로 리소스 목록이 동일한지 확인한다.

실무 체크리스트

항목확인 내용
State 파일 보안*.tfstate.gitignore에 추가; S3 버킷 퍼블릭 액세스 차단
S3 버저닝State 덮어쓰기 실수 대비 버전 보관
잠금 설정Terraform 1.10+: use_lockfile = true; 이전 버전: DynamoDB 잠금 테이블
환경별 분리환경마다 다른 S3 key 경로; 가능하면 다른 AWS 계정
state mv 습관코드에서 리소스 이름 바꿀 때 반드시 state mv 선행
import 블록수동 리소스 가져올 때 1.5+ import 블록 사용; -generate-config-out으로 코드 자동 생성
백엔드 마이그레이션terraform init -migrate-statestate list로 리소스 수 일치 확인

한 줄 정리

State는 코드와 실제 인프라를 연결하는 매핑 테이블이며, S3 원격 백엔드와 잠금(1.10+의 S3 네이티브 잠금 권장)으로 팀 협업을 안전하게 하고, terraform state 명령어로 리소스 주소 변경·분리·가져오기를 수행한다.

References

  • https://developer.hashicorp.com/terraform/language/state
  • https://developer.hashicorp.com/terraform/language/backend/s3
  • https://dev.to/drewmullen/terraform-state-with-native-s3-locking-1fck
  • https://oneuptime.com/blog/post/2026-02-12-terraform-state-commands-mv-rm-list-aws/view
  • https://oneuptime.com/blog/post/2026-02-23-terraform-state-pull-command/view
  • https://medium.com/@christianashedrack/managing-terraform-state-with-aws-s3-remote-backend-a-complete-guide-3fcd8c22adef
  • https://blogs.pavanrangani.com/terraform-state-management-remote-backend-guide/
  • https://www.terraformpilot.com/articles/terraform-state-management/
  • https://docs.aws.amazon.com/prescriptive-guidance/latest/terraform-aws-provider-best-practices/backend.html