Terraform

[Terrafrom101 Study - 4w ] State

su''@ 2024. 7. 7. 02:00
Terrafrom T101 4기 실습 스터디 게시글입니다.
"테라폼으로 시작하는 IaC" 도서를 참고하여 정리했습니다. 

 

State

State의 목적과 의미
  • 상태 파일 확인 실습
    mkdir 5.0 && cd 5.0
    touch 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
    
    # 상태 파일 확인(VSCODE) : JSON 형식
    ls
    cat terraform.tfstate | jq
    ...
    "serial": 2,
    ...
    
    # 아래 정보는 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*
      terraform.tfstate        terraform.tfstate.backup
      
      diff terraform.tfstate terraform.tfstate.backup
      <   "serial": 4,
      ---
      >   "serial": 2,
      ...
    • 한번 더 태그 수정 후 상태 파일 확인
      provider "aws" {
        region  = "ap-northeast-2"
      }
      
      resource "aws_vpc" "myvpc" {
        cidr_block       = "10.10.0.0/16"
      
        tags = {
          Name = "tf-state-tag-change"
        }
      }
      
      
      -------------------------------------------------
      
      
      # 배포
      terraform plan && terraform apply -auto-approve
      
      # 상태 파일 비교(바로 직전 상태 백업)
      ls terraform.tfstate*
      terraform.tfstate        terraform.tfstate.backup
      
      diff terraform.tfstate terraform.tfstate.backup
      <   "serial": 6,
      ---
      >   "serial": 4,
      ...
    • 다음 실습을 위해 삭제: terraform destroy -auto-approve

 

  • 이론 내용

  • 상태 파일은 배포할 때마다 변경되는 프라이빗 API private API로, 오직 테라폼 내부에서 사용하기 위한 것입니다.
  • 테라폼 상태 파일직접 편집하거나 직접 읽는 코드로 작성해서는 안됩니다.

 

팀 단위에서 테라폼 운영 시 문제점

  1. 상태 파일을 저장하는 공유 스토리지 Shared storage for state files
    • 각 팀원이 동일한 테라폼 상태 파일 사용을 위해서, 공유 위치에 저장이 필요
  2. 상태 파일 잠금 Locking state files
    • 잠금 기능 없이 두 팀원이 동시에 테라폼 실행 시 여러 테라폼 프로세스가 상태 파일을 동시에 업데이트하여 충돌 가능(경쟁 상태 race condition)
  3. 상태 파일 격리 Isolating state files
    • 예를 들면 테스트 dev 와 검증 stage 과 상용 prodction 각 환경에 대한 격리가 필요

 

상태 파일 공유로 버전 관리 시스템 비추천

  1. 수동 오류 Manual error
    • 테라폼을 실행하기 전에 최신 변경 사항을 가져오거나 실행하고 나서 push 하는 것을 잊기 쉽습니다(?).
    • 팀의 누군가가 이전 버전의 상태 파일로 테라폼을 실행하고, 그 결과 실수로 이전 버전으로 롤백하거나 이전에 배포된 인프라를 복제하는 문제가 발생 할 수 있음.
  2. 잠금 Locking
    • 대부분의 버전 관리 시스템은 여러 명의 팀 구성원이 동시에 하나의 상태 파일에 terraform apply 명령을 실행하지 못하게 하는 잠금 기능이 제공되지 않음.
  3. 시크릿 Secrets
    • 테라폼 상태 파일의 모든 데이터는 평문으로 저장됨. 민감 정보가 노출될 위험.

 

지원되는 원격 백엔드 : AWS S3, Azure Blob Storage, Google Cloud Storage, Consul, Postgres database, k8s secret 등 - 링크

  1. 수동 오류 해결 : plan/apply 실행 시 마다 해당 백엔드에서 파일을 자동을 로드, apply 후 상태 파일을 백엔드에 자동 저장
  2. 잠금 : apply 실행 시 테라폼은 자동으로 잠금을 활성화, -lock-timout=<TIME> 로 대기 시간 설정 지정 가능
  3. 시크릿 : 대부분 원격 백엔드는 기본적으로 데이터를 보내거나 상태 파일을 저장할 때 암호화하는 기능을 지원

S3 + DynamoDB 동작

