EMD Blog

Terraform State 본문

IaC/Terraform

Terraform State

EmaDam 2022. 9. 3. 10:39

Terraform state에 관해서 꼭 알아야 할 부분들 정리해보았음.

Terraform은 현재 구성에 대한 정보를 tfstate 파일에 저장한다. tfstate 파일에는 생성한 리소스와의 바인딩 정보가 저장되어 있기 때문에 인프라를 변경 할 경우 이 tfstate 파일을 참고해 리소스를 생성 할 지, 삭제 할지 등을 판단하게 된다.

Terraform의 동작 방식은 아래와 같다. (apply 시)

  1. terraform refresh 후 계획 생성(plan)
  2. 계획에 맞게 리소스 생성 (apply)
  3. terraform refresh

terraform refresh는 현재 tfstate의 바인딩 정보와 실제 생성된 리소스를 비교 후 tfstate 파일에 반영하는 작업이다. 중요한 것은 tfstate에 기록된 바인딩 정보와 관련이 있는 리소스 범위 내에서만 반영된다. 만약에 리소스를 수동으로 생성하고 refresh 할 경우 terraform에 바인딩 정보가 없는 리소스이기 때문에 tfstate 파일에 반영되지 않는다.

State 관리

terraform state 명령어를 사용하면 state를 관리 할 수 있다. tfstate 파일을 직접 수정하는 것은 권장하지 않는다.

list                state 상의 리소스 전체 출력
mv                  한 리소스의 바인딩 정보 변경 (아래 문서 참고)
pull                원격 및 로컬 state 정보를 다운로드
push                local의 state를 기준으로 remote state 변경
replace-provider    state 상의 공급자 변경
rm                  state에서 특정 리소스 제거
show                state에서 단일 리소스 출력

더 자세한 내용은 아래 문서들 참고

https://www.terraform.io/cli/commands/state/list

https://www.terraform.io/cli/commands/state/mv

https://www.terraform.io/cli/commands/state/pull

https://www.terraform.io/cli/commands/state/push

https://www.terraform.io/cli/commands/state/replace-provider

https://www.terraform.io/cli/commands/state/rm

https://www.terraform.io/cli/commands/state/show

만약 다른 소프트웨어에서 state 정보를 사용해야 할 경우 아래 두 명령어를 참고 하면 된다.

https://www.terraform.io/cli/commands/output

https://www.terraform.io/cli/commands/show

State의 목적

  • 다양한 공급자를 지원하고 해당 공급자 내에서 리소스와 매핑하기 위함. 만약 state 파일이 없었다면 모든 공급자와 모든 리소스들을 지원 할 수 있는 매핑 데이터베이스를 구축 할 수가 없음.
  • 리소스 종속성과 같은 메타데이터를 추적하기 위해 state 파일이 필요함. 올바른 작동을 보장하기 위해 제일 최근의 종속성 세트 사본을 유지 함.
  • Terraform은 원래 plan/apply 동작에서 리소스 동기화 작업이 발생함 (refresh), 만약 대규모 인프라를 구성 할 경우 선택적으로 이를 제한 할 수 있음.(refresh=fasle, -target 등)
  • 만약 여러명이서 협업 할 경우 이 remote state 설정을 통해 동일한 state로 작업 가능 함.

terraform_remote_state

terraform_remote_state는 원격에 있는 다른 모듈의 출력 값을 가져올 때 사용 할 수 있는 Data source이다. 이 Data source는 Terraform에 내장되어 있기 때문에 따로 Provider를 설정 할 필요가 없으며, 출력 값을 사용 할 module에서 Data Source를 생성해 사용하기만 하면 된다. (Data Source의 Argument는 아래 경로 참고)

https://www.terraform.io/language/state/remote-state-data#argument-reference

Terraform Cloud의 Workspace에서 데이터를 주고 받을 때는 terraform_remote_state 대신 tfe_outputs를 사용하면 된다.

https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/data-sources/outputs

위 처럼 지정하면 remote state상의 output 정보를 받아 사용 할 수 있긴 하지만, state 내의 sensitive한 정보에는 접근 할 수가 없다는 단점(terraform_remote_state는 output만 가져옴)이 있다. 꼭 sensitive한 정보가 아니더라도 state 상의 다른 정보들이 필요한 상황이 생길 수 있는데 이 때는 저장소에 있는 state에 직접 접근 할 수 있도록 설정하면 된다. (아래 링크의 표 참고)

https://www.terraform.io/language/state/remote-state-data#alternative-ways-to-share-data-between-configurations

