본문 바로가기

DevOps

Terraform State란 무엇인가: 상태 관리의 개념과 실무 운영 전략

반응형

Terraform State는 Terraform이 관리하는 인프라의 현재 상태를 기록하는 파일입니다. 이 파일이 없으면 Terraform은 어떤 리소스를 만들었는지, 무엇을 변경해야 하는지 판단할 수 없습니다.

핵심 요약

  • Terraform State는 코드(.tf)와 실제 인프라 사이의 매핑 정보를 저장하는 JSON 파일입니다.
  • State가 없으면 Terraform은 매번 모든 리소스를 새로 생성하려고 시도합니다.
  • 팀 환경에서는 Local State 대신 Remote Backend(S3 + DynamoDB 등)를 사용해야 합니다.
  • State Locking은 동시 작업으로 인한 충돌을 방지하는 필수 메커니즘입니다.
  • State 파일에는 민감 정보가 포함될 수 있으므로 접근 제어와 암호화가 필요합니다.

1. 왜 State가 필요한가

팀원 3명이 같은 AWS 인프라를 Terraform으로 관리하고 있다고 가정합니다.

A가 EC2 인스턴스를 추가하고, B가 Security Group을 수정하고, C가 RDS를 생성합니다. 이때 Terraform이 "지금 인프라가 어떤 상태인지" 모른다면 어떻게 될까요?

  • A가 만든 EC2를 Terraform이 인식하지 못해 B가 apply할 때 같은 EC2를 또 만들려고 시도합니다.
  • C가 RDS를 삭제하려 해도 Terraform은 그 RDS가 자기가 만든 것인지 알 수 없습니다.
  • terraform plan이 "변경 없음"이라고 해야 할 상황에서 전체 리소스를 새로 만들겠다고 표시합니다.

이 문제를 해결하는 것이 State입니다. Terraform은 State 파일에 "내가 어떤 리소스를 만들었고, 그 리소스의 현재 속성은 무엇인지"를 기록합니다. 다음 실행 시 이 기록과 코드를 비교해서 변경이 필요한 부분만 계산합니다.

2. Terraform State란

Terraform State는 Terraform이 관리하는 모든 리소스의 현재 상태를 JSON 형식으로 기록한 파일입니다. 기본 파일명은 terraform.tfstate입니다.

핵심 특성:

특성 설명
형식 JSON
기본 파일명 terraform.tfstate
저장 위치 기본은 로컬, 운영 환경에서는 Remote Backend
역할 코드와 실제 인프라 간 매핑
갱신 시점 terraform apply 실행 후

실무에서 이해하기 쉬운 비유를 들면, State는 "인프라의 장부"입니다. 회계 장부 없이 돈을 관리할 수 없듯이, State 없이 인프라를 관리할 수 없습니다.

State 파일의 구조를 간략히 보면 다음과 같습니다.

{
  "version": 4,
  "terraform_version": "1.9.0",
  "resources": [
    {
      "mode": "managed",
      "type": "aws_instance",
      "name": "web",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "attributes": {
            "id": "i-0abc123def456",
            "ami": "ami-0abcdef1234567890",
            "instance_type": "t3.micro",
            "tags": {
              "Name": "web-server"
            }
          }
        }
      ]
    }
  ]
}

이 파일을 통해 Terraform은 코드의 aws_instance.web이 실제 AWS에서 i-0abc123def456이라는 인스턴스에 매핑된다는 것을 알 수 있습니다. 코드에서 instance_typet3.small로 바꾸면, State와 비교해서 "변경이 필요하다"고 판단합니다.

3. 핵심 구성 요소

구성 요소 역할
terraform.tfstate 현재 인프라 상태 기록 (메인 파일)
terraform.tfstate.backup 이전 상태 백업 (apply 전 자동 생성)
Backend State 저장 위치 정의 (Local 또는 Remote)
State Locking 동시 수정 방지 메커니즘

3.1 State 파일과 Backup

terraform apply가 성공할 때마다 State가 갱신됩니다. 갱신 직전에 기존 State를 .backup 파일로 자동 저장합니다.

실무에서 이 backup이 유용한 상황: apply 후 인프라에 문제가 생겼을 때, backup 파일로 이전 상태를 확인하고 어떤 변경이 문제를 일으켰는지 추적할 수 있습니다.

3.2 Backend

State를 어디에 저장할지 결정합니다. 이 선택이 팀 협업 방식을 결정짓기 때문에 프로젝트 초기에 정해야 합니다.

  • Local Backend: 기본값. 작업 디렉토리에 파일로 저장. 개인 학습용.
  • Remote Backend: S3, GCS, Azure Blob 등 원격 저장소. 팀 운영용.