그림출처 https://github.com/binbashar/terraform-aws-tfstate-backend

  • T101 2기 책 내용 소개 및 확인 : 테라폼은 Stateful 애플리케이션. 프로비저닝 결과 State를 저장하고 추적에 활용
    • 개인 1인 : 로컬 환경으로 terraform.tfstate 파일에 JSON 형태로 저장
    • 이나 조직 : 공동 관리를 위해 원격 저장소에 저장해 공유 - 링크
    • State에는 작업자가 정의한 코드와 실제 반영된 프로비저닝 결과를 저장하고, 이 정보를 토대로 이후의 리소스 생성, 수정, 삭제에 대한 동작 판단 작업을 수행
    State 역할
    • State에는 테라폼 구성과 실제를 동기화하고 각 리소스에 고유한 아이디(리소스 주소)로 맵핑
    • 리소스 종속성과 같은 메타데이터를 저장하고 추적
    • 테라폼 구성으로 프로비저닝 결과를 캐싱하는 역할을 수행
  • 실습을 위해서 5.1 디렉터리를 신규 생성 후 열기 → main.tf 파일 생성 - random_provider
    mkdir 5.1 && cd 5.1
    touch main.tf
    
    -------------------------------------------------
    
    #main.tf
    resource "random_password" "mypw" {
      length           = 16
      special          = true
      override_special = "!#$%"
    }
 

Terraform Registry

 

registry.terraform.io

  • 실행 : 랜덤 프로바이더는 테라폼 구성 내에서 무작위로 기입해야 되는 숫자, 패스워드, 문자열 등의 값을 생성하는 데 사용
    # 
    terraform init && terraform plan
    ls *.tfstate
    
    #
    terraform apply -auto-approve
    terraform state list
    terraform state show random_password.mypw
    
    # (참고) sensitive value 내용은 테라폼 콘솔에서 보일까요? 
    echo "random_password.mypw" | terraform console
    echo "random_password.mypw.result" | terraform console
    
    # VSCODE에서 확인
    ls *.tfstate
    cat terraform.tfstate | jq
    cat terraform.tfstate | jq | grep result
    YLGmKyb3jOuI8sEf
  • 테라폼에서는 JSON 형태로 작성된 State를 통해 속성인수를 읽고 확인할 수 있다. 테라폼에서는 typename으로 고유한 리소스분류하며, 해당 리소스의 속성인수를 구성과 비교해 대상 리소스생성, 수정, 삭제한다.
  • State는 테라폼만을 위한 API로 정의할 수도 있다. Plan을 실행하면 암묵적으로 refresh 동작을 수행하면서 리소스 생성의 대상(클라우드 등)과 State를 기준으로 비교하는 과정을 거친다. 이 작업은 프로비저닝 대상의 응답 속도와 기존 작성된 State의 리소스 양에 따라 속도 차이가 발생한다. 대량의 리소스를 관리해야 하는 경우 Plan 명령에서 -refresh=false 플래그를 사용해 State를 기준으로 실행 계획을 생성하고, 이를 실행에 활용해 대상 환경과의 동기화 과정을 생략할 수 있다.
    # 실행 계획 생성 시 저장되어 있는 State와 실제 형상을 비교하는 기본 실행
    time terraform plan
    
    # 실행 계획 생성 시 실제 형상과 비교하지 않고 실행 계획을 생성하는 -refresh=false 옵션
    time terraform plan -refresh=false
State 동기화

소개 : 테라폼 구성 파일은 기존 State와 구성을 비교해 실행 계획에서 생성, 수정, 삭제 여부를 결정한다

  • 테라폼 구성과 State 흐름 : Plan 과 Apply 중 각 리소스에 발생할 수 있는 네 가지 사항, 아래 실행 계획 출력 기호와 의미기호 의미
    + Create
    - Destroy
    -/+ Replace
    ~ Updated in-place
    • Replace 동작은 기본값을 삭제 후 생성하지만 lifecycle의 create_before_destroy 옵션을 통해 생성 후 삭제 설정 가능

유형 별 실습 + 문제상황 → 복구 import*

테라폼 구성에 추가된 리소스와 State에 따라 어떤 동작이 발생하는지 다음 표로 살펴본다유형 구성 리소스 정의(*.tf) State 구성 데이터 실제 리소스 기본 예상 동작