terraform_remote_state으로 remote state의 output 데이터를 받아올 수 있다는 뜻은 해당 권한을 가지고 다른 사용자나 서버가 네트워크 요청 방식을 통해 state 파일에 접근 할 수 있다는 뜻이 된다. 만약 state상에 보안적으로 중요한 정보가 존재 할 경우 terraform_remote_state를 사용하지 않는 것이 좋다.

이렇게 하면 꼭 Terraform이 아니더라도 현재 state에 대한 정보를 다른 곳에서도 사용 할 수 있기 때문에 state를 저장 할 때는 다른 곳에서도 접근 할 수 있는 위치(Public한 곳을 말하는 것이 아닌 접근 제어가 가능 한 위치)에 저장하는 것이 좋다. (예를 들면 s3)

위처럼 저장한 데이터르 가져와 사용 할 때는 아래 두 가지 함수를 사용해 데이터를 변환 해주면 된다.

참고로 저런 데이터들을 받아서 가공하는 중간 모듈(데이터 전용 모듈)을 만들어서 캡슐화 할 수도 있다.(아래 이미지 참고)

Backend

Backend에 위치를 지정하면 그 곳에 state가 저장된다. Backend를 로컬이 아닌 다른 곳으로 지정하게 되면 이후의 state는 해당 위치에만 저장 된다. 만약 로컬에서 state를 저장하다가 Backend를 원격으로 변경하면 로컬의 state 파일은 빈 파일이 된다. 하지만 다시 로컬에 state가 저장되는 경우가 있는데, Backend에 state 작성 시 오류가 발생하면 로컬에 상태를 저장하게 되며 오류가 해결되면 사용자가 직접 로컬 state를 Backend로 push 해주어야 한다.

state를 Push 하는 것은 위험하기 때문에 특별한 상황이 아니면 권장하지 않으며, Terraform은 아래와 같은 상황에서 Push를 제한하고 있다.

  • Differing lineage : lineage는 state가 생성될 때 할당되는 ID인데, 이 값이 다르다는 것은 Backend의 state와 아예 다른 state일 가능성이 높아 Push가 거부 된다.
  • Higher serial : 모든 state에는 serial number가 존재하는데, Push시 Backend의 serial number가 더 높으면 로컬에 state 생성 시점과 push 시점 사이에 Backend의 state에 변경사항이 발생한 것으로 해석되어 Push가 거부된다(버전 불일치).

만약 위와 같은 이유로 Push가 막혔음에도 반드시 Push를 해야한다면 -force 옵션을 줘서 강제로 Push 할 수 있다. 하지만 이 경우에도 가급적이면 terraform state pull 먼저 사용할 것을 권장하고 있다.

상태 잠금

Backend는 원격에 존재하는 state에 다른 여러 사용자가 동시에 작업 할 수 없도록 lock 기능을 제공한다. 모든 Backend에 지원하는 것은 아니고 일부 Backend만 지원한다. 현재 지원하는 Backend가 어떤 것이 있는지, 그리고 사용하고자 하는 Backend가 lock을 지원하는 지 확인하고 싶다면 아래 경로에서 “Available Backends” 중 원하는 Backend를 선택해 확인해보면 된다.

https://www.terraform.io/language/settings/backends

S3 기준으로 보면은 DynamoDB에 LockID로 <BUCKET_PATH>/<STATE_FILE_NAME>-md5, digest에 체크섬이 저장된다. 직접 확인해보고 싶다면 state 파일을 다운로드 받아서 md5sum 명령어를 사용해 digest값과 비교해 볼 수 있다.

$ md5sum ~/Desktop/terraform.tfstate.json
<CHECK_SUM> /Users/emadam/Desktop/terraform.tfstate.json

이제 여기서 lock을 걸어보면 테이블에 Item이 하나 더 생성되고 Info Attribute에 lock에 대한 정보가 들어가 있는 것을 확인 할 수 있다.

{
  "ID":"<ID>",
  "Operation":"OperationTypeApply",
  "Info":"",
  "Who":"누가 lock을 걸었는지",
  "Version":"<VERSION>",
  "Created":"<LOCK_DATETIME>",
  "Path":"<STATE_FILE_PATH>"
}

위 내용은 lock 상태에서 작업 시도 할 경우 콘솔에도 표시된다. 만약 강제로 잠금을 해제하고 싶다면 동작에서 -lock=false 옵션을 주거나 force-unlock 명령어를 사용하면 된다.

