Terraform

[ Terraform101 Study - 8w ] OpenTofu

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

 

OpenTofu 소개

  • OpenTofu는 클라우드와 온프레미스 리소스를 모두 사람이 읽을 수 있는 구성 파일에 정의하여 버전 관리, 재사용 및 공유할 수 있는 코드 도구로서의 인프라입니다. 그런 다음 일관된 워크플로를 사용하여 라이프사이클 전체에 걸쳐 모든 인프라를 프로비저닝하고 관리할 수 있습니다.
    • OpenTofu is an infrastructure as code tool that lets you define both cloud and on-prem resources in human-readable configuration files that you can version, reuse, and share. You can then use a consistent workflow to provision and manage all of your infrastructure throughout its lifecycle.
How does OpenTufu work?
  • OpenTofu는 애플리케이션 프로그래밍 인터페이스(API)를 통해 클라우드 플랫폼 및 기타 서비스에서 리소스를 생성하고 관리합니다. 공급자는 OpenTofu가 액세스 가능한 API를 통해 사실상 모든 플랫폼이나 서비스와 함께 작업할 수 있도록 합니다.
    • The OpenTofu community have already written thousands of providers to manage many different types of resources and services. You can find all publicly available providers on the Public OpenTofu Registry, including Amazon Web Services (AWS), Azure, Google Cloud Platform (GCP), Kubernetes, Helm, GitHub, Splunk, DataDog, and many more.
  • 핵심 OpenTofu 워크플로는 세 단계로 구성 - Link
    • Write 쓰기 : 여러 클라우드 공급자와 서비스에 걸쳐 있을 수 있는 리소스를 정의합니다. 예를 들어, 보안 그룹과 로드 밸런서가 있는 Virtual Private Cloud(VPC) 네트워크의 가상 머신에 애플리케이션을 배포하는 구성을 만들 수 있습니다.
    • Pla0n 계획 : OpenTofu는 기존 인프라와 구성에 따라 생성, 업데이트 또는 파괴할 인프라를 설명하는 실행 계획을 만듭니다.
    • Apply 적용 : 승인 시 OpenTofu는 모든 리소스 종속성을 존중하여 올바른 순서로 제안된 작업을 수행합니다. 예를 들어, VPC의 속성을 업데이트하고 해당 VPC의 가상 머신 수를 변경하는 경우 OpenTofu는 가상 머신을 확장하기 전에 VPC를 다시 생성합니다.
FAQ - Link
  • OpenTofu와 Terraform의 차이점은 무엇인가요?
    • 기술적인 측면에서 OpenTofu 1.6.xTerraform 1.6.x와 기능적으로 매우 유사합니다. 앞으로는 프로젝트 기능 세트가 갈라질 것입니다.
    • 또 다른 주요 차이점은 OpenTofu는 오픈 소스라는 점이며, 그 목표는 단일 회사가 로드맵을 지시할 수 없는 협력적인 방식으로 추진되는 것입니다.
  • Terraform의 드롭인 대체품으로 OpenTofu를 사용할 수 있나요? OpenTofu는 프로덕션 사용에 적합할까요?
    • 지금 당장, OpenTofu는 Terraform 버전 1.5.x 및 대부분 1.6.x와 호환되므로 Terraform의 드롭인 대체품입니다. 호환성을 보장하기 위해 코드를 변경할 필요가 없습니다.
    • 자세한 내용은 마이그레이션 가이드를 참조하세요 : Terraform 1.5 이하, 1.6, 1.7, 1.8 버전별 마이그레이션 가이드 제공
  • OpenTofu가 기존 상태 파일과 호환되나요?
    • OpenTofu는 Terraform 버전 1.5.x로 생성된 파일까지 기존 상태 파일을 지원합니다.
  • OpenTofu는 Terraform이 협력하는 모든 공급업체와 호환됩니까?
    • OpenTofu는 자체 공급자가 없습니다. Terraform 공급자는 라이선스를 변경하지 않았으며 그러한 변경 가능성은 사실상 0입니다.
    • OpenTofu는 현재 Terraform 공급자와 함께 작동하지만 별도의 레지스트리를 사용합니다.

 

 

 

 

OpenTofu 1.7.0

[실습 시나리오 참고] : https://www.youtube.com/watch?v=CQkUszVlemE&list=PLcTwvG0VZZTcrILMJDvLOMrrMcWlXFUla
[실습] Provider-defined functions-Docs
 