1 있음     리소스 생성
2 있음 있음   리소스 생성
3 있음 있음 있음 동작 없음
4   있음 있음 리소스 삭제
5     있음 동작 없음

 

 

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

  • 실습을 위해서 5.2 디렉터리를 신규 생성 후 열기 → main.tf 파일 생성
    mkdir 5.2 && cd 5.2
    touch 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
    cat terraform.tfstate | jq
    
    # 아래 실행 시 어떻게 되나? 테라폼은 멱등성 한가?
    terraform apply -auto-approve
    ls *.tfstate
    
    # iam 사용자 리스트 확인
    aws iam list-users | jq

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

  • 실행
    # 실제 리소스 수동 제거
    aws iam delete-user --user-name mytest1
    aws iam delete-user --user-name mytest2
    aws iam list-users | jq
    terraform state list
    
    # 아래 명령어 실행 결과 차이는?
    terraform plan
    terraform plan -refresh=false
    cat terraform.tfstate | jq .serial
    
    #
    terraform apply -auto-approve
    terraform state list
    cat terraform.tfstate | jq .serial
    
    # iam 사용자 리스트 확인
    aws iam list-users | jq

유형3 : Apply → Apply ← 코드, State, 형상 모두 일치한 경우

  • 실행
    #
    terraform apply -auto-approve
    cat terraform.tfstate | jq .serial
    
    terraform apply -auto-approve
    cat terraform.tfstate | jq .serial
    
    terraform apply -auto-approve
    cat terraform.tfstate | jq .serial

유형4 : 코드에서 일부 리소스 삭제 → Apply

  • 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
    cat terraform.tfstate | jq
    
    # iam 사용자 리스트 확인
    aws iam list-users | jq

유형6 : 실수로 tfstate 파일 삭제 → plan/apply ← 책에는 없는 내용

  • 실행
    # 실수로 tfstate 파일 삭제
    terraform state list
    aws_iam_user.myiamuser1
    
    rm -rf terraform.tfstate*
    
    #
    terraform plan
    aws iam list-users | jq
    terraform plan -refresh=false
    
    #
    terraform apply -auto-approve
    terraform state list
    cat terraform.tfstate | jq
    
    # iam 사용자 리스트 확인
    aws iam list-users | jq
      ⇒ 위 상황에서 복구? 하는 방법은? import 등 방법이 있습니다!

유형7 : 실수로 tfstate 파일 삭제 시 → import 로 tfstate 파일 복구 - Docs Tutorials

https://malwareanalysis.tistory.com/633

 

  • 실행
    # iam 사용자 리스트 확인
    aws iam list-users | jq
    
    # import 도움말 : 빨간색 설명 출력...
    terraform import
    
    # ADDR은 리소스주소 , ID는 
    # terraform [global options] import [options] ADDR ID
    terraform import aws_iam_user.myiamuser1 mytest1
    aws_iam_user.myiamuser1: Importing from ID "mytest1"...
    aws_iam_user.myiamuser1: Import prepared!
      Prepared aws_iam_user for import
    aws_iam_user.myiamuser1: Refreshing state... [id=mytest1]
    
    Import successful!
    
    The resources that were imported are shown above. These resources are now in
    your Terraform state and will henceforth be managed by Terraform.
    
    #
    terraform state list
    cat terraform.tfstate | jq
    terraform apply -auto-approve
  • 다음 실습을 위해 삭제: terraform destroy -auto-approve
Terraform Backend : AWS S3 + DynamoDB

https://developer.hashicorp.com/terraform/language/settings/backends/s3

 

Backend Type: s3 | Terraform | HashiCorp Developer

Terraform can store state remotely in S3 and lock that state with DynamoDB.

developer.hashicorp.com

 