$ terraform apply -lock=false
$ terraform force-unlock [options] LOCK_ID [DIR]

LOCK_ID는 콘솔에 표시되는 ID 값을 사용하면 된다. lock이 해제되면 Dynamodb에서도 lock 정보는 삭제된다.

Terraform은 현재 구성에 대한 정보를 tfstate 파일에 저장한다. tfstate 파일에는 생성한 리소스와의 바인딩 정보가 저장되어 있기 때문에 인프라를 변경 할 경우 이 tfstate 파일을 참고해 리소스를 생성 할 지, 삭제 할지 등을 판단하게 된다.

Terraform의 동작 방식은 아래와 같다. (apply 시)

  1. terraform refresh 후 계획 생성(plan)
  2. 계획에 맞게 리소스 생성 (apply)
  3. terraform refresh

terraform refresh는 현재 tfstate의 바인딩 정보와 실제 생성된 리소스를 비교 후 tfstate 파일에 반영하는 작업이다. 중요한 것은 tfstate에 기록된 바인딩 정보와 관련이 있는 리소스 범위 내에서만 반영된다. 만약에 리소스를 수동으로 생성하고 refresh 할 경우 terraform에 바인딩 정보가 없는 리소스이기 때문에 tfstate 파일에 반영되지 않는다.

State 관리

terraform state 명령어를 사용하면 state를 관리 할 수 있다. tfstate 파일을 직접 수정하는 것은 권장하지 않는다.

list                state 상의 리소스 전체 출력
mv                  한 리소스의 바인딩 정보 변경 (아래 문서 참고)
pull                원격 및 로컬 state 정보를 다운로드
push                local의 state를 기준으로 remote state 변경
replace-provider    state 상의 공급자 변경
rm                  state에서 특정 리소스 제거
show                state에서 단일 리소스 출력

더 자세한 내용은 아래 문서들 참고

Command: state list | Terraform by HashiCorp

Command: state mv | Terraform by HashiCorp

Command: state pull | Terraform by HashiCorp

Command: state push | Terraform by HashiCorp

Command: state replace-provider | Terraform by HashiCorp

Command: state rm | Terraform by HashiCorp

Command: state show | Terraform by HashiCorp

만약 다른 소프트웨어에서 state 정보를 사용해야 할 경우 아래 두 명령어를 참고 하면 된다.

Command: output | Terraform by HashiCorp

Command: show | Terraform by HashiCorp

State의 목적

  • 다양한 공급자를 지원하고 해당 공급자 내에서 리소스와 매핑하기 위함. 만약 state 파일이 없었다면 모든 공급자와 모든 리소스들을 지원 할 수 있는 매핑 데이터베이스를 구축 할 수가 없음.
  • 리소스 종속성과 같은 메타데이터를 추적하기 위해 state 파일이 필요함. 올바른 작동을 보장하기 위해 제일 최근의 종속성 세트 사본을 유지 함.
  • Terraform은 원래 plan/apply 동작에서 리소스 동기화 작업이 발생함 (refresh), 만약 대규모 인프라를 구성 할 경우 선택적으로 이를 제한 할 수 있음.(refresh=fasle, -target 등)
  • 만약 여러명이서 협업 할 경우 이 remote state 설정을 통해 동일한 state로 작업 가능 함.

terraform_remote_state

terraform_remote_state는 원격에 있는 다른 모듈의 출력 값을 가져올 때 사용 할 수 있는 Data source이다. 이 Data source는 Terraform에 내장되어 있기 때문에 따로 Provider를 설정 할 필요가 없으며, 출력 값을 사용 할 module에서 Data Source를 생성해 사용하기만 하면 된다. (Data Source의 Argument는 아래 경로 참고)

The terraform_remote_state Data Source | Terraform by HashiCorp

Terraform Cloud의 Workspace에서 데이터를 주고 받을 때는 terraform_remote_state 대신 terraform_remote_state를 사용하면 된다.

Terraform Registry

위 처럼 지정하면 remote state상의 output 정보를 받아 사용 할 수 있긴 하지만, state 내의 sensitive한 정보에는 접근 할 수가 없다는 단점(terraform_remote_state는 output만 가져옴)이 있다. 꼭 sensitive한 정보가 아니더라도 state 상의 다른 정보들이 필요한 상황이 생길 수 있는데 이 때는 저장소에 있는 state에 직접 접근 할 수 있도록 설정하면 된다. (아래 링크의 표 참고)

The terraform_remote_state Data Source | Terraform by HashiCorp

