본문 바로가기
Terraform/Terraform 101 Study

4주차 2편 테라폼 State

by 개발자 영만 2024. 7. 7.

State

State의 목적과 의미

이론 내용

  • 상태 파일은 테라폼이 관리하는 인프라 리소스의 현재 상태를 기술합니다.
  • 이 파일은 JSON 형식 또는 Terraform Cloud 및 Terraform Enterprise에서는 HCL 형식으로 저장될 수 있습니다.
  • 테라폼 상태 파일을 직접 편집하거나 직접 읽는 코드로 작성해서는 안 됩니다. 이는 테라폼의 내부 동작 원리와 상태의 일관성을 보장하기 위한 것입니다.

 

팀 단위에서 테라폼을 운영하기 위한 고려사항

  • 상태 파일을 저장하는 공유 스토리지 (Shared storage for state files)
    • 팀원들이 동일한 테라폼 상태 파일을 사용하기 위해서는 상태 파일을 공유할 수 있는 위치가 필요합니다.
  • 상태 파일 잠금 (Locking state files)
    • 테라폼에서는 상태 파일을 잠그는 기능이 필요합니다.
    • 동시에 여러 팀원이 같은 상태 파일을 업데이트하여 충돌이 발생할 경우 상태 파일의 일관성이 깨질 수 있으며, 경쟁 상태 (race condition)가 발생할 수 있습니다.
  • 상태 파일 격리 (Isolating state files)
    • 각 환경(예: DEV, STG, PROD)에 대한 상태 파일 격리가 필요합니다.

 

상태 파일을 버전 관리 시스템에 저장하는 것이 권장되지 않는 이유

  • 수동 오류 (Manual error)
    • 버전 관리 시스템을 사용할 경우, 팀원들이 최신 변경 사항을 가져오거나 push 하는 것을 잊어버리거나 잘못된 버전의 상태 파일을 사용할 수 있습니다.
    • 이로 인해 테라폼이 이전 상태로 롤백되거나 예기치 않은 인프라 변경이 발생할 수 있습니다.
  • 잠금 (Locking)
    • 대부분의 버전 관리 시스템은 여러 팀 구성원이 동시에 하나의 파일을 수정할 수 있지만, 잠금 기능을 통한 동시 수정 방지는 제공하지 않습니다.
  • 시크릿 (Secrets)
    • 테라폼 상태 파일에는 인프라의 구성과 관련된 중요한 정보가 포함될 수 있습니다. 이 정보들은 평문으로 저장되기 때문에, 버전 관리 시스템에서 이 정보들이 노출될 위험이 있습니다.
    • 특히 민감한 정보(예: 인증 토큰, 비밀번호 등)가 포함된 경우, 보안 문제가 발생할 수 있습니다.

 

원격 백엔드를 사용하자 - AWS S3, Azure Blob Storage, Google Cloud Storage, Consul 등

  • 수동 오류 해결
    • 원격 백엔드를 사용하면 테라폼이 자동으로 상태 파일을 백엔드에서 로드하고, apply 후에도 자동으로 상태 파일을 백엔드에 저장합니다. 
  • 잠금 (Locking)
    • 대부분의 원격 백엔드는 자체적으로 잠금 메커니즘을 제공하여 여러 팀원이 동시에 상태 파일을 수정할 때 충돌을 방지할 수 있습니다.
    • 예를 들어, 테라폼이 AWS S3를 원격 백엔드로 사용할 경우 S3의 객체 잠금 기능을 활용하여 apply 시 잠금을 설정할 수 있습니다.
    • 또한 -lock-timeout=<TIME> 옵션을 사용하여 대기 시간을 지정할 수 있습니다.
  • 시크릿 (Secrets)
    • 대부분의 원격 백엔드는 데이터 전송 및 저장 시 기본적으로 암호화를 지원합니다.
    • 예를 들어, AWS S3, Azure Blob Storage, Google Cloud Storage 등의 원격 백엔드는 데이터를 암호화하여 저장합니다.

 

원격 백엔드로 AWS S3 와 DynamoDB를 함께 사용하는 경우

https://dev.to/bhusein/configuring-terraform-backend-with-aws-s3-and-dynamodb-state-locking-96l

 

상태 파일 확인 실습

  • vpc.tf 파일 생성
provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"

  tags = {
    Name = "t101-study"
  }
}
  • 실행 및 확인
# 리소스 배포
terraform init && terraform plan && terraform apply -auto-approve

# 현재 디렉토리 내에 상태 파일 확인
ls