워크스페이스

 T101 1기 책 내용 상태 파일 격리 소개 State File Isolation

  • 테라폼 상태를 격리하지 않았을 때 발생하는 일 - 링크
  • 환경을 격리하는 방법
    • 작업 공간을 통한 격리 Isolation via workspaces
      • 동일한 구성에서 빠르고 격리된 테스트 환경에 유용
    • 파일 레이아웃을 이용한 격리 Isolation via file layout
      • 보다 강력하게 분리해야 하는 운영 환경에 적합
      • 테라폼 프로젝트의 파일 레이아웃 설명
        • 각 테라폼 구성 파일을 분리된 폴더에 넣기. (예. stage , prod)
        • 각 환경에 서로 다른 백엔드 구성. (예. S3 버킷 백엔드의 AWS 계정을 분리)
        • 최상위 폴더
          • stage : 테스트 환경과 같은 사전 프로덕션 워크로드 workload 환경
          • prod : 사용자용 맵 같은 프로덕션 워크로드 환경
          • mgmt : 베스천 호스트 Bastion Host, 젠킨스 Jenkins 와 같은 데브옵스 도구 환경
          • global : S3, IAM과 같이 모든 환경에서 사용되는 리소스를 배치
          각 환경별 구성 요소
          • vpc : 해당 환경을 위한 네트워크 토폴로지
          • services : 해당 환경에서 서비스되는 애플리케이션, 각 앱은 자체 폴더에 위치하여 다른 앱과 분리
          • data-storage : 해당 환경 별 데이터 저장소. 각 데이터 저장소 역시 자체 폴더에 위치하여 다른 데이터 저장소와 분리
          명명 규칙 naming conventions (예시)
          • variables.tf : 입력 변수
          • outputs.tf : 출력 변수
          • main-xxx.tf : 리소스 → 개별 테라폼 파일 규모가 커지면 특정 기능을 기준으로 별도 파일로 분리 (ex. main-iam.tf, main-s3.tf 등) 혹은 모듈 단위로 나눔
          • dependencies.tf : 데이터 소스
          • providers.tf : 공급자
  • T101 2기 책 내용 소개 : State관리하는 논리적인 가상 공간워크스페이스라고 한다 - 링크

  • 테라폼 구성 파일은 동일하지만 작업자는 서로 다른 State를 갖는 실제 대상을 프로비저닝할 수 있다.
  • 워크스페이스는 기본 default로 정의된다. 로컬 작업 환경의 워크스페이스 관리를 위한 CLI 명령어로 workspace가 있다.
    terraform workspace list
    * default
    
    
    --------------------------------------
    
    
    #실습을 위해서 5.3 디렉터리를 신규 생성 후 열기 → main.tf 파일 생성
    mkdir 5.3 && cd 5.3
    touch main.tf
    
    resource "aws_instance" "mysrv1" {
      ami           = "ami-0ea4d4b8dc1e46212"
      instance_type = "t2.micro"
      tags = {
        Name = "t101-study"
      }
    }
    
    
    --------------------------------------
    
    
    # 실행
    # [분할/터미널1] 모니터링
    export AWS_PAGER=""
    while true; do aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text ; echo "------------------------------" ; sleep 1; done
    
    #
    terraform init && terraform apply -auto-approve
    terraform state list
    
    #
    cat terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
    cat terraform.tfstate | jq -r '.resources[0].instances[0].attributes.private_ip'
    
    # 워크스페이스 확인
    terraform workspace list
    
    # graph 확인
    terraform graph > graph.dot
    
    
    --------------------------------------
    
    
    # 신규 워크스페이스 생성 및 확인
    
    # 새 작업 공간 workspace 생성 : mywork1
    terraform workspace new mywork1
    terraform workspace show
    
    # 서브 디렉터리 확인
    tree terraform.tfstate.d
    terraform.tfstate.d
    └── mywork1
    
    # plan 시 어떤 결과 내용이 출력되나요?
    terraform plan
    
    # apply 해보자!
    terraform apply -auto-approve
    
    
    # 워크스페이스 확인
    terraform workspace list
    
    #
    cat terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
    cat terraform.tfstate.d/mywork1/terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
    
    # graph 확인
    terraform graph > graph.dot
    
    
    # 새 작업 공간 workspace 생성 : mywork2
    terraform workspace new mywork2
    
    # 서브 디렉터리 확인
    tree terraform.tfstate.d
    ...
    
    # plan & apply
    terraform plan && terraform apply -auto-approve
    cat terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
    cat terraform.tfstate.d/mywork1/terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
    cat terraform.tfstate.d/mywork2/terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
    
    # workspace 정보 확인
    terraform workspace show
    terraform workspace list
    
    # 실습 리소스 삭제
    terraform workspace select default
    terraform destroy -auto-approve
    
    terraform workspace select mywork1
    terraform destroy -auto-approve
    
    terraform workspace select mywork2
    terraform destroy -auto-approve

  • 장점
    • 하나의 루트 모듈에서 다른 환경을 위한 리소스를 동일한 테라폼 구성으로 프로비저닝하고 관리
    • 기존 프로비저닝된 환경에 영향을 주지 않고 변경 사항 실험 가능
    • 깃의 브랜치 전략처럼 동일한 구성에서 서로 다른 리소스 결과 관리
  • 단점
    • State가 동일한 저장소(로컬 또는 백엔드)에 저장되어 State 접근 권한 관리가 불가능
    • 모든 환경이 동일한 리소스를 요구하지 않을 수 있으므로 테라폼 구성에 분기 처리가 다수 발생 가능
    • 프로비저닝 대상에 대한 인증 요소를 완벽히 분리하기 어려움⇒ 해결하기 위해 루트 모듈을 별도로 구성하는 디렉터리 기반의 레이아웃을 사용할 수 있다 or Terraform Cloud 환경의 워크스페이스를 활용
    • → 가장 큰 단점은 완벽한 격리가 불가능