OpenTofu and Terraform registry

 

library.tf

실습 따라하기
  • 실습을 위해서 8.1 디렉터리를 신규 생성 후 열기 → main.tf 파일 생성
    mkdir 8.1 && cd 8.1
    touch main.tf
  • main.tf 파일 작성
    terraform {
      required_providers {
        corefunc = {
          source = "northwood-labs/corefunc"
          version = "1.4.0"
        }
      }
    }
    
    provider "corefunc" {
    }
    
    output "test" {
      value = provider::corefunc::str_snake("Hello world!")
      # Prints: hello_world
    }
  • 실행 후 확인
    # 초기화
    tofu init
    
    # 프로바이더 정보 확인
    tree .terraform
    .terraform
    └── providers
        └── registry.opentofu.org
            └── northwood-labs
                └── corefunc
    
    # Plan
    tofu plan
    ...
    Changes to Outputs:
      + test = "hello_world"
    
    # Apply
    tofu apply
    ...
     Enter a value: yes
    ...
    Outputs:
    test = "hello_world"
    
    # output
    tofu output
    
    # tfstate 파일 확인 : VSCODE에서 열어보기
    cat terraform.tfstate | jq
  • main.tf 파일 수정 : Converts a string to camelCase, removing any non-alphanumeric characters.
    terraform {
      required_providers {
        corefunc = {
          source = "northwood-labs/corefunc"
          version = "1.4.0"
        }
      }
    }
    
    provider "corefunc" {
    }
    
    output "test" {
      value = provider::corefunc::str_camel("Hello world!")
      # Prints: hello_world
    }
  • 실행 후 확인
    # output
    tofu output
    
    # Apply
    tofu apply -auto-approve
    ...
    Outputs:
    test = "helloWorld"
    
    # tfstate 파일 확인 : VSCODE에서 열어보기
    ls -l terraform.tfstate*

 

[실습] Loopable import blocks-Docs
  • Import : Use the import block to import existing infrastructure resources into OpenTofu, bringing them under OpenTofu's management.  - Link
    • You can add an import block to any OpenTofu configuration file. A common pattern is to create an imports.tf file, or to place each import block beside the resource block it imports into.
 

Import | OpenTofu

Import and manage existing resources with OpenTofu using configuration-driven import.

opentofu.org

  • The import block has the following arguments:
    • to - The instance address this resource will have in your state file.
    • id - A string with the import ID of the resource.
    • provider (optional) - An optional custom resource provider, see The Resource provider Meta-Argument for details.
    • for_each (optional) - Import several resources by iterating over a map or a set. See Importing multiple resources below.
      import {
        to = aws_instance.example
        id = "i-abcd1234"
      }
      
      resource "aws_instance" "example" {
        name = "hashi"
        # (other resource arguments...)
      }
  • Importing multiple resources\
    • You can import multiple resources with one import block by using a for_each expression.
    • This expression accepts a set, a tuple or a map and provides the each.key and each.value variables to access the individual elements.
실습 따라하기
  • 실습을 위해서 8.2 디렉터리를 신규 생성 후 열기 → main.tf 파일 생성
  • f 파일 작성
  • 실행 후 확인
  • 문제 상황 재연 : tfstate 파일 삭제
    # 문제 상황 재연 : tfstate 파일 삭제
    rm -rf .terraform* terraform.tfstate*
    tree
    
    # EC2 확인 : ID 메모
    aws ec2 describe-instances --query 'Reservations[*].Instances[*].{InstanceID:InstanceId,PublicIP:PublicIpAddress,Name:Tags[?Key==`Name`]|[0].Value}' --output json | jq -r '.[][] | "\(.InstanceID)\t\(.PublicIP)\t\(.Name)"'
    i-0e2d4475790337a81     13.125.183.90   web
    i-00a4daebb71942280     3.38.152.103    app

  • main.tf 파일 수정 : 아래 ami-id 를 바로 위 ‘EC2 확인’ 에서 출력된 ID 를 입력
    data "aws_ami" "ubuntu" {
        most_recent = true
    
        filter {
            name   = "name"
            values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
        }
    
        filter {
            name   = "virtualization-type"
            values = ["hvm"]
        }
    
        owners = ["099720109477"] # Canonical
    }
    
    variable "instance_ids" {
      type = list(string)
      default = ["i-0e2d4475790337a81", "i-00a4daebb71942280"]
    }
    
    variable "instance_tags" {
      type = list(string)
      default = ["web", "app"]
    }
    
    resource "aws_instance" "this" {
      count = length(var.instance_tags)
      ami                    = data.aws_ami.ubuntu.id
      instance_type          = "t3.micro"
    
      tags = {
        Name = var.instance_tags[count.index]
      }
    }
    
    import {
      for_each = { for idx, item in var.instance_ids : idx => item }
      to = aws_instance.this[tonumber(each.key)]
      id = each.value
    }
  • 실행 후 확인
    # 초기화
    tofu init -json
    tree .terraform
    
    #  
    tofu apply -auto-approve
    Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.
    aws_instance.this[1]: Importing... [id=i-00a4daebb71942280]
    aws_instance.this[1]: Import complete [id=i-00a4daebb71942280]
    aws_instance.this[0]: Importing... [id=i-0e2d4475790337a81]
    aws_instance.this[0]: Import complete [id=i-0e2d4475790337a81]
    
    # 확인
    tofu state ls
    tofu show
    
    # tfstate 파일 확인 : VSCODE에서 열어보기
    cat terraform.tfstate | jq