terraform_remote_state으로 remote state의 output 데이터를 받아올 수 있다는 뜻은 해당 권한을 가지고 다른 사용자나 서버가 네트워크 요청 방식을 통해 state 파일에 접근 할 수 있다는 뜻이 된다. 만약 state상에 보안적으로 중요한 정보가 존재 할 경우 terraform_remote_state를 사용하지 않는 것이 좋다.

이렇게 하면 꼭 Terraform이 아니더라도 현재 state에 대한 정보를 다른 곳에서도 사용 할 수 있기 때문에 state를 저장 할 때는 다른 곳에서도 접근 할 수 있는 위치(Public한 곳을 말하는 것이 아닌 접근 제어가 가능 한 위치)에 저장하는 것이 좋다. (예를 들면 s3)

위처럼 저장한 데이터르 가져와 사용 할 때는 아래 두 가지 함수를 사용해 데이터를 변환 해주면 된다.

참고로 저런 데이터들을 받아서 가공하는 중간 모듈(데이터 전용 모듈)을 만들어서 캡슐화 할 수도 있다.(아래 이미지 참고)

Backend

Backend에 위치를 지정하면 그 곳에 state가 저장된다. Backend를 로컬이 아닌 다른 곳으로 지정하게 되면 이후의 state는 해당 위치에만 저장 된다. 만약 로컬에서 state를 저장하다가 Backend를 원격으로 변경하면 로컬의 state 파일은 빈 파일이 된다. 하지만 다시 로컬에 state가 저장되는 경우가 있는데, Backend에 state 작성 시 오류가 발생하면 로컬에 상태를 저장하게 되며 오류가 해결되면 사용자가 직접 로컬 state를 Backend로 push 해주어야 한다.

state를 Push 하는 것은 위험하기 때문에 특별한 상황이 아니면 권장하지 않으며, Terraform은 아래와 같은 상황에서 Push를 제한하고 있다.

  • Differing lineage : lineage는 state가 생성될 때 할당되는 ID인데, 이 값이 다르다는 것은 Backend의 state와 아예 다른 state일 가능성이 높아 Push가 거부 된다.
  • Higher serial : 모든 state에는 serial number가 존재하는데, Push시 Backend의 serial number가 더 높으면 로컬에 state 생성 시점과 push 시점 사이에 Backend의 state에 변경사항이 발생한 것으로 해석되어 Push가 거부된다(버전 불일치).

만약 위와 같은 이유로 Push가 막혔음에도 반드시 Push를 해야한다면 -force 옵션을 줘서 강제로 Push 할 수 있다. 하지만 이 경우에도 가급적이면 terraform state pull 먼저 사용할 것을 권장하고 있다.

상태 잠금

Backend는 원격에 존재하는 state에 다른 여러 사용자가 동시에 작업 할 수 없도록 lock 기능을 제공한다. 모든 Backend에 지원하는 것은 아니고 일부 Backend만 지원한다. 현재 지원하는 Backend가 어떤 것이 있는지, 그리고 사용하고자 하는 Backend가 lock을 지원하는 지 확인하고 싶다면 아래 경로에서 “Available Backends” 중 원하는 Backend를 선택해 확인해보면 된다.

Backend Overview - Configuration Language | Terraform by HashiCorp

S3 기준으로 보면은 DynamoDB에 LockID로 <BUCKET_PATH>/<STATE_FILE_NAME>-md5, digest에 체크섬이 저장된다. 직접 확인해보고 싶다면 state 파일을 다운로드 받아서 md5sum 명령어를 사용해 digest값과 비교해 볼 수 있다.

1$ md5sum ~/Desktop/terraform.tfstate.json
2<CHECK_SUM> /Users/emadam/Desktop/terraform.tfstate.json

이제 여기서 lock을 걸어보면 테이블에 Item이 하나 더 생성되고 Info Attribute에 lock에 대한 정보가 들어가 있는 것을 확인 할 수 있다.

1{
2  "ID":"<ID>",
3  "Operation":"OperationTypeApply",
4  "Info":"",
5  "Who":"누가 lock을 걸었는지",
6  "Version":"<VERSION>",
7  "Created":"<LOCK_DATETIME>",
8  "Path":"<STATE_FILE_PATH>"
9}

위 내용은 lock 상태에서 작업 시도 할 경우 콘솔에도 표시된다. 만약 강제로 잠금을 해제하고 싶다면 동작에서 -lock=false 옵션을 주거나 force-unlock 명령어를 사용하면 된다.

