Automation/Terraform

[Terraform] 3장 How to manage Terraform State (terraform up&running 책 정리)

[앙금빵] 2024. 12. 28.

본 글은 Terraform Up& Running 책에 대하여 3장에 대한 내용 정리 글이다.


3-1. Terraform State 란?

(1) Terraform은 인프라 리소스를 정의하는 설정과 실제 인프라 상태 간의 매핑 정보를 state 파일에 저장되어지도록 설계되었다.

 

(2) 이 파일은 로컬에서 실행하는 경우 기본적으로 명령어를 실행한 디렉터리에 terraform.tfstate라는 이름으로 생성되며, JSON 형식으로 리소스 ID, 속성 정보, 메타데이터 등을 기록한다.

 

(3) 인프라 변경/삭제사항이 생겨 Terraform 명령어(terraform plan, terraform apply , terraform destroy 등)를 실행할 때 Terraform은 terraform.state 파일을 참고하여 실제 환경(AWS 등)에서 리소스 상태를 조회하고, 코드와의 차이점을 찾아 필요한 변경 사항을 계획(plan) 및 적용(apply)한다.

 

(4) Terraform에서 state 파일은 Terraform 내부에서만 사용하도록 설계된, 일종의 Private API다. 이는 사용자에게 명시적으로 공개된 인터페이스가 아니기 때문에, 직접적으로 파일을 수정하거나 파일을 직접 읽도록 코드를 작성하는 것은 절대로 하면 안된다.

 

3-2. 공유 환경에서의 Terraform State

개인 프로젝트 수준에서는 한 컴퓨터에 위치한 로컬 terraform.tfstate 파일로도 충분하지만, 팀 단위 혹은 실제 운영 환경에서는 다음과 같은 문제가 발생할 수 있다.

  • 공유 저장소: 여러 팀원이 동일한 state 파일에 접근해야 하므로 공용 스토리지나 파일 공유가 필요하다.
  • Locking: 누락된 Lock 메커니즘으로 인해 여러 사람이 동시에 terraform apply를 실행하면 race condition이 생길 수 있다.
Lock 기능: 동시에 여러 사용자가 State 파일을 수정하는 상황을 자동으로 막아주는 ‘잠금(Lock)’ 기능을 의미한다.
Race Condition: 여러 사용자가 거의 같은 시점에 State 파일에 접근·수정하여 최종 상태가 예측 불가능하게 뒤엉키는 문제이다.
  • 환경 분리: 개발, 테스트, 스테이징, 운영환경 등을 구분하여 별도의 state 파일을 유지하지 않으면, 테스트 변경이 운영환경에 영향을 미칠 위험이 있다.
  • 버전 관리: 기본적으로 로컬에 보관된 terraform.tfstate 파일은 별도의 버전 이력을 생성하지 않으므로, 파일이 변경될 때마다 어떤 변경이 발생했는지 추적하기가 어렵다.

 

3-3. 버전관리

이러한 이슈를 해결하기 위해 Git과 같은 형상관리 저장소를 이용하면 해결되어질 것이라 생각되어지지만 이 책에는 GIt에 State 파일을 저장하는 것을 권장하지 않는다. 그 이유를 정리하면 다음과 같다.


첫째, 휴먼 에러 발생 가능성

버전 관리 시스템에 state 파일을 저장하면, Terraform 실행 전후로 항상 최신 파일을 pull/push하는 과정을 사용자가 직접 수행해야 한다. 이 과정에서 한 번이라도 pull을 깜빡하거나, 변경된 state 파일을 push하지 않는 실수가 발생하면, 다른 팀원이 사용 중인 state 파일과 불일치가 생길 수 있다.

 

또한, 잘못된 상태로 인한 중복 배포 및 롤백 위험이 존재한다. 최신 state를 사용해야 하는 상황에서 이전 버전의 state 파일을 기반으로 terraform apply를 수행하면, Terraform는 이미 존재하는 인프라 리소스를 “없는 것”으로 간주해 새로 생성하거나, 혹은 이미 삭제된 리소스를 “존재하는 것”으로 간주해 재삭제를 시도할 수 있다. 이로 인해 인프라가 중복 배포되거나, 의도하지 않은 롤백이 발생할 수 있게 되어진다.

 

둘째, Locking 미지원

버전 관리 시스템 대부분(예: Git)은 특정 파일 자체에 대한 Lock을 제공하지 않는다. 즉, 두 명 이상의 팀원이 동시에 terraform apply를 수행해도, Git 레벨에서 누가 먼저 State 파일을 수정할지 제어하지 않는다.

 

셋째, 민감 정보(Secrets) 노출

state 파일에는 DB 사용자명, 비밀번호 등의 민감 정보가 평문으로 저장되는데, 비공개 저장소이더라도, 계정 권한이 있는 사람은 누구든지 민감 데이터를 볼 수 있게 된다는 점에서 보안적인 위험요소가 존재한다.

 

이러한 대안으로 Terrraform에서는 버전관리 툴 대신 원격 백엔드(Remote Backend) 기능을 지원한다. Terraform가 제공하는 원격 Backend(예: AWS S3, Terraform Cloud 등)를 사용하면, state 파일이 중앙에서 자동으로 관리되고 Lock 메커니즘도 지원된다. 또한 대부분 원격 Backend는 암호화와 접근 제어(IAM 등)를 제공하므로, 민감 정보를 보다 안전하게 보호할 수 있다.

 

