본문 바로가기
DevOps

[T1014-실습] OpenTofu 1.7.0

by 서어켜엉 2024. 8. 2.
해당 내용은 cloudNet@ 팀의 가시다 님이 진행하는 테라폼 스터디 T101 4기에서 학습한 내용을 정리한 것입니다.

 

 

1. Provider-defined functions

  • Function 종류 : Built-in Functions, Provider-defined Functions - Link
    • Open
  • 새로운 Terraform 플러그인 SDK는 OpenTofu에서 직접 사용할 수 있는 공급자 정의 함수에 대한 지원을 추가했다. 공급자 정의 함수는 상태 파일의 크기를 늘리지 않고 작성하는데 필요한 코드가 적기 때문에 데이터 소스를 사용하는 것보다 훨씬 효율적이다.
  • 공급자가 사용자의 구성에 따라 사용자 정의 기능을 동적으로 정의할 수 있도록 OpenTofu 전용 기능을 추가했다. 이 업데이트를 통해 라이브 스트림에서 보여지는 것처럼 다른 프로그래밍 언어를 완전히 통합할 수 있다. 

실습 

  • main.tf 파일 작성
    문자열 Snake case 로 변환
terraform {
  required_providers {
    corefunc = {
      source = "northwood-labs/corefunc"
      version = "1.4.0"
    }
  }
}

provider "corefunc" {
}

# Snake case 로 변환
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 변경 및 실행 확인
    문자열 Camel case로 변환
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*

 

2. Loopable import blocks - Docs

  • Import : Import 블록을 사용하여 기존 인프라 리소스를 OpenTofu로 가져와 OpenTofu의 관리 하에 둔다.
    • Import 블록은 모든 OpenTofu 구성 파일에 추가할 수 있다. 일반적은 패턴은 imports.tf 파일을 만들거나 import 블록을 가져오는 리소스 블록 옆에 배치하는 것이다.
    • Import 블록 인수
      • 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
      • for_each(optional) - Import several resources by iterating over a map or a set.
import {
  to = aws_instance.example
  id = "i-abcd1234"
}

resource "aws_instance" "example" {
  name = "hashi"
  # (other resource arguments...)
}
  • Import multiple resources
    • for_each 표현식을 사용하여 하나의 import 블록으로 여러 리소스를 가져올 수 있다. 
    • 이 표현식은 집합, 튜플 또는 맵을 허용하고 each.key 및 each.value 변수를 제공하여 개별 요소에 액세스한다.

실습

  • 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     3.35.50.179   running
app     43.200.245.83    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",
...

  • 문제 상황 재연 : 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

 

 

3. State file encryption - Local

  • State and plan Encryption
    • OpenTofu는 로컬 스토리지와 백엔드를 사용할 때 모두 휴면상태 및 계획 파일을 암호화 할 수 있다. 또한 terraform_remote_state 데이터 소스와 함께 암호화를 사용할 수도 있다.

실습

  • 위에서 실습한 파일을 복사해서 사용
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

  • 좀 더 강력한 보안 정책 적용 : 암호화 되지 않은 환경 변수가 저장되는 것을 차단
  • enforced = true
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
    }
  }
}
  • Rolling back encryption : 암호화 -> 평문으로 마이그레이션
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
    }
  }
}