[실습] State file encryption - Local-Docs
  • State and Plan Encryption - Link
  • OpenTofu는 로컬 스토리지와 백엔드를 사용할 때 모두 휴면 상태 및 계획 파일을 암호화하는 것을 지원합니다. 또한 terraform_remote_state데이터 소스와 함께 암호화를 사용할 수도 있습니다. 
    • OpenTofu supports encrypting state and plan files at rest, both for local storage and when using a backend. In addition, you can also use encryption with the terraform_remote_state data source.
  • 기본 코드 예시
    terraform {
      encryption {
        key_provider "some_key_provider" "some_name" {
          # Key provider options here
        }
    
        method "some_method" "some_method_name" {
          # Method options here
          keys = key_provider.some_key_provider.some_name
        }
    
        state {
          # Encryption/decryption for state data
          method = method.some_method.some_method_name
        }
    
        plan {
          # Encryption/decryption for plan data
          method = method.some_method.some_method_name
        }
    
        remote_state_data_sources {
          # See below
        }
      }
    }

  • key and method rollover : 신규 암호화 설정 변경 설정 후 실행 시 문제 발생 시, 자동으로 이전 암호화 설정으로 전환 적용 fallback
    terraform {
      encryption {
        # Methods and key providers here.
    
        state {
          method = method.some_method.new_method
          fallback {
            method = method.some_method.old_method
          }
        }
    
        plan {
          method = method.some_method.new_method
          fallback {
            method = method.some_method.old_method
          }
        }
      }
    }

  • [초기 설정] 신규 프로젝트
    • If you are setting up a new project and do not yet have a state file, this sample configuration will get you started with passphrase-based encryption:
      terraform {
        encryption {
          ## Step 1: Add the desired key provider:
          key_provider "pbkdf2" "mykey" {
            # Change this to be at least 16 characters long:
            passphrase = "changeme!"
          }
          ## Step 2: Set up your encryption method:
          method "aes_gcm" "new_method" {
            keys = key_provider.pbkdf2.mykey
          }
      
          state {
            ## Step 3: Link the desired encryption method:
            method = method.aes_gcm.new_method
      
            ## Step 4: Run "tofu apply".
      
            ## Step 5: Consider adding the "enforced" option:
            # enforced = true
          }
      
          ## Step 6: Repeat steps 3-5 for plan{} if needed.
        }
      }

  • [초기 설정] 기존 프로젝트
    • 기존 프로젝트에서 암호화를 처음 구성할 때 상태 및 계획 파일은 암호화되지 않습니다. OpenTofu는 기본적으로 조작되었을 수 있기 때문에 이를 읽는 것을 거부합니다. 암호화되지 않은 데이터 읽기를 활성화하려면 다음 unencrypted방법을 지정해야 합니다.
      • When you first configure encryption on an existing project, your state and plan files are unencrypted. OpenTofu, by default, refuses to read them because they could have been manipulated. To enable reading unencrypted data, you have to specify an unencrypted method:
        terraform {
          encryption {
            ## Step 1: Add the unencrypted method:
            method "unencrypted" "migrate" {}
        
            ## Step 2: Add the desired key provider:
            key_provider "pbkdf2" "mykey" {
              # Change this to be at least 16 characters long:
              passphrase = "changeme!"
            }
        
            ## Step 3: Add the desired encryption method:
            method "aes_gcm" "new_method" {
              keys = key_provider.pbkdf2.mykey
            }
        
            state {
              ## Step 4: Link the desired encryption method:
              method = method.aes_gcm.new_method
        
              ## Step 5: Add the "fallback" block referencing the
              ## "unencrypted" method.
              fallback {
                method = method.unencrypted.migrate
              }
        
              ## Step 6: Run "tofu apply".
        
              ## Step 7: Remove the "fallback" block above and
              ## consider adding the "enforced" option:
              # enforced = true
            }
        
            ## Step 8: Repeat steps 4-8 for plan{} if needed.
          }
        }
  • Rolling back encryption : 암호화 → 평문으로 마이그레이션
    • unencrypted를 사용하여 암호화되지 않은 상태 및 계획 파일로 마이그레이션
      terraform {
        encryption {
          ## Step 1: Leave the original encryption method unchanged:
          method "some_method" "old_method" {
            ## Parameters for the old method here.
          }
      
          # Step 2: Add the unencrypted method here:
          method "unencrypted" "migrate" {}
      
          state {
            ## Step 3: Disable or remove the "enforced" option:
            enforced = false
      
            ## Step 4: Move the original encryption method into the "fallback" block:
            fallback {
              method = method.some_method.old_method
            }
      
            ## Step 5: Reference the unencrypted method as your primary "encryption" method.
            method = method.unencrypted.migrate
          }
      
          ## Step 6: Run "tofu apply".
      
          ## Step 7: Remove the "state" block once the migration is complete.
      
          ## Step 8: Repeat steps 3-7 for plan{} if needed.
        }
      }
  • Key providers : PBKDS2, AWS KMS, GCP KMS, OpenBao(experimental)
  • PBKDS2 : PBKDF2 키 제공자는 AES-GCM과 같은 암호화 방법에 대한 키를 생성하기 위해 긴 패스프레이즈를 사용 가능
    • The PBKDF2 key provider allows you to use a long passphrase as to generate a key for an encryption method such as AES-GCM.
      terraform {
        encryption {
          key_provider "pbkdf2" "foo" {
            # Specify a long / complex passphrase (min. 16 characters)
            passphrase = "correct-horse-battery-staple"
      
            # Adjust the key length to the encryption method (default: 32)
            key_length = 32
      
            # Specify the number of iterations (min. 200.000, default: 600.000)
      	    ## The work factor for PBKDF2 is implemented through an iteration count, which should set differently based on the internal hashing algorithm used.
      			## PBKDF2-HMAC-SHA1: 1,300,000 iterations
      			## PBKDF2-HMAC-SHA256: 600,000 iterations
      			## PBKDF2-HMAC-SHA512: 210,000 iterations
            iterations = 600000
      
            # Specify the salt length in bytes (default: 32)
            salt_length = 32
      
            # Specify the hash function (sha256 or sha512, default: sha512)
            hash_function = "sha512"
          }
        }
      }
       

      Option  Description Min Default
      passphrase (required) Enter a long and complex passphrase. 16 chars. -
      key_length Number of bytes to generate as a key. 1 32
      iterations Number of iterations. See https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 for recommendations. 200.000 600.000
      salt_length Length of the salt for the key derivation. 1 32
      hash_function Specify either sha256 or sha512 to use as a hash function. sha1 is not supported. N/A sha512
  • AWS : AWS 키 제공자는 AWS KMS로 키 생성
    terraform {
      encryption {
        key_provider "aws_kms" "basic" {
          kms_key_id = "a4f791e1-0d46-4c8e-b489-917e0bec05ef"
          region = "us-east-1"
          key_spec = "AES_256"
        }
      }
    }

    Option Description Min  Default
    kms_key_id https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id. 1 -
    key_spec https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-spec. Adapt this to your encryption method (e.g. AES_256). 1 -

 

 

 