왜 Remote를 선택하는가? Local State는 내 노트북에만 존재합니다. 팀원이 같은 인프라를 수정하려면 이 파일을 공유해야 하는데, Git으로 공유하면 민감 정보가 노출되고, 수동 복사는 충돌을 만듭니다. Remote Backend는 이 문제를 구조적으로 해결합니다.

4. 동작 원리

terraform-state-workflow

Terraform의 핵심 워크플로우에서 State가 어떻게 사용되는지, 실무 시나리오로 설명합니다.

시나리오: EC2 인스턴스 타입 변경

코드에서 instance_typet3.micro에서 t3.small로 변경한 상황입니다.

terraform plan 실행 시:

  1. .tf 파일에서 원하는 상태(Desired State)를 읽습니다 → t3.small
  2. State 파일에서 현재 상태(Current State)를 읽습니다 → t3.micro
  3. 실제 AWS에 API를 호출해 State가 정확한지 확인합니다(Refresh)
  4. Desired와 Current를 비교해 "instance_type 변경 필요"라는 계획을 생성합니다

terraform apply 실행 시:

  1. 위 계획을 실행합니다 (AWS API로 인스턴스 타입 변경)
  2. 변경 결과를 State 파일에 기록합니다 → t3.small로 갱신

terraform destroy 실행 시:

  1. State에서 관리 중인 모든 리소스를 확인합니다
  2. 의존 관계의 역순으로 삭제합니다 (EC2 → Security Group 순)
  3. State에서 해당 리소스 정보를 제거합니다
Tip
terraform plan 실행 시 기본적으로 Refresh가 동작합니다. 리소스가 수백 개면 AWS API 호출만으로 수 분이 걸릴 수 있습니다. -refresh=false로 건너뛸 수 있지만, 누군가 콘솔에서 직접 변경한 내용을 감지하지 못할 수 있으므로 주의가 필요합니다.

5. Local State vs Remote State

5.1 Local State

기본 동작입니다. 별도 설정 없이 terraform init을 실행하면 작업 디렉토리에 terraform.tfstate가 생성됩니다.

terraform {
  # backend 설정이 없으면 자동으로 local
}

언제 Local을 사용하는가: - Terraform을 처음 학습할 때 - 혼자 테스트 환경을 만들고 바로 삭제할 때 - 일회성 스크립트처럼 사용할 때

왜 운영 환경에서는 사용하지 않는가: - 내 노트북이 고장나면 State가 사라집니다. State 없이는 Terraform이 기존 리소스를 인식하지 못합니다. - 팀원과 공유할 방법이 없습니다. Slack으로 파일을 주고받으면 버전 충돌이 발생합니다. - 동시 실행을 막을 수 없습니다. 두 명이 동시에 apply하면 State가 깨집니다.

5.2 Remote State (S3 + DynamoDB)

terraform-state-remote-backend

팀 환경에서 가장 많이 사용되는 조합입니다. S3가 State 저장을, DynamoDB가 Locking을 담당합니다.