(Tip) Terraform Workspace Prompt - dozyio amatellanes

  • terraform workspace prompt를 bash와 zsh를 자동으로 detect해서 설정될 수 있게 약간 변경해봤습니다.
    • terraform-bash-workspace-prompt.sh
      #!/usr/bin/env bash
      
      # dozy.io 2020
      # Download latest version from https://github.com/dozyio/terraform-bash-workspace-prompt
      
      
      RESETCOLOR="$(echo -e "\001\033[0m\002")"
      PURPLETEXT="$(echo -e "\001\033[35m\002")"
      REDBGWHITETEXT="$(echo -e "\001\033[1;41;97m\002")"
      GREENTEXT="$(echo -e "\001\033[1;32m\002")"
      BOLDTEXT="$(echo -e "\001\033[1m\002")"
      
      TFPURPLE="$(echo -e "$PURPLETEXT(TF:$RESETCOLOR")"
      TFPURPLEEND="$(echo -e "$PURPLETEXT)$RESETCOLOR")"
      
      function terraform_workspace_prompt()
      {
          if [ -d .terraform ]; then
              terraform_workspace="$(command terraform workspace show 2>/dev/null)"
      
              if [[ $terraform_workspace == "prod"* ]] || \
                  [[ $terraform_workspace == "PROD"* ]] || \
                  [[ $terraform_workspace == "Prod"* ]]; then
                  echo -e " $TFPURPLE$REDBGWHITETEXT${terraform_workspace}$RESETCOLOR$TFPURPLEEND"
      
              elif [[ $terraform_workspace == "dev"* ]] || \
                  [[ $terraform_workspace == "DEV"* ]] || \
                  [[ $terraform_workspace == "Dev"* ]]; then
                  echo -e " $TFPURPLE$GREENTEXT${terraform_workspace}$RESETCOLOR$TFPURPLEEND"
      
              else
                  echo -e " $TFPURPLE$BOLDTEXT${terraform_workspace}$RESETCOLOR$TFPURPLEEND"
              fi
          fi
      }
      
      # Determine our shell
      if [ "${ZSH_VERSION-}" ]; then
          TF_PS1_SHELL="zsh"
      elif [ "${BASH_VERSION-}" ]; then
          TF_PS1_SHELL="bash"
      fi
       
    • 위 코드를 특정 디렉토리에 생성하시고, .zshrc 또는 .bashrc에 아래 코드를 추가로 삽입하시면 됩니다.
      # terraform-ps1 for terraform workspace
      source /usr/local/bin/terraform-bash-workspace-prompt.sh
      if [[ "$TF_PS1_SHELL" == "zsh" ]]; then
          PS1='$(terraform_workspace_prompt)'$PS1
      elif [[ "$TF_PS1_SHELL" == "bash" ]]; then
          if [[ "$OSTYPE" == "darwin"* ]]; then
              export PS1=${PS1/ \\\u\\\$/\$(terraform_workspace_prompt) \\\u\\\$}
          elif [[ "$OSTYPE" == "linux-gnu" ]]; then
              export PS1=${PS1/\\\$/\$(terraform_workspace_prompt) \\\$}
          fi
      fi
       
    • Bash Shell 출력
    • Zsh Shell 출력