실습 따라하기
  • 실습을 위해서 8.3 디렉터리를 신규 생성 후 열기 → 8.2에 main.tf 파일 복사
    mkdir 8.3 && cd 8.3
    cp ../8.2/main.tf .
    touch backend.tf
  • backend.tf 파일 작성
    terraform {
      encryption {
        key_provider "pbkdf2" "my_passphrase" {
          ## Enter a passphrase here:
          passphrase = "ChangeIt_123abcd"
        }
    
        method "aes_gcm" "my_method" {
          keys = key_provider.pbkdf2.my_passphrase
        }
    
        ## Remove this after the migration:
        method "unencrypted" "migration" {
        }
    
        state {
          method = method.aes_gcm.my_method
    
          ## Remove the fallback block after migration:
          fallback{
            method = method.unencrypted.migration
          }
          ## Enable this after migration:
          #enforced = true
        }
      }
    }
  • 실행 후 tfstate 파일 암호화 확인
    #
    tofu init -json | jq
    tree .terraform
    
    # import 실행
    tofu apply -auto-approve
    tofu state list
    tofu show
    
    # tfstate 파일 확인 : VSCODE에서 열어보기
    cat terraform.tfstate | jq
  • 좀 더 강력한 보안 정책 적용 : 암호화 되지 않은 환경 변수가 저장 되는 것을 차단
    • code configuration to prevent unencrypted data from being written in the absence of an environment variable
      terraform {
          encryption {
              state {
                  enforced = true
              }
              plan {
                  enforced = true
              }
          }
      }
  • backend.tf 파일 수정 : 아래 주석 제거
    terraform {
      encryption {
        key_provider "pbkdf2" "my_passphrase" {
          ## Enter a passphrase here:
          passphrase = "ChangeIt_123abcd"
        }
    
        method "aes_gcm" "my_method" {
          keys = key_provider.pbkdf2.my_passphrase
        }
    
        ## Remove this after the migration:
        method "unencrypted" "migration" {
        }
    
        state {
          method = method.aes_gcm.my_method
    
          ## Remove the fallback block after migration:
          fallback{
            method = method.unencrypted.migration
          }
          # Enable this after migration:
          enforced = true
        }
      }
    }
  • 실행 후 확인
    #
    tofu apply -auto-approve
    
    # tfstate 파일 확인 : VSCODE에서 열어보기
    cat terraform.tfstate | jq
  • Rolling back encryption : 암호화 → 평문으로 마이그레이션
  • backend.tf 파일 수정
    terraform {
      encryption {
        key_provider "pbkdf2" "my_passphrase" {
          ## Enter a passphrase here:
          passphrase = "ChangeIt_123abcd"
        }
    
        method "aes_gcm" "my_method" {
          keys = key_provider.pbkdf2.my_passphrase
        }
    
        ## Remove this after the migration:
        method "unencrypted" "migration" {
        }
    
        state {
          method = method.unencrypted.migration
    
          ## Remove the fallback block after migration:
          fallback{
            method = method.aes_gcm.my_method
          }
          # Enable this after migration:
          enforced = false
        }
      }
    }
  • 실행 후 확인
    #
    tofu apply -auto-approve
    
    # tfstate 파일 확인 : VSCODE에서 열어보기
    cat terraform.tfstate | jq

 