CSP(AWS, Azure, GCP)에서 제각각 테라폼 상태파일에 대한 원격 백엔드 솔루션을 제공해준다. AWS 경우, S3 + DynamoDB 활용을 통해 state 파일을 관리할 수 있다.


 

3-4. Terraform Backend 한계점

문제 1. ‘닭이 먼저냐 달걀이 먼저냐’ (Chicken-and-Egg)문제

Terraform가 상태를 저장해야 할 대상(S3 버킷 등)을 만들 때, 그 대상 자체도 Terraform로 만들고 싶어 “Chicken-and-Egg” 문제가 생기게 된다.

  • 원격 백엔드를 사용하려면 → 이미 존재하는 S3 버킷과 DynamoDB 테이블이 필요하다.
  • 그런데 정작 그 버킷과 테이블조차 Terraform로 만들고 싶다.

 

즉, 상태를 저장할 리소스가 이미 존재해야 원격 백엔드 설정이 가능한데, 그 리소스도 Terraform로 만들려 하니 닭이 먼저냐 달걀이 먼저냐 딜레마가 생기는 것이다. 이른바 치킨 앤 에그(Chicken-and-Egg) 문제라고 불리는 이 상황은, 인프라 생성과 상태 저장 간의 의존 관계가 서로 얽혀 있기 때문에 발생한다.

 

서로가 서로를 필요로 하는 상태이므로, 다음과 같은 두 과정을 걸쳐야 한다.

  • 처음에 상태(state)를 저장할 버킷과 테이블이 아직 없으므로, 일단 로컬 백엔드(즉, 내 로컬 환경)에 상태를 저장하는 방식으로 Terraform를 실행해 S3 버킷과 DynamoDB 테이블을 생성한다.
  • 그 후에 다시 Terraform 코드에 원격 백엔드 설정(S3 버킷, DynamoDB 테이블 정보)을 추가하고 terraform init을 실행해 기존 로컬 상태를 원격으로 옮기는 과정을 걸친다.

 

이렇게 두 단계를 거쳐 구성을 진행하면 새로운 Terraform 코드를 작성할 때 바로 원격 백엔드를 지정해서 시작할 수 있다. 이 방법으로 처음에는 조금 번거로울 지라도 장기적으로 훨씬 편리하게 Terraform 상태를 관리할 수 있는 기대효과를 얻게 된다.

 

다시말해, 새로운 프로젝트를 만들 때마다 로컬 백엔드 → 원격 백엔드 전환 과정을 또다시 할 필요 없이, 처음부터 원격 백엔드 설정을 코드에 넣고 Terraform 파일을 작성하면 된다.

 

문제 2. Backend 블록에서 변수나 참조를 사용할 수 없는 제약

아래 예시와 같이 백엔드 설정에서 var.bucket 이나 var.region 등을 직접 사용할 수 없다.

terraform {
  backend "s3" {
    bucket = var.bucket         // 사용할 수 없음
    region = var.region         // 사용할 수 없음
    dynamodb_table = var.table  // 사용할 수 없음
    key = "example/terraform.tfstate"
    encrypt = true
  }
}

따라서 S3 버킷, DynamoDB 테이블 이름 등은 코드 내에 하드코딩해야 하고, 여러 모듈에서 복붙(copy & paste)하여 쓰다 보면 실수발생 여지가 존재한다. 특히 key 값이 충돌하면 다른 모듈의 state를 덮어쓰는 치명적 오류가 날 수 있으므로 주의가 필요하다.

 

이러한 제약사항을 Partial Configuration(부분 설정)으로 완화할 수 있다.

백엔드 설정에서 반복되는 항목(bucket, region, dynamodb_table)을 별도 파일(backend.hcl)에 분리해두고, -backend-config 파라미터를 사용해 terraform init 시점에 읽어오는 방식으로 중복을 줄이는 방법이다.

 

핵심 취지는 공통 항목(버킷 이름, 리전, DynamoDB 테이블 등)은 별도 파일로 관리하고, key 같은 모듈별 고유 설정만 Terraform 코드에 남긴다”는 점이다.

 

  • 공통 항목 분리: 버킷 이름, 리전, DynamoDB 테이블 이름 등은 모든 모듈에서 똑같이 써야 하므로, 따로 backend.hcl 같은 파일에 모아두면 재사용이 쉬워진다. terraform init -backend-config=backend.hcl처럼 공통 설정 파일을 불러와서, 모든 모듈이 같은 설정을 공유하게 만들 수 있다.

  • key 분리: 반면에 key는 각 모듈별로 달라야 한다. 같은 버킷 안에서 state 파일을 구분하는 경로 역할을 하기 때문에, key가 겹치면 서로의 상태가 꼬이게 된다. 따라서 key만큼은 모듈 코드에서 따로 지정해야 하므로, 이 부분만 “모듈별 코드에 남겨” 두는 것이다.

 

Part 1. 공통설정 항목 (반복되는 항목)

# backend.hcl
bucket         = "terraform-up-and-running-state"
region         = "us-east-2"
dynamodb_table = "terraform-up-and-running-locks"
encrypt        = true

 

Part 2. key 항목

terraform {
  backend "s3" {
    key = "example/terraform.tfstate"
  }
}

다른 대안으로는 Terragrunt라는 오픈 소스 툴을 사용하는 방법이다. 백엔드 설정(버킷명, 리전, DynamoDB 테이블명 등)을 한 파일에만 정의하고, key 파라미터 역시 모듈 폴더 경로와 자동으로 매핑하는 등 반복을 더욱 줄일 수 있게 된다. (구체적인 예시는 챕터 10에서 다룰 예정이다.)


 

댓글