1$ terraform apply -lock=false
2$ terraform force-unlock [options] LOCK_ID [DIR]

LOCK_ID는 콘솔에 표시되는 ID 값을 사용하면 된다. lock이 해제되면 Dynamodb에서도 lock 정보는 삭제된다.

Workspace

별 다른 설정을 하지 않았다면 Terraform 구성의 state에 대한 정보가 적혀있는 tfstate파일은 보통 1개만 생성되어 있다(lock 제외). 하지만 Terraform은 하나의 구성으로 여러개의 상태를 만들 수 있는 기능을 제공하는데 바로 Workspace다. 처음에 자동으로 생성되는 state에 대한 workspace의 이름은 “default“이고 이후에 수동으로 생성하는 workspace의 이름은 직접 지정하면 된다. (Git의 branch랑 느낌이 비슷한데 코드 대신 state를 관리하는 것이라 생각하면 된다.)

용도

이 workspace는 보통 환경 분리를 위해서 사용된다. 실제로 Terraform 0.9 버전에서 environment라는 이름을 사용했다가 0.10 버전 부터 workspace라는 이름을 사용하기 시작했다.

참고로 Terraform Workspace와 Terraform Cloud의 Workspace는 다른 개념이다.

하지만 주의해야 하는 점은 terraform workspace를 단일 구성에 대한 state를 분리 하는 것이지 구성을 분리하는 것이 아니라는 점이다.

활용법

Terraform 구성에서 workspace의 이름은 변수로 사용 될 수 있다. 그렇기 때문에 아래 처럼 활용이 가능하다.

사용법(${terraform.workspace})

1resource "aws_instance" "example" {
2  count = "${terraform.workspace == "default" ? 5 : 1}"
3
4  ...
5  tags = {
6    Name = "web-${terraform.workspace}"
7  }
8}

또한 workspace를 활용해서 작업을 하게되면 특정 작업 시 새로운 workspace를 만들어 다른 환경의 인프라에 영향을 주지 않고 빠르게 별도의 인프라를 생성해 테스트 해볼 수 있다. 만약 Terraform workspace 없이 작업을 한다고 하면 기존 인프라(prod든, dev든)에 직접 테스트하거나, 새로운 환경 구성에 대한 여러 설정을 추가해줘야 할 것이다.

관리

CLI에서 workspace를 관리 할 경우 아래의 명령어를 사용 할 수 있다.

Command: workspace new | Terraform by HashiCorp

Command: workspace select | Terraform by HashiCorp

Command: workspace delete | Terraform by HashiCorp

Command: workspace show | Terraform by HashiCorp

Command: workspace list | Terraform by HashiCorp

만약 Backend가 local로 설정되어 있으면 workspace 별 상태는 terraform.tfstate.d에 저장되며 원격 저장소에 저장 할 경우 원격 저장소에 따로 저장(S3 기준 /env: 폴더) 된다. 그렇기 때문에 원격 저장소로 지정 할 경우 팀원들이 workspace를 공유 할 수 있다. 또한 원격 저장소에 workspace 별 state가 저장되어 있더라도 선택한 workspace에 대한 정보는 로컬의 .terraform 디렉토리에 저장되기 때문에 모든 팀원이 각자 다른 workspace에서 작업을 할 수도 있다.

위 내용은 Terraform Code 실행 주체가 workspace를 정해서 실행해야 한다는 뜻이다. 그런데 Terraform Cloud의 경우 실행 주체인 Terraform Cloud 자체가 workspace(TFC 말고 TF)를 선택 할 수 없기 때문에 Terraform Cloud에서는 의미가 없다. 애초에 Terraform Cloud는 state를 직접 관리한다.

보안

Access Key나 계정정보, DB 암호 등의 중요한 데이터들은 암호화 할 필요가 있다. 만약 중요한 데이터가 하나라도 포함되어 있다면 해당 데이터가 아닌 state 파일 자체를 암호화해서 보관하는 것이 좋다. Backend를 local로 지정했다면 state 파일도 local에 저장 될 것이기 때문에 외부에 유출되지 않도록 조심해야하고, 원격 저장소에 저장 한 경우에는 저장소의 암호화 옵션과 액세스 추적을 활성화 하는 것이 좋다. Terraform Cloud의 경우는 자체적으로 암호화하고 데이터 전송시에도 TLS로 보호한다. 또한 변경 이력을 유지해 액세스 추적에 활용 할 수도 있다. (Enterprise는 감사 로깅 지원)