[실습] State file encryption - AWS KMS
  • [사전 준비] AWS S3 버킷 생성 : Backend State 저장용도
  • ]AWS KMS 소개
    • 암호화는 키를 사용해 평문을 암호문으로 변환하는 프로세스다
    • 동일한 키를 사용해 암호문을 평문으로 변환할 수 있는데, 이를 복호화라고 한다
    • AWS 키 관리 서비스 KMS는 공유 하드웨어 보안 모듈HSM 을 사용하면서 암호화키생성하고 관리할 수 있게 도와준다
    • CloudHSM은 AWS 내에서 암호화키를 관리할 수 있지만 보안 강화를 위해 전용 HSM을 사용할 수 있는 서비스다
    • 용어 변경 참고 : 기존 Customer Master Key (CMK) → AWS KMS key 혹은 KMS key 로 변경 - 링크
 

Document history - AWS Key Management Service

Thanks for letting us know this page needs work. We're sorry we let you down. If you've got a moment, please tell us how we can make the documentation better.

docs.aws.amazon.com

  • [사전 준비] AWS KMS 생성 및 실습 : 대칭 키 생성 후 평문 파일을 암호화 및 복호화
    # 키 생성(기본값)
    # aws kms create-key --description "my text encrypt descript demo"
    CREATE_KEY_JSON=$(aws kms create-key --description "my text encrypt descript demo")
    echo $CREATE_KEY_JSON | jq
    
    # 키 ID확인
    KEY_ID=$(echo $CREATE_KEY_JSON | jq -r ".KeyMetadata.KeyId")
    echo $KEY_ID
    
    # 키 alias 생성
    export ALIAS_SUFFIX=<각자 닉네임>
    export ALIAS_SUFFIX=gasida
    aws kms create-alias --alias-name alias/$ALIAS_SUFFIX --target-key-id $KEY_ID
    
    # 생성한 별칭 확인 : 키 ID 메모하두기, 아래 테라폼 코드에서 사용
    aws kms list-aliases
    aws kms list-aliases --query "Aliases[?AliasName=='alias/<각자 닉네임>'].TargetKeyId" --output text
    aws kms list-aliases --query "Aliases[?AliasName=='alias/gasida'].TargetKeyId" --output text
    c0bfc529-1ede-44f3-a7e5-ae60814c1ca1
    
    # CMK로 평문을 암호화해보기
    echo "Hello 123123" > secrect.txt
    aws kms encrypt --key-id alias/$ALIAS_SUFFIX --cli-binary-format raw-in-base64-out --plaintext file://secrect.txt --output text --query CiphertextBlob | base64 --decode > secrect.txt.encrypted
    
    # 암호문 확인
    cat secrect.txt.encrypted
    
    # 복호화해보기
    aws kms decrypt --ciphertext-blob fileb://secrect.txt.encrypted --output text --query Plaintext | base64 --decode
    Hello 123123
     
  • 8.3 디렉터리에 backend.tf 파일 수정
    terraform {
      backend "s3" {
        bucket = "gasida-t101" # 각자 자신의 S3 버킷명
        key = "terraform.tfstate"
        region = "ap-northeast-2"
        encrypt = true
      }
    
      encryption {
        key_provider "aws_kms" "kms" {
          kms_key_id = "c0bfc529-1ede-44f3-a7e5-ae60814c1ca1" # 각자 자신의 KMS ID
          region = "ap-northeast-2"
          key_spec = "AES_256"
        }
    
        method "aes_gcm" "my_method" {
          keys = key_provider.aws_kms.kms
        }
    
        ## Remove this after the migration:
        method "unencrypted" "migration" {
        }
    
        state {
          method = method.aes_gcm.my_method
    
          ## Remove the fallback block after migration:
          fallback{
            method = method.unencrypted.migration
          }
          # Enable this after migration:
          #enforced = false
        }
      }
    }
  • 실행 및 확인
    # 로컬 tfstate 파일 제거
    rm -rf .terraform* terraform.tfstate*
    tree
    
    #
    tofu init
    Initializing the backend...
    
    Successfully configured the backend "s3"! OpenTofu will automatically
    use this backend unless the backend configuration changes.
    ...
    
    #
    tree .terraform 
    .terraform
    ├── providers
    │   └── registry.opentofu.org
    │       └── hashicorp
    │           └── aws
    │               └── 5.60.0
    │                   └── darwin_arm64 -> /Users/gasida/.terraform.d/plugin-cache/registry.opentofu.org/hashicorp/aws/5.60.0/darwin_arm64
    └── terraform.tfstate
    
    # 
    cat .terraform/terraform.tfstate | jq
    
    # import 실행
    tofu apply -auto-approve
    tofu state list
    tofu show
    ls -l terraform.tfstate*
    
    # 원격 백엔드에 저장된 tfstate 파일 확인 및 로컬에 다운로드
    aws s3 ls s3://<각자 자신의 S3 버킷명> --recursive
    aws s3 cp s3://<각자 자신의 S3 버킷명>/terraform.tfstate .
    
    aws s3 ls s3://gasida-t101 --recursive
    aws s3 cp s3://gasida-t101/terraform.tfstate .
    
    # 다운받은 tfstate 파일 확인 : VSCODE에서 열어보기
    cat terraform.tfstate | jq