왜 이 조합인가? S3는 내구성 99.999999999%(11 9's)를 제공하므로 State 파일 손실 위험이 거의 없습니다. DynamoDB는 조건부 쓰기(Conditional Write)를 지원하므로 Lock 구현에 적합합니다. 둘 다 AWS 관리형 서비스라 별도 운영 부담이 없습니다.

terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "prod/network/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}
설정 설명 왜 필요한가
bucket State 파일을 저장할 S3 버킷 팀 전체가 같은 State에 접근
key 버킷 내 State 파일 경로 환경/계층별 분리 가능
region S3 버킷 리전 리전 지정 필수
encrypt 서버 측 암호화 State에 민감 정보 포함 가능
dynamodb_table Locking용 DynamoDB 테이블 동시 실행 충돌 방지
Security Note
State 파일에는 리소스 ID, IP 주소, 데이터베이스 비밀번호 등이 평문으로 저장될 수 있습니다. S3 Backend 사용 시 encrypt = true는 필수이며, 버킷 접근 권한을 인프라 관리자로 제한해야 합니다.

5.3 대안 비교: 왜 S3 + DynamoDB인가

Backend Locking 지원 장점 단점
S3 + DynamoDB O AWS 네이티브, 높은 내구성, 저비용 AWS 종속
GCS O (내장) Locking 별도 설정 불필요 GCP 종속
Azure Blob O (내장) Azure 환경에 적합 Azure 종속
Terraform Cloud O 무료 티어 제공, UI 제공 외부 서비스 의존
Consul O 멀티 클라우드 직접 운영 필요

AWS를 주로 사용하는 팀이라면 S3 + DynamoDB가 가장 실용적입니다. 별도 서비스를 운영할 필요 없고, IAM으로 접근 제어를 통합할 수 있습니다. GCP나 Azure 환경이라면 각 클라우드의 네이티브 Backend를 사용하는 것이 운영 복잡도를 줄입니다.

6. State Locking

6.1 왜 Locking이 필요한가

실무 시나리오로 설명합니다. 월요일 오전, A와 B가 동시에 같은 프로젝트에서 작업합니다.

  1. A가 terraform apply 실행 → State를 읽음 (EC2 1대)
  2. B가 동시에 terraform apply 실행 → 같은 State를 읽음 (EC2 1대)
  3. A의 apply 완료 → State에 EC2 2대로 기록
  4. B의 apply 완료 → State에 EC2 2대로 기록 (A의 변경을 덮어씀)

결과: 실제로는 EC2가 3대인데 State에는 2대로 기록됩니다. 이후 terraform destroy를 실행하면 1대가 State에 없으므로 삭제되지 않고 남습니다. 이것이 "State drift"이며, 복구하려면 수동으로 terraform import를 해야 합니다.

State Locking은 이 문제를 원천 차단합니다. 한 번에 하나의 작업만 State를 수정할 수 있도록 잠금을 겁니다.

6.2 동작 방식 (DynamoDB 기준)

terraform-state-locking

  1. terraform apply 실행 시 DynamoDB 테이블에 Lock 레코드를 생성합니다.
  2. 다른 사용자가 같은 State에 접근하면 Lock이 존재하므로 에러를 반환합니다.
  3. 작업이 완료되면 Lock 레코드를 삭제합니다.
# Lock이 걸린 상태에서 다른 사용자가 실행하면
$ terraform apply
Error: Error acquiring the state lock
Lock Info:
  ID:        xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  Path:      my-terraform-state-bucket/prod/network/terraform.tfstate
  Operation: OperationTypeApply
  Who:       engineer-a@hostname
  Created:   2026-05-28 10:30:00 UTC

이 에러가 보이면 Who 필드를 확인해서 해당 팀원에게 작업 완료 여부를 확인합니다.

6.3 Lock 강제 해제

비정상 종료(네트워크 끊김, 프로세스 강제 종료 등)로 Lock이 남아 있는 경우에만 사용합니다.

terraform force-unlock <LOCK_ID>
주의
force-unlock은 다른 사용자가 실제로 작업 중인 경우에도 Lock을 해제합니다. 반드시 해당 Lock을 건 사용자가 작업을 완료했거나 비정상 종료된 것을 확인한 후 사용해야 합니다. 확인 없이 사용하면 State 충돌이 발생할 수 있습니다.

7. 실무 사용 사례

사례 1: 환경별 State 분리 — 스타트업 인프라 관리

5명 규모의 팀에서 프로덕션과 스테이징 환경을 운영한다고 가정합니다.

문제 상황: 하나의 State에 모든 환경을 넣으면, 스테이징에서 실험하다가 실수로 프로덕션 리소스를 삭제할 수 있습니다. terraform destroy가 스테이징만 지우려 했는데 프로덕션 RDS까지 포함되는 상황입니다.

해결 방법: 환경별로 State를 분리합니다.

s3://my-terraform-state/
├── prod/
│   ├── network/terraform.tfstate
│   ├── compute/terraform.tfstate
│   └── database/terraform.tfstate
├── staging/
│   ├── network/terraform.tfstate
│   └── compute/terraform.tfstate
└── shared/
    └── iam/terraform.tfstate

왜 계층(network/compute/database)까지 분리하는가?

blast radius(폭발 반경)를 줄이기 위해서입니다. network State를 수정할 때 database에 영향을 주지 않습니다. 다만 분리할수록 State 간 참조가 필요해지므로 복잡도가 증가합니다.

설계 관점에서의 trade-off:

분리 수준 blast radius 복잡도 적합 환경
환경별만 분리 중간 낮음 소규모 팀, 리소스 50개 이하
환경 + 계층 분리 작음 중간 중규모 팀, 리소스 100개 이상
환경 + 계층 + 팀별 분리 최소 높음 대규모 조직, 멀티 팀

소규모 팀이라면 환경별 분리만으로 충분할 수 있습니다. 리소스가 100개를 넘어가면 plan 속도가 느려지고 변경 영향 범위가 커지므로 계층 분리를 검토합니다.

사례 2: State 참조 — 모듈 간 의존성 관리

Network 팀이 VPC를 만들고, Application 팀이 그 VPC 안에 EC2를 배포하는 상황입니다.

# Application 팀의 코드
data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "my-terraform-state-bucket"
    key    = "prod/network/terraform.tfstate"
    region = "ap-northeast-2"
  }
}