# 상태 파일 출력(JSON 형식)
cat terraform.tfstate | jq

# terraform.tfstate 파일에서 아래 정보들을 확인
# 상태 파일에서 관리되는 리소스의 목록 출력
terraform state list

# 특정 리소스의 상세 정보 출력
terraform state show aws_vpc.myvpc

  • 태그 수정 후 상태 파일 확인
provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"

  tags = {
    Name = "tf-state"
  }
}
  • 실행 및 확인
# 리소스 배포 : plan 시 tfstate 상태와 코드 내용을 비교해서 검토
terraform plan && terraform apply -auto-approve

# 현재 디렉토리 내에 상태 파일들 확인 : 백업 파일 생성됨
ls terraform.tfstate*

# 상태 파일 비교 : 백업 파일과 원본 파일 비교
diff terraform.tfstate terraform.tfstate.backup

  • 한번 더 태그 수정 후 상태 파일 확인
provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"

  tags = {
    Name = "tf-state-tag-change"
  }
}
  • 실행 및 확인
# 리소스 배포 : plan 시 tfstate 상태와 코드 내용을 비교해서 검토
terraform plan && terraform apply -auto-approve

# 현재 디렉토리 내에 상태 파일들 확인 : 백업 파일 생성됨
ls terraform.tfstate*

# 상태 파일 비교 : 백업 파일과 원본 파일 비교
diff terraform.tfstate terraform.tfstate.backup

 

State 동기화

소개

https://kschoi728.tistory.com/135

  • 테라폼에서는 Plan 과정에서 현재 State와 구성 파일의 차이를 분석하여 각 리소스에 대해 Create, Update, Delete, 그리고 Replace의 동작을 결정합니다.
  • 각 동작은 Plan 및 Apply 출력에서 특정 기호를 통해 나타납니다.
    • + (Create) : 새로운 리소스를 생성합니다.
    • ~ (Update) : 기존 리소스의 수정이 필요한 경우입니다.
    • - (Delete) : 기존 리소스를 삭제합니다.
    • -/+ (Replace) : 기존 리소스를 새로운 리소스로 교체합니다. 기본적으로 삭제 후 생성하는 방식입니다.
      • create_before_destroy 옵션을 사용하면 기본 동작인 삭제 후 생성 대신, 먼저 새로운 리소스를 생성한 다음에 기존 리소스를 삭제할 수 있습니다.

 

유형 별 실습

  • 구성 파일에 추가된 리소스와 State에 따라 발생할 수 있는 동작들을 표로 정리해 보겠습니다.
유형 리소스 - 구성 파일(*.tf) State 파일 실제 리소스 예상 동작
1 O     리소스 생성
2 O O   리소스 생성
3 O O O 동작 없음
4   O O 리소스 삭제
5     O 동작 없음

 

유형1 : 신규 리소스 정의  리소스 생성

  • main.tf 파일 생성
locals {
  name = "mytest"
}

resource "aws_iam_user" "myiamuser1" {
  name = "${local.name}1"
}

resource "aws_iam_user" "myiamuser2" {
  name = "${local.name}2"
}
  • 실행 및 확인
# 리소스 배포
terraform init && terraform apply -auto-approve

# 상태 파일에서 관리되는 리소스의 목록 출력
terraform state list

# 특정 리소스의 상세 정보 출력
terraform state show aws_iam_user.myiamuser1

# 현재 디렉토리 내에 상태 파일들 확인
ls *.tfstate

# 상태 파일 출력(JSON 형식)
cat terraform.tfstate | jq

# 재실행 시 테라폼은 멱등성을 보장하는가?
terraform apply -auto-approve

# 현재 디렉토리 내에 상태 파일들 확인
ls *.tfstate

# iam 사용자 리스트 확인
aws iam list-users | jq

 

유형2 : 실제 리소스 수동 제거  리소스 생성

  • 실행 및 확인
# 실제 리소스 수동 제거
aws iam delete-user --user-name mytest1
aws iam delete-user --user-name mytest2

# iam 사용자 리스트 확인
aws iam list-users | jq

# 상태 파일에서 관리하는 리소스 목록 출력
terraform state list

# 아래 명령어 실행 결과 차이는?
terraform plan

# 이 옵션은 테라폼이 원격 인프라의 현재 상태를 확인하기 위해 상태 파일을 업데이트(refresh)하지 않도록 합니다.
terraform plan -refresh=false

# 상태 파일 출력(JSON 형식)
cat terraform.tfstate | jq .serial

# 리소스 배포
terraform apply -auto-approve