[실습] Removed block-Docs

 

  • The removed block lets you remove a resource from the state file but keep it on the infrastructure - Link
    1. You can test it by creating a resource first:
      resource "local_file" "test" {
        content = "Hello world!"
        filename = "test.txt"
      }
    2. After applying, you can replace the resource with a removed block:
      removed {
        from = local_file.test
      }
    3. After the next apply, you will see that the local_file.test resource no longer exists in your state file, but the test.txt file should still exist on your disk. You can now remove the removed block safely.
실습 따라하기
  • 실습을 위해서 8.4 디렉터리를 신규 생성 후 열기 → 8.2에 main.tf 파일 복사
    mkdir 8.4 && cd 8.4
    cp ../8.3/main.tf .
    touch backend.tf
  • main.tf 파일 작성
    data "aws_ami" "ubuntu" {
        most_recent = true
    
        filter {
            name   = "name"
            values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
        }
    
        filter {
            name   = "virtualization-type"
            values = ["hvm"]
        }
    
        owners = ["099720109477"] # Canonical
    }
    
    variable "instance_ids" {
      type = list(string)
      default = ["i-0e2d4475790337a81", "i-00a4daebb71942280"] # 각자 자신의 EC2 ID를 기입 할 것
    }
    
    variable "instance_tags" {
      type = list(string)
      default = ["web", "app"]
    }
    
    resource "aws_instance" "this" {
      count = length(var.instance_tags)
      ami                    = data.aws_ami.ubuntu.id
      instance_type          = "t3.micro"
    
      tags = {
        Name = var.instance_tags[count.index]
      }
    }
    
    import {
      for_each = { for idx, item in var.instance_ids : idx => item }
      to = aws_instance.this[tonumber(each.key)]
      id = each.value
    }
    
    resource "aws_ssm_parameter" "this" {
      count = length(var.instance_tags)
      name  = var.instance_tags[count.index]
      type  = "String"
      value = aws_instance.this[count.index].id
    }
  • 실행 및 확인
    #
    tofu init
    tree .terraform
    
    # 2개 리소스는 import , 2개 리소스는 생성
    tofu apply -auto-approve
    Plan: 2 to import, 2 to add, 0 to change, 0 to destroy.
    aws_instance.this[0]: Importing... [id=i-0e2d4475790337a81]
    aws_instance.this[0]: Import complete [id=i-0e2d4475790337a81]
    aws_instance.this[1]: Importing... [id=i-00a4daebb71942280]
    aws_instance.this[1]: Import complete [id=i-00a4daebb71942280]
    aws_ssm_parameter.this[1]: Creating...
    aws_ssm_parameter.this[0]: Creating...
    aws_ssm_parameter.this[1]: Creation complete after 0s [id=app]
    aws_ssm_parameter.this[0]: Creation complete after 0s [id=web]
    
    #
    tofu state ls
    tofu show
    tofu state show 'aws_ssm_parameter.this[0]'
    tofu state show 'aws_ssm_parameter.this[1]'
    
    # tfstate 파일 확인 : VSCODE에서 열어보기
    cat terraform.tfstate | jq
    ...
          "type": "aws_ssm_parameter",
          "name": "this",
          "provider": "provider[\"registry.opentofu.org/hashicorp/aws\"]",
          "instances": [
            {
              "index_key": 0,
              "schema_version": 0,
              "attributes": {
                "allowed_pattern": "",
                "arn": "arn:aws:ssm:ap-northeast-2:911283464785:parameter/web",
                "data_type": "text",
                "description": "",
                "id": "web",
                "insecure_value": null,
                "key_id": "",
                "name": "web",
                "overwrite": null,
                "tags": null,
                "tags_all": {},
                "tier": "Standard",
                "type": "String",
                "value": "i-0e2d4475790337a81",
    ...
    
    # parameters 정보 확인
    aws ssm describe-parameters | jq
    aws ssm get-parameter --name "web"
    aws ssm get-parameter --name "web" --query "Parameter.Value" --output text
    aws ssm get-parameter --name "app"
    aws ssm get-parameter --name "app" --query "Parameter.Value" --output text
     
  • 이번 실습 목적 : 파라미터 스토어 리소스만 tfstate 에서 제거하고, AWS 상에는 유지 하게 설정
  • main.tf 파일 수정
    data "aws_ami" "ubuntu" {
        most_recent = true
    
        filter {
            name   = "name"
            values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
        }
    
        filter {
            name   = "virtualization-type"
            values = ["hvm"]
        }
    
        owners = ["099720109477"] # Canonical
    }
    
    variable "instance_ids" {
      type = list(string)
      default = ["i-0e2d4475790337a81", "i-00a4daebb71942280"]
    }
    
    variable "instance_tags" {
      type = list(string)
      default = ["web", "app"]
    }
    
    resource "aws_instance" "this" {
      count = length(var.instance_tags)
      ami                    = data.aws_ami.ubuntu.id
      instance_type          = "t3.micro"
    
      tags = {
        Name = var.instance_tags[count.index]
      }
    }
    
    import {
      for_each = { for idx, item in var.instance_ids : idx => item }
      to = aws_instance.this[tonumber(each.key)]
      id = each.value
    }
    
    # resource "aws_ssm_parameter" "this" {
    #   count = length(var.instance_tags)
    #   name  = var.instance_tags[count.index]
    #   type  = "String"
    #   value = aws_instance.this[count.index].id
    # }
    
    removed {
      from = aws_ssm_parameter.this
    }
  • 실행 및 확인
    #
    tofu apply -auto-approve
    ...
      # aws_ssm_parameter.this[0] will be removed from the OpenTofu state but will not be destroyed
      # aws_ssm_parameter.this[1] will be removed from the OpenTofu state but will not be destroyed
    ...
    
    # parameters 정보 확인
    aws ssm describe-parameters | jq
    
    #
    tofu state ls
    
    # tfstate 파일 확인 : VSCODE에서 열어보기
    cat terraform.tfstate | jq
    ...

 

[실습] Test-Link

 

  • The tofu test command lets you test your OpenTofu configuration by creating real infrastructure and checking that the required conditions (assertions) are met. Once the test is complete, OpenTofu destroys the resources it created.
  • Usage : tofu test [options]
    • This command will execute all *.tftest.hcl files in the current directory or in a directory called tests.
    • You can customize this behavior using the options below.
      • Consider the following simple example which creates a test.txt file from main.tf and then checks that the main code has successfully performed its job from main.tftest.hcl.
      • Tofu now reads the .**tfvars** file from the tests folder.
    • main.tf
      resource "local_file" "test" {
        filename = "${path.module}/test.txt"
        content  = "Hello world!"
      }
    • main.tftest.hcl
      run "test" {
        assert {
          condition     = file(local_file.test.filename) == "Hello world!"
          error_message = "Incorrect content in ${local_file.test.filename}."
        }
      }
       
실습 따라하기
  • 실습을 위해서 8.5 디렉터리를 신규 생성
    mkdir 8.5 && cd 8.5
    touch main.tf
    mkdir tests
    touch tests/main.tftest.hcl
    touch tests/terraform.tfvars
  • main.tf 파일 작성
    variable "test" {
      type = string
    }
    
    resource "local_file" "this" {
      filename = "${path.module}/test.txt"
      content  = var.test
    }
  • tests/main.tftest.hcl 파일 작성
    run "test" {
      assert {
        condition     = file(local_file.this.filename) == var.test
        error_message = "Incorrect content in file"
      }
    }
  • 실행 후 확인
    #
    tofu init
    tree .terraform
    
    # Test 실행
    tofu test 
    
    # Test 실행
    tofu test -var 'test=t101'
  • tests/terraform.tfvars 파일 작성
    test = "study-end"
  • 실행 후 확인
    # Test 실행
    tofu test 
    
    # Apply 확인
    tofu apply -auto-approve
    tofu state list
    cat test.txt
  • CLI changes
    • tofu init now supports the json flag for JSON output.
    • tofu plan now has a concise flag to shorten the plan output.
    • tofu console now works on Solaris and AIX.
    • The CLI now supports the XDG directory specification.
    • Aliases for:
      • state list → state ls
      • state mv → state move
      • state rm → state remove

 

실습 리소스 삭제
  • AWS EC2 삭제 : 8.2 디렉터리 이동 후 삭제
    # 8.4
    cd 8.2
    
    # 리소스 삭제
    tofu apply -destroy -auto-approve
    
    # running 상태 EC2만 출력 : EC2 삭제 확인
    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
  • SSM 파라미터 삭제
    # 확인
    aws ssm describe-parameters
    
    # 삭제
    aws ssm delete-parameters --names "web" "app"
    
    # 확인
    aws ssm describe-parameters
  • S3 버킷 삭제
    #
    aws s3 rm s3://<각자 자신의 S3 버킷명> --recursive
    aws s3 rb s3://<각자 자신의 S3 버킷명>
    
    aws s3 rm s3://gasida-t101 --recursive
    aws s3 rb s3://gasida-t101
    
    #
    aws s3 ls
  • KMS 키 비활성화 및 삭제 예약
    # 생성한 키 ID 변수 지정
    KEY_ID=$(echo $CREATE_KEY_JSON | jq -r ".KeyMetadata.KeyId")
    
    # 키 비활성화
    aws kms disable-key --key-id $KEY_ID
    
    # 키 삭제 예약 : 대기 기간(7일) 
    aws kms schedule-key-deletion --key-id $KEY_ID --pending-window-in-days 1 # 최소 7일 이상 필요
    aws kms schedule-key-deletion --key-id $KEY_ID --pending-window-in-days 7