본문 바로가기

study/T101 4기

T101 4기 8주차 첫번째

드디어 마지막 8주차입니다.

 

OpenTofu

Install OpenTofu - Link

 

Installing OpenTofu | OpenTofu

Learn how to install OpenTofu.

opentofu.org

 

Tenv 설치 및 확인

# (옵션) tfenv 제거
brew remove tfenv

# Tenv 설치
## brew install cosign
brew install tenv
tenv -v
tenv -h
tenv tofu -h
which tenv

# (옵션) Install shell completion
tenv completion zsh > ~/.tenv.completion.zsh
echo "source '~/.tenv.completion.zsh'" >> ~/.zshrc

 

Tofu 설치 및 확인

#
tenv tofu -h
tenv tofu list
tenv tofu list-remote

# 설치
tenv tofu install 1.7.3
tenv tofu list
tenv tofu use 1.7.3
tenv tofu detect

# tofu 확인
tofu -h
tofu version

 

 

OpenTofu 1.7.0

실습 시나리오 참고 영상

 

 

실습 따라하기

실습을 위해서 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

 

What's new in OpenTofu 1.8? | OpenTofu

Learn all about the new features in OpenTofu 1.8.

opentofu.org

 

Import : Use the import block to import existing infrastructure resources into OpenTofu, bringing them under OpenTofu's management.  - Link

 

Import | OpenTofu

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

opentofu.org

- 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 {
  to = aws_instance.example
  id = "i-abcd1234"
}

resource "aws_instance" "example" {
  name = "hashi"
  # (other resource arguments...)
}

 

 

실습 따라하기

실습을 위해서 8.2 디렉터리를 신규 생성 후 열기 → main.tf 파일 생성

mkdir 8.2 && cd 8.2
touch main.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_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]
  }
}

 

실행 후 확인

# 초기화
tofu init

# 프로바이더 정보 확인
tree .terraform
.terraform
└── providers
    └── registry.opentofu.org
        └── hashicorp
            └── aws
                └── 5.60.0
                
# Apply
tofu apply -auto-approve

# EC2 확인
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
web     13.125.183.90   running
app     3.38.152.103    running

# 확인
tofu state list
tofu state ls
echo "data.aws_ami.ubuntu" | tofu console
tofu show

# tfstate 파일 확인 : VSCODE에서 열어보기
cat terraform.tfstate | jq
...
      "provider": "provider[\"registry.opentofu.org/hashicorp/aws\"]",
...
            "arn": "arn:aws:ec2:ap-northeast-2::image/ami-01e69ea1a3e0010f9",
...

 

문제 상황 재연

# 문제 상황 재연
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-00b94831b0a558fcd     13.209.10.245   app
i-0b8d0ebe68337af21     43.201.102.70   web

 

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
}

 

실행 후 확인

# 초기화
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

 

What's new in OpenTofu 1.8? | OpenTofu

Learn all about the new features in OpenTofu 1.8.

opentofu.org

 

 

 

 

 

 

실습 따라하기

실습을 위해서 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

 

 

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

 

 

 

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
    }
  }
}

 

 

 

 

State file encryption - AWS KMS

[사전 준비] AWS S3 버킷 생성 : Backend State 저장용도

#
aws s3 mb s3://<각자 유일한 S3 버킷명 이름> --region ap-northeast-2
aws s3 mb s3://gasida-t101 --region ap-northeast-2

# 확인
aws s3 ls

 

 

[사전 준비] 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

tree .terraform 

cat .terraform/terraform.tfstate | jq

# import 실행
tofu apply -auto-approve
tofu state list
tofu show
ls -l terraform.tfstate*

# 원격 백엔드에 저장된 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

 

What's new in OpenTofu 1.8? | OpenTofu

Learn all about the new features in OpenTofu 1.8.

opentofu.org

 

실습 따라하기

실습을 위해서 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

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

# 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

 

Command: test | OpenTofu

The tofu test command performs integration tests of OpenTofu modules.

opentofu.org

 

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 = "t101-study-end"

 

실행 후 확인

# Test 실행
tofu test 

# Apply 확인
tofu apply -auto-approve
tofu state list
cat test.txt

 

실습 리소스 삭제

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://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

 

 

 

Migrating to OpenTofu 1.7.x from Terraform - Docs

 

Migrating to OpenTofu 1.7.x from Terraform | OpenTofu

Learn how to migrate to OpenTofu from Terraform.

opentofu.org

 

실습을 위해서 8.6 디렉터리를 신규 생성 후 열기 → main.tf 파일 생성

mkdir 8.6 && cd 8.6
touch main.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_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]
  }
}

 

 

실행 후 확인

# 초기화
terraform init

# 프로바이더 정보 확인
tree .terraform
.terraform
└── providers
    └── registry.terraform.io
        └── hashicorp
            └── aws
                └── 5.60.0
                
# Apply
terraform apply -auto-approve

# [신규 터미널] EC2 확인
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
web     13.125.183.90   running
app     3.38.152.103    running

# 확인
terraform state list

# tfstate 파일 확인 : VSCODE에서 열어보기
cat terraform.tfstate | jq
...
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
...

'study > T101 4기' 카테고리의 다른 글

T101 4기 7주차 두번째  (0) 2024.07.27
T101 4기 7주차 첫번째  (0) 2024.07.21
T101 4기 5주차 두번째  (0) 2024.07.07
T101 4기 5주차 첫번째  (0) 2024.07.07
T101 4기 4주차 두번째  (0) 2024.07.06