resource "aws_instance" "web" {
  subnet_id = data.terraform_remote_state.network.outputs.private_subnet_id
  # ...
}

이 방식의 장점은 Network 팀이 Subnet을 변경해도 Application 팀은 코드를 수정할 필요 없이 Output만 참조하면 된다는 것입니다. 다만 Network State의 Output이 삭제되면 Application 쪽에서 에러가 발생하므로, Output 변경 시 하위 의존성을 확인하는 프로세스가 필요합니다.

사례 3: CI/CD 파이프라인에서의 State 관리

GitHub Actions에서 Terraform을 실행하는 상황입니다. CI/CD 환경에서는 실행할 때마다 새로운 컨테이너가 생성되므로 Local State를 유지할 수 없습니다. Remote Backend가 필수입니다.

# GitHub Actions 예시
- name: Terraform Init
  run: terraform init

- name: Terraform Plan
  run: terraform plan -out=tfplan

- name: Terraform Apply
  run: terraform apply tfplan
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

CI/CD에서 추가로 고려할 점:

  • plan과 apply 분리: PR에서 plan을 실행하고 결과를 리뷰한 후, merge 시 apply를 실행하는 패턴이 안전합니다.
  • State Locking 필수: 여러 PR이 동시에 merge되면 동시 apply가 발생할 수 있습니다.
  • 권한 최소화: CI/CD에 부여하는 IAM 권한은 해당 State가 관리하는 리소스에 한정합니다.

8. 보안 고려사항

Security Note
State 파일은 인프라의 전체 구조와 민감 정보를 포함할 수 있습니다. State 파일이 유출되면 공격자가 인프라 구조를 파악하고 취약점을 찾는 데 활용할 수 있습니다.

Git에 커밋하면 안 되는 이유

State 파일에는 다음 정보가 평문으로 저장됩니다:

  • RDS 비밀번호 (aws_db_instancepassword 속성)
  • API 키, Secret Key
  • 리소스 ID, Private IP 주소
  • 인프라 전체 구조 (공격 표면 파악 가능)

.gitignore에 반드시 추가해야 합니다:

*.tfstate
*.tfstate.backup
.terraform/

S3 Backend 보안 설정 체크리스트

설정 방법 왜 필요한가
서버 측 암호화 encrypt = true + KMS 키 지정 State 내 민감 정보 보호
퍼블릭 접근 차단 S3 Block Public Access 활성화 실수로 공개되는 것 방지
접근 권한 최소화 IAM 정책으로 인프라 관리자만 허용 불필요한 접근 차단
버전 관리 S3 Versioning 활성화 State 변경 이력 추적 및 복구
접근 감사 CloudTrail로 State 접근 로그 기록 누가 언제 State를 읽었는지 추적

sensitive 표시의 한계

output "db_password" {
  value     = aws_db_instance.main.password
  sensitive = true
}

sensitive = true는 CLI 출력에서 값을 마스킹합니다. 다만 State 파일 내부에는 여전히 평문으로 저장됩니다. 따라서 State 파일 자체의 암호화와 접근 제어가 근본적인 보안 대책입니다.

9. 비용/운영 고려사항

주의
State 관리 인프라 자체의 비용은 미미하지만, State 손실이나 불일치는 인프라 전체의 운영 중단으로 이어질 수 있습니다. 복구 비용은 State 관리 비용과 비교할 수 없을 만큼 큽니다.

비용

리소스 비용 비고
S3 (State 저장) 월 $0.01 미만 State 파일은 수 KB~수 MB
DynamoDB (Locking) 월 $0.01 미만 On-Demand 모드, Lock 횟수 미미
S3 Versioning 거의 무시 가능 버전 수에 비례하지만 파일 크기 작음
KMS (암호화) 월 $1/키 + API 호출당 $0.03/10,000건 (월 20,000건 무료)  

비용 측면에서 Remote Backend를 사용하지 않을 이유는 거의 없습니다. 월 $1 미만으로 팀 협업, Locking, 암호화, 백업을 모두 확보할 수 있습니다.

운영 관점

State 파일 크기와 plan 속도:

리소스가 수백 개를 넘으면 terraform plan 시 Refresh에 수 분이 걸릴 수 있습니다. 이 경우 State 분리를 검토합니다. 일반적으로 리소스 100~200개를 하나의 State 단위로 권장합니다.

State 백업 전략:

S3 Versioning으로 자동 백업됩니다. 다만 대규모 변경(리팩토링, 마이그레이션) 전에는 수동으로 State를 다운로드해두는 것이 안전합니다.

# 수동 백업
terraform state pull > backup-$(date +%Y%m%d).tfstate

State drift 대응:

누군가 AWS 콘솔에서 직접 리소스를 수정하면 State와 실제 인프라가 불일치합니다. terraform plan을 실행하면 이 차이를 감지합니다. 정기적으로 plan을 실행해서 drift를 조기에 발견하는 것이 좋습니다.

10. 자주 하는 실수

1. State 파일을 Git에 커밋

민감 정보가 Git 히스토리에 남습니다. 한 번 커밋되면 git rm으로 삭제해도 히스토리에서 복구할 수 있습니다. .gitignore에 처음부터 추가하는 것이 유일한 예방책입니다.

2. Local State로 팀 작업

"나중에 Remote로 옮기면 되지"라고 생각하지만, 이미 Local State로 만든 리소스를 Remote로 마이그레이션하는 과정이 번거롭습니다. 프로젝트 시작 시 Remote Backend를 설정하는 것이 좋습니다.

3. State를 수동으로 편집

JSON 구조가 깨지면 Terraform이 동작하지 않습니다. 리소스 이름 변경이나 제거가 필요하면 CLI 명령을 사용합니다:

# 리소스 이름 변경 (코드 리팩토링 시)
terraform state mv aws_instance.old_name aws_instance.new_name

# State에서 리소스 제거 (실제 인프라는 유지)
terraform state rm aws_instance.legacy

4. DynamoDB Locking 없이 S3 Backend 사용

S3 Backend만 설정하고 dynamodb_table을 빼먹는 경우입니다. 동시 실행 시 State 충돌이 발생할 수 있습니다. CI/CD에서 여러 파이프라인이 동시에 실행되면 이 문제가 실제로 발생합니다.

5. 하나의 State에 모든 리소스를 넣는 것

리소스가 300개인 State에서 Security Group 하나를 수정하려 해도 plan이 전체 300개를 확인합니다. 시간도 오래 걸리고, 실수로 다른 리소스에 영향을 줄 위험도 커집니다.

6. force-unlock을 확인 없이 사용

Lock 에러가 보이면 반사적으로 force-unlock을 실행하는 경우가 있습니다. 다른 팀원이 실제로 apply 중이라면 State가 깨질 수 있습니다. Who 필드를 확인하고 해당 팀원에게 먼저 연락합니다.

11. State 관련 주요 명령어

명령어 설명 사용 시나리오
terraform state list State의 모든 리소스 목록 현재 관리 중인 리소스 확인
terraform state show <resource> 특정 리소스 상세 정보 리소스 속성 확인
terraform state mv <src> <dst> 리소스 이름 변경 코드 리팩토링 시
terraform state rm <resource> State에서 리소스 제거 Terraform 관리에서 제외
terraform state pull Remote State를 로컬로 다운로드 백업, 디버깅
terraform state push 로컬 State를 Remote로 업로드 마이그레이션 (주의 필요)
terraform import <resource> <id> 기존 리소스를 State에 추가 수동 생성 리소스를 Terraform 관리로
terraform force-unlock <ID> Lock 강제 해제 비정상 종료 후 Lock 잔존 시
Tip
terraform state rm은 State에서만 제거하고 실제 인프라는 그대로 둡니다. "이 리소스는 더 이상 Terraform으로 관리하지 않겠다"는 의미입니다. 반대로 terraform import는 "이미 존재하는 리소스를 Terraform 관리로 가져오겠다"는 의미입니다.

12. 정리

  • Terraform State는 코드와 실제 인프라 간 매핑을 기록하는 핵심 파일입니다. 이것 없이는 Terraform이 변경 사항을 계산할 수 없습니다.
  • 팀 환경에서는 Remote Backend(S3 + DynamoDB)를 사용합니다. 비용은 월 $1 미만이지만, 협업, Locking, 암호화, 백업을 모두 확보할 수 있습니다.
  • State Locking은 동시 작업 충돌을 방지하는 필수 메커니즘입니다. DynamoDB 설정을 빼먹지 않도록 합니다.
  • State 파일에는 민감 정보가 포함되므로 Git 커밋 금지, 암호화, 접근 제어가 필수입니다.
  • State를 환경별, 계층별로 분리하면 blast radius를 줄이고 plan 속도를 개선할 수 있습니다. 다만 분리할수록 참조 복잡도가 증가하므로 팀 규모와 리소스 수에 맞게 결정합니다.

참고 문서

반응형