# 상태 파일에서 관리하는 리소스 목록 출력
terraform state list

# 상태 파일 출력(JSON 형식)
cat terraform.tfstate | jq .serial

# iam 사용자 리스트 확인
aws iam list-users | jq

 

유형3 : 구성 파일, State, 형상 모두 일치하는 경우 → 동작 없음

  • 실행 및 확인
# 리소스 배포
terraform apply -auto-approve

# 상태 파일 출력(JSON 형식)
cat terraform.tfstate | jq .serial

# 리소스 배포
terraform apply -auto-approve

# 상태 파일 출력(JSON 형식)
cat terraform.tfstate | jq .serial

# 리소스 배포
terraform apply -auto-approve

# 상태 파일 출력(JSON 형식)
cat terraform.tfstate | jq .serial

 

유형4 : 구성 파일에서 일부 리소스 삭제 → 리소스 삭제

  • main.tf 파일 수정
locals {
  name = "mytest"
}

resource "aws_iam_user" "myiamuser1" {
  name = "${local.name}1"
}
  • 실행 및 확인
# 리소스 배포
terraform apply -auto-approve

# 상태 파일에서 관리하는 리소스 목록 출력
terraform state list

# 특정 리소스의 상세 정보 출력
terraform state show aws_iam_user.myiamuser1

# 현재 디렉토리 내에 상태 파일들 확인
ls *.tfstate

# 상태 파일 출력(JSON 형식)
cat terraform.tfstate | jq

# iam 사용자 리스트 확인
aws iam list-users | jq

 

유형6 : 실수로 tfstate 파일 삭제 → 리소스 생성 실패

  • 실행 및 확인
# 상태 파일에서 관리하는 리소스 목록 출력
terraform state list

# 실수로 tfstate 파일 삭제
rm -rf terraform.tfstate*

# 실행 계획
terraform plan

# iam 사용자 리스트 확인
aws iam list-users | jq

# 실행 계획
terraform plan -refresh=false

# 리소스 배포
terraform apply -auto-approve

# 상태 파일에서 관리하는 리소스 목록 출력
terraform state list

# 상태 파일 출력(JSON 형식)
cat terraform.tfstate | jq

# iam 사용자 리스트 확인
aws iam list-users | jq

  • 테라폼 상태 파일과 실제 인프라가 불일치하는 경우 복구하는 방법 중 하나는 import를 사용하는 것입니다.

 

유형7 : 실수로 tfstate 파일 삭제 → import를 사용하여 tfstate 파일 복구

  • 실행 및 확인
# iam 사용자 리스트 확인
aws iam list-users | jq

# import 도움말 : 빨간색 설명 출력...
terraform import
...
The import command expects two arguments.
Usage: terraform [global options] import [options] ADDR ID
...

# ADDR은 테라폼 구성 파일 내 리소스의 주소를 의미
# ID는 원격 인프라에서 해당 리소스를 식별하는 고유 ID
terraform import aws_iam_user.myiamuser1 mytest1

# 상태 파일에서 관리하는 리소스 목록 출력
terraform state list

# 상태 파일 출력(JSON 형식)
cat terraform.tfstate | jq

# 리소스 배포
terraform apply -auto-approve

 

Terraform Backend : AWS S3 + DynamoDB

  • [사전 준비 1] 원격 공용 저장소 AWS S3 생성
# provider.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.4.0"
    }
  }

  required_version = ">= 1.4"
}

provider "aws" {}

# main.tf
resource "aws_s3_bucket" "main" {
  bucket = var.bucket_name

  tags = {
    Name = "terraform test"
  }
}

resource "aws_s3_bucket_versioning" "main" {
  bucket = aws_s3_bucket.main.id

  versioning_configuration {
    status = "Enabled"
  }
}

# variables.tf
variable "bucket_name" {
  type = string
}

# terraform.tfvars
bucket_name = "<닉네임>-hello-t1014-remote-backend"

# 리소스 배포
terraform init && terraform apply -auto-approve

# 상태 파일에서 관리하는 리소스 목록 출력
terraform state list

# s3 리스트 확인
aws s3 ls

  • [사전 준비 2] Locking을 위한 DynamoDB 테이블 생성
    • DynamoDB를 사용하여 잠금을 구현하려면 LockID라는 기본 키가 있는 DynamoDB 테이블을 생성해야 합니다.
# main.tf
resource "aws_s3_bucket" "main" {
  bucket = var.bucket_name

  tags = {
    Name = "terraform test"
  }
}

