OpenTofu란?
HashiCorp가 Terraform 라이선스를 Mozilla Public License (MPL) → 비오픈 소스 라이선스인 Business Source License (BUSL)로 변경되면서 테라폼의 포크 버전으로 이를 대응하기 위해 개발하고 있는 CNCF 프로젝트입니다.
HashiCorp가 제공하는 Provider는 여전히 MPL-2.0 License를 사용하고 있습니다.
OpenTofu 설치
Tenv 설치
tenv는 tfenv(테라폼 버전 관리)의 후속버전으로 OpenTofu, Terraform, Terragrunt and Atmos 등의 도구들의 버전 관리를 도와주는 오픈소스입니다. tenv 설치를 위해서는 기존 tfenv를 제거해주어야 합니다.
다음은 tenv에서 사용하는 도구들의 약자(alias)입니다.
아래 실습은 MacOS 기준이며, 공식문서를 참고하여 각자 OS에 맞게 설치합니다.
# (옵션) 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 1.7.3 버전을 설치하고 확인합니다.
#
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
테라폼에는 없지만 있는 기능들 강조.
Provider-defined functions
기존 테라폼에서 지원하는 build-in 함수 외에도 OpenTofu에서 직접 사용할 수 있는 공급자 정의 함수를 지원합니다. 코드의 수와 상태파일의 크기를 줄이고, 데이터 소스를 사용하는 것보다 성능이 개선됩니다.
다음과 같은 구성으로 동작합니다.
provider::<provider_name>::<function_name>
provider::<provider_name>::<provider_alias>::<function_name>
Provider-defined functions 실습
corefunc는 테라폼 주요 함수(문자열, 환경변수 등)를 지원하는 공급자 정의 함수를 실습해 봅니다.
main.tf 파일 작성
str_snake 함수는 문자열을 snake case으로 변환합니다.
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 파일 수정
이번에는 문자열을 camelCase로 변환해 주는 caeml 함수로 수정합니다.
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
terraform의 import 기능은 명령어를 통해서 tfstate 파일의 리소스를 가져오는 기능이다. opentofu는 import.tf파일이나 import 블록을 통해서 여러 리소스를 한 번에 가져올 수 있는 기능을 제공합니다.
OpenTofu에서 import 블록의 매개변수는 다음과 같습니다:
- to: 이 리소스가 상태 파일에 가질 인스턴스 주소.
- id: 리소스의 import ID를 포함한 문자열.
- provider (선택 사항): 커스텀 리소스 제공자, 자세한 내용은 The Resource provider Meta-Argument를 참조.
- for_each (선택 사항): 맵이나 집합을 반복(iterating)하여 여러 리소스를 가져옴. 자세한 내용은 Importing multiple resources를 참조.
Loopable import blocks 실습
main.tf 파일 작성
실습을 위해 app, web 두 개의 aws instanace를 생성하는 코드를 작성합니다.
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 43.202.58.222 running
app 3.39.0.124 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-0443a434a57db296a",
...
문제 상황 재연 : tfstate 파일 삭제
import 기능을 검증하기 위해 tfstate 파일을 삭제합니다. import문의 id로 사용할 EC2 정보를 메모합니다.
# 문제 상황 재연 : 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-03f535c22081172dc 43.202.58.222 web
i-06c1abf24bec7a051 3.39.0.124 app
main.tf 파일 수정
- instance_id 변수 리스트에 위에서 메모해 둔 인스턴스 정보를 합니다.
- import문은 for_each 반복문을 통해서 한 번에 리소스를 가져올 수 있습니다.
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-03f535c22081172dc", "i-06c1abf24bec7a051"]
}
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
}
실행 후 확인
tfstate 파일의 리소스를 잘 가져오는 것을 확인할 수 있습니다.
# 초기화
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
기존 테라폼에서는 State 파일을 암호화해서 관리하려면 추가적인 써드파티를 통해서만 가능했지만, OpenTofu는 로컬 스토리지와 백엔드를 사용할 때 State 파일을 암호화하는 것을 지원합니다.
Key providers는 PBKDS2, AWS KMS, GCP KMS, OpenBao(experimental) 등을 지원합니다.
다음은 기본 코드 예시입니다.
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 기능은 신규 암호화 설정 변경 설정 후 실행 시 문제 발생 시, 자동으로 이전 암호화 설정으로 전환 적용하여 대체하는 기능을 제공합니다.
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
}
}
}
}
로컬 State 파일 암호화하기 : State file encryption - Local
backend.tf 파일 작성
main.tf 파일은 위 실습에서 사용한 코드를 사용하고, encryption에 필요한 backend 파일을 작성합니다.
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
{
"serial": 1,
"lineage": "0378370c-36d8-17e4-b479-45a3b76ece75",
"meta": {
"key_provider.pbkdf2.my_passphrase": "eyJzYWx0IjoiQWNQYzRYMDhWM2drYWh6dDdBZ1I3SkdaVWw5ZS9Ka0o5bkJhWFdvK09Xbz0iLCJpdGVyYXRpb25zIjo2MDAwMDAsImhhc2hfZnVuY3Rpb24iOiJzaGE1MTIiLCJrZXlfbGVuZ3RoIjozMn0="
},
"encrypted_data": "WJqtFXC/dq3xIABpM30zY5JZbdiiv8X44MkSeYWt94SzWD9beceAgeA1bSzBj38QUQJLPYnad2KBy8snxhfeQTWjG1qUtRkJjebFkQlfyKyqLU/SrAHIz0sRKMDe0Q2EezXex+1w/4539p4R7pYiaDaPcNe+tIWBy8Ps8+6sltLrFeg/YmK+sJxkYF9ulvfoGkQVy2wWdlOVTSI7P47eJiHwETd+CLDzwfum0PepdVpd2FNv8TlOQ9g9hGkQ5pGvhEQm8/KBIvPStmcCGsub/2fnsN+1WQ6KdkeAn8GhYom2pxlZ7VXszl32a9pgx/sD/C5CObJa2X+qM+84WVCpQpH4q0+x4OdRfkZ7/Oat1sp4+6M5sbz+8fgPDy+bVhwTD/AtDKlFh5N4zKJsforJays+ALgzOqCCidheupruuwrdMXjjTtpYaAv0I/mfWRSwIgp7TTieQjCtLL1A1FbBHsL3A1mnbEYX7F3UuAEXHV7aFoFR58UZpvG4xicsm1AN9r842dVzTihd658ECFthImTjFvi6NWSfV1OFXiagW2vt/qnisbznTFGFwOK4o612Vu9LTzGViC1IMGY3rx489BYbaLBvYiQjMQau+KjkG0WVKiCzkHYrfsbPIX2+lJliAK2PKPxavQvBS57eUxrttY3RQCWFp2CdF90Vecc7/QU7KZZ0M1OIdqxEKcNsRAe66f6VkU8ebpNwmQ7XWE5b7BaGGKEWThlStnJFlvdI27zl/0uhL8c1DQdPAOd5gxHdyL5WnLQOofdqmsmffw9KIyrBpeG1oMb+iFBzxRy8HMz/nDsm6bgIbx7I35DIdszpD7DzXYOu5W7rhEIL47ckD8It+86pn9I2VUrbr4DQstIbqimhVH1cBWtxnGwPfWeqBWY/2N8HAABFh/HSS4LVF4o7yXlKBCA7fwF7uTc6o0TuyYAnWBHCOSEUAr8eeaW6NFcpFV1RCAh6Wp5JvAq8f/W+OHLNqjMZt0JY7bUatrYkFx1deNXvunTpJwIcsMGntgv1bC8cnU2isZN/eAZ427iUQZXop0VjiR9p8O59iK2cH+x0miMveB+nnV/UWnVOn4vmSmdw7cm//46nZvNtSxjBO5q8bid53W4hKe8vHJooEhcoATBtQwtzcitwCz80GEQ4p8VNspvqEIagfTPt0YeHJgsiT8gqb4KJdz2GoVtmfqCYxi/dB15o3yIz2OBCU61Bk2aKVIX7/+bAP0at4tYvs5hhYxS+e84j3o3NuuGif4yYZ4RtuT09yxGOBMqHuIKvsyzODpQsPdWGcrqpTr58LCZnlcyWt5En6lcIZ5FgBIPjXmz6
다음 옵션은 암호화되지 않은 환경변수가 저장되는 것을 차단할 수 있습니다.
terraform {
encryption {
state {
enforced = true
}
plan {
enforced = true
}
}
}
Rolling back encryption : 암호화 → 평문으로 마이그레이션
backend.tf 파일 수정
unencrypted.migration 메서드를 통해서 암호화한 state파일의 내용을 평문으로 복호화합니다.
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
{
"version": 4,
"terraform_version": "1.7.3",
"serial": 1,
"lineage": "0378370c-36d8-17e4-b479-45a3b76ece75",
"outputs": {},
"resources": [
{
"mode": "data",
"type": "aws_ami",
"name": "ubuntu",
"provider": "provider[\"registry.opentofu.org/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"architecture": "x86_64",
"arn": "arn:aws:ec2:ap-northeast-2::image/ami-0443a434a57db296a",
...
원격 State 파일 암호화하기 : State file encryption - KMS
원격으로 관리되는 state파일을 aws의 kms 키 프로바이더를 통해서 암호화합니다.
[ 사전준비 ] - 1.AWS S3 버킷 생성 : Backend State 저장용도
state 파일을 원격으로 저장할 S3 버켓을 하나 생성합니다.
aws s3 mb s3://<각자 유일한 S3 버킷명 이름> --region ap-northeast-2
aws s3 mb s3://hackjap-t101 --region ap-northeast-2
# 확인
aws s3 ls
[ 사전준비 ] - 2. AWS KMS 생성 및 실습
KMS에서 대칭키(Symmetric) 생성 후 평문 파일을 암복호화 합니다.
먼저, 키를 생성하고, 쉬운 사용을 위해 별칭도 생성합니다.
# 키 생성(기본값)
# 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=hackjap
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/hackjap'].TargetKeyId" --output text
405f8453-fd31-460a-9338-782df4393150
생성한 kms 키를 통해 평문을 암/복호화합니다.
# CMK로 평문을 암호화해보기
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
0 `�He.0(4}���iB�w"���/Q��-�R$;�B��k0i *�H��
X�p�LbyS����(��J�Q]0�05*=�����j��
���U�������%
# 복호화해보기
aws kms decrypt --ciphertext-blob fileb://secrect.txt.encrypted --output text --query Plaintext | base64 --decode
Hello 123123
backend.tf 파일 수정
- bucket: 생성한 s3 bucket을 입력합니다.
- kms_key_id: 생성한 KMS_ID를 입력합니다.
terraform {
backend "s3" {
bucket = "hackjap-t101" # 각자 자신의 S3 버킷명
key = "terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
}
encryption {
key_provider "aws_kms" "kms" {
kms_key_id = "405f8453-fd31-460a-9338-782df4393150" # 각자 자신의 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 파일을 제거하고 재실행하여, 원격에 저장된 tfstate파일을 로컬에 다운로드하여 내용을 확인합니다.
# 로컬 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/hackjap/.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://hackjap-t101 --recursive
aws s3 cp s3://hackjap-t101/terraform.tfstate .
# 다운받은 tfstate 파일 확인 : VSCODE에서 열어보기
cat terraform.tfstate | jq
{
"serial": 1,
"lineage": "b4cc37e3-2681-4d8f-f79c-a57c8a7771b1",
"meta": {
"key_provider.aws_kms.kms": "eyJjaXBoZXJ0ZXh0X2Jsb2IiOiJBUUlEQUhoRis4NllqZzVRVW0zVHd6TFlFUTQ1SUNnMGZaV09zV2xDRXVGM0ZDTDByd0hvRDVaZU1DbCtiR0VUNy9aejB6L2JBQUFBZmpCOEJna3Foa2lHOXcwQkJ3YWdiekJ0QWdFQU1HZ0dDU3FHU0liM0RRRUhBVEFlQmdsZ2hrZ0JaUU1FQVM0d0VRUU1ZdGo0QldBRllLUFBZM3lsQWdFUWdEdnVmaUtlemRBRGJUK2tzQmgzWUhCZnJpNUExQmpEcTBvM3V2TzAxeDYzOWd1Mk9JT2krQ2piMXZUaElaWGQvT3F6aWttSGpaZGZjMmpkbmc9PSJ9"
},
"encrypted_data": "K4ZcmQeAK7+WkB57CjUpipOgip5/7DCCFsT7Beidr1M1kc0aYjjVz9sb3wwKur/fcYVt0+PqbdCwaMUmXoJahbKEJZeWqyhNd8tCdXGIV4etPhbMT7oQ9ikDS7xjNWMOGbE0pZd3/WbwlgeF1J66UoJSyOVqUDfqkxv/t9j+XymT/XnDctB1KOcmcue1JRH5lNA3XlaxA1/o29dBHlrLqQg9QzaVw0epok6SN8hJmBG2+Mu5J2cLhBvmOKCd2Bm3TjW/kMGVXD+NVUn32HlI6uhM22lMYAcQLpkkVB2LaM04lBasmcfb4XXPmNa2QWDqJdyxBeq+MqOusLIHwRybpERvlRNxcqcMxehEbHZorMAS7C31Ct5rS46Dg3x8iEWyYTrsJcEvRpBIhSqTFd2RyyAuPR43XR6KbZd6KVCidOXMiBnQR8VSAsgRTU5mrlsU3p9kJJ82CYZKIQLxFdUL83LCo2eopUtbN08J9xcH0EYHuMSXgV08qwXFm7jREnfr7HOdL7489omo8RaOCeoaEqDBii8a6itCcYE2LtJ6dhwfwGUNloLzkE/tCXPv09fWFCgWegoa4gad8utcJDA4Po3XJoG+4kPy4ytPAT3T3oZl+rmztCyVkgwT9JnG1au1bj+lbdMID2S7tCxp+ZPT9+aOY+WNtEtxrLhIJEtmEepsvyJVMhdQeWQ2sb4c0iIP5TXEJ1OibIjUoHJKcWJrR7NU9t/Z4JI1wxNhOlRgATCznNCEJVnlOnG/QE5MGGsMPV0ouMKmdrXGYyb7Ea4HJ7p+KVvjI//HXQFAakQWvD2gNN3NCMMcLU9jcGJRS2EmlxEe+DfYg959NDSiqg2FFTREvdTHADybmwuSMUeCRoNsKlRkvTxJaZIi3boHaTgdATM8GgeewCfqD9JwRvj37ITE7OAM3iy3olke0GbTnayATPbTxm4qaqXWOwz1N/w
...
Removed block
opentofu의 removed block은 리소스를 실제 인프라 환경에는 유지하면서 상태 파일에서는 제거할 수 있게 해주는 기능입니다.
제거하고자 하는 리소스를 from 값에 명시합니다.
removed {
from = local_file.test
}
Removed block 실습
위 실습에서 코드에서 EC2 인스턴스를 정보를 aws ssm 파라미터 스토어에 저장하도록 추가하고, removed 블록을 통해서 파라미터 스토어 리소스를 제거하고 tfstate파일과 실제 인프라(aws ssm)를 확인해 봅니다.
main.tf 파일 작성
aws_ssm_parameter 리소스를 통해 인스턴스 정보를 aws ssm에 저장한다.
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-0a41627c9c86c0ad6", "i-0e05abe7a3f229ddd"] # 각자 자신의 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-0a41627c9c86c0ad6]
aws_instance.this[0]: Import complete [id=i-0a41627c9c86c0ad6]
aws_instance.this[1]: Importing... [id=i-0e05abe7a3f229ddd]
aws_instance.this[1]: Import complete [id=i-0e05abe7a3f229ddd]
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:089224560468: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-0a41627c9c86c0ad6",
...
# 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
main.tf 파일 수정
aws_ssm_parameter 리소스를 주석처리하고 removed 블록을 추가합니다.
# 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
}
실행 후 확인
aws_ssm_parameter 리소스가 tfstate 파일에서는 제거되었지만, AWS 상에는 유지되는 것을 확인할 수 있습니다.
#
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
data.aws_ami.ubuntu
aws_instance.this[0]
aws_instance.this[1]
# tfstate 파일 확인 : VSCODE에서 열어보기
cat terraform.tfstate | jq
...
테라폼에서 OpenTofu로 마이그레이션
테라폼 버전별로 OpenTofu의 특정 버전으로 마이그레이션 하는 방법이 잘 나와있으니 각자 버전에 맞는 내용을 참고합니다.
Terraform(1.8) -> OpenTofu(1.7.x) 마이그레이션 실습
Tenv로 Terraform 설치 : 1.8.5
공식문서에서는 마이그레이션 시, 최소 1.8.2 이상 Terraform 버전 사용 권고하고 있습니다.
#
tenv tf list-remote
# 설치
tenv tf install 1.8.5
tenv tf list
tenv tf use 1.8.5
tenv tf detect
#
terraform -version
Terraform v1.8.5
main.tf 파일 작성
간단하게 aws 인스턴스를 생성하는 코드를 작성합니다.
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에서 실행 후 확인
terraform 명령어로 배포 후 확인합니다.
# 초기화
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 43.202.44.81 running
app 3.39.249.14 running
# 확인
terraform state list
# tfstate 파일 확인 : VSCODE에서 열어보기
cat terraform.tfstate | jq
...
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
...
마이그레이션
1. 최신 상태 여부 확인
마이그레이션을 위해서는 현재 최신 상태인지 plan 명령어를 통해 확인합니다.
terraform plan
2. 파일 백업
마이그레이션 시, 예외상황이 발생할 수 있으니, 코드 파일과 state 파일을 백업합니다.
cp terraform.tfstate ../terraform_backup
cp *.tf ./terraform_backup
S3 backend, Removed block, Testing changes 등 테라폼과 OpenTofu 간 문법 차이가 있으니 버전에 맞는 문법에 맞게 수정합니다.
4. OpenTofu init & plan
tofu 명령어를 통해 init & plan을 진행합니다.
공식문서에서는 init 단계에서 하나라도 문제 발생 시, 진행하지 말고, 롤백하라고 하니, 운영환경에서는 각별한 주의가 필요합니다.
terraform과 tofu 두 개의 프로바이더 파일이 존재하는 것을 확인할 수 있습니다. 또한 plan 시에도, 변경사항이 없는 것으로 보아 배포 시 문제가 없어 보이는 것으로 판단됩니다.
#
tofu init
#
tree .terraform -L 5
.terraform
└── providers
├── registry.opentofu.org
│ └── hashicorp
│ └── aws
│ └── 5.60.0
└── registry.terraform.io
└── hashicorp
└── aws
└── 5.60.0
#
tofu plan
data.aws_ami.ubuntu: Reading...
data.aws_ami.ubuntu: Read complete after 1s [id=ami-0443a434a57db296a]
aws_instance.this[0]: Refreshing state... [id=i-086726bac1a36fd33]
aws_instance.this[1]: Refreshing state... [id=i-0c0c1a36d1c1e5e59]
No changes. Your infrastructure matches the configuration.
OpenTofu has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
5. OpenTofu Apply
공식문서에서도 안전한 마이그레이션을 위해서 경미한 변경을 해서 배포하고 잘 적용이 되는지 확인하도록 권장하고 있습니다.
ec2의 태그를 하나 추가해서 잘 적용되는지 확인해 봅니다.
#
tofu apply -auto-approve
diff terraform.tfstate terraform.tfstate.backup # 테라폼 상태와 비교
# Test out a small change
# EC2 인스턴스에 신규 태그를 추가 후 apply 해서 한번 더 정상 상태 확인 해보기를 권장!
...
tags = {
Name = var.instance_tags[count.index]
T101 = "end"
}
#
tofu apply -auto-approve
ec2 태그를 확인해 보면 잘 반영됨을 확인할 수 있습니다.
'Automation > Terraform' 카테고리의 다른 글
테라폼으로 AWS EKS 배포 - T101 7주차 (0) | 2024.07.27 |
---|---|
Terraform Module & Runner - T101 5주차 (2) | 2024.07.14 |
Terraform Provider & State - T101 4주차 (0) | 2024.07.06 |
Terraform 기본사용 3 - T101 3주차 (0) | 2024.06.29 |
Terraform 기본사용 2 - T101 2주차 (0) | 2024.06.23 |