resource "aws_s3_bucket_versioning" "main" {
  bucket = aws_s3_bucket.main.id

  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_dynamodb_table" "terraform_state_lock" {
  name         = "terraform-lock"
  hash_key     = "LockID"
  billing_mode = "PAY_PER_REQUEST"

  attribute {
    name = "LockID"
    type = "S"
  }
}

# 리소스 배포
terraform init && terraform apply -auto-approve

# 상태 파일에서 관리하는 리소스 목록 출력
terraform state list

# 특정 리소스의 상세 정보 출력
terraform state show aws_dynamodb_table.terraform_state_lock

# DynamoDB 테이블 생성 확인
aws dynamodb list-tables --output text
aws dynamodb describe-table --table-name terraform-lock | jq
aws dynamodb describe-table --table-name terraform-lock --output table

  • 테라폼 백엔드 설정 및 적용
# provider.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.4.0"
    }
  }

  backend "s3" {
    bucket         = "<닉네임>-hello-t1014-remote-backend"
    key            = "terraform/state-test/terraform.tfstate"
    region         = "ap-northeast-2"
    dynamodb_table = "terraform-lock"
  }

  required_version = ">= 1.4"
}

provider "aws" {}

# main.tf
...
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr

  tags = {
    Name = "terraform VPC"
  }
}

# variables.tf
...
variable "vpc_cidr" {
  type = string
}

# terraform.tfvars
...
vpc_cidr = "192.168.0.0/16"

# 리소스 배포
terraform init && terraform apply -auto-approve

# 상태 파일에서 관리하는 리소스 목록 출력
terraform state list

# 현재 디렉토리 내에 상태 파일들 확인
ls *.tfstate

# AWS S3 버킷 내에 tfstate 파일 확인
MYBUCKET=<닉네임>-hello-t1014-remote-backend
aws s3 ls s3://$MYBUCKET --recursive --human-readable --summarize

  • VPC 태그 수정 후 Locking 확인
# main.tf 수정
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr

  tags = {
    Name = "terraform VPC 2"
  }
}

# apply 후 DynamoDB 테이블 확인
terraform apply
...
 Enter a value: <입력하지 않고 대기>
  • LockID 정보 확인 후 apply
    • LockID : woo-hello-t1014-remote-backend/terraform/state-test/terraform.tfstate
    • Info : {"ID":"e247b53f-3783-1268-93cd-494dc31a59f6","Operation":"OperationTypeApply","Info":"","Who":"DESKTOP-xxxx\\우인혁@DESKTOP-xxxx","Version":"1.8.5","Created":"2024-07-06T18:49:27.7582799Z","Path":"woo-hello-t1014-remote-backend/terraform/state-test/terraform.tfstate"}

  • apply 완료 후 다시 DynamoDB 테이블 확인

  • S3 버저닝 정보 확인
# main.tf 수정
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr

  tags = {
    Name = "terraform VPC 3"
  }
}

# 리소스 배포
terraform apply -auto-approve

# S3 버킷에 파일 확인 
aws s3 ls s3://$MYBUCKET --recursive --human-readable --summarize

# 버저닝된 파일 확인
aws s3api list-object-versions --bucket $MYBUCKET | egrep "Key|VersionId|LastModified"

  • S3 버킷에 버전 표시 확인

 

테라폼 백엔드 단점

  • backend 블록에서 변수나 참조를 사용할 수 없는 제약 때문에, S3 버킷 이름, 리전, DynamoDB 테이블 이름 등을 직접 입력해야 하는 불편함이 있습니다.
# 사용 불가
terraform {
  backend "s3" {
    bucket         = var.bucket
    region         = var.region
    dynamodb_table = var.dynamodb_table
     key           = "example/terraform.tfstate"
    encrypt        = true
  }
}
  • partial configuration 기능을 통해 일부 매개 변수를 전달하여 사용할 수 있습니다.
# backend.hcl
bucket         = "terraform-up-and-running-state"
region         = "us-east-2"
dynamodb_table = "terraform-up-and-running-locks"
encrypt        = true
  • 이 방법을 이용해도 모듈마다 고유한 key 값을 설정해야 하기 때문에 key 매개 변수는 테라폼 코드에 있어야 합니다.
terraform {
  backend "s3" {
    key            = "example/terraform.tfstate"
  }
}
  • 각 모듈에 대해 backend 설정을 초기화하려면 terraform init 명령을 실행합니다. 이 때 -backend-config 인수를 사용하여 필요한 backend 매개 변수를 전달하면 됩니다.
terraform init -backend-config=backend.hcl