FINAL
들어가며
이번 스터디에서는 Terraform을 오픈소스 형태로 사용할 수 있는 OpenTofu에 대해 학습했습니다.
IaC의 핵심 언어인 Terraform이 BUSL 형태로 변경되어서, 고민하고 있던 차에 스터디에서 새로운 해결책을 발견한 것 같아 기뻤습니다.
OpenTofu란?
작년 8월 Terrfarom 라이선스가 MPL(Mozilla Public License)에서 BUSL(Business Source License)로 변경되며, 오픈소스 버전인 OpenTofu가 개발되었습니다.
OpenTofu에서 HashiCorp가 2023년 8월 10일 Terraform의 라이선스를 오픈 소스 라이선스(MPL)에서 비오픈 소스 라이선스인 Business Source License(BUSL)로 전환한 것에 대한 우려를 선언문으로 공표했습니다. - 링크
선언문의 요약은 다음과 같습니다.
커뮤니티는 이 변경이 예고 없이 이루어졌으며, 이는 수천 명의 사용자와 기여자, 기업들이 법적 위험에 직면하게 된 새로운 현실을 만들었다고 주장합니다.
선언문에서는 BUSL이 모호하고, Terraform을 사용하는 모든 기업과 개발자가 HashiCorp의 제품과 경쟁하게 될 가능성에 대해 우려하고 있으며, 이는 커뮤니티와 생태계에 심각한 영향을 미칠 것이라고 경고합니다. 또한, Terraform을 중심으로 구축된 오픈 소스 생태계가 감소하고 사라질 위험이 있다고 주장합니다.
이 선언문의 주요 목표는 Terraform을 다시 완전한 오픈 소스 라이선스로 되돌리는 것입니다. 이를 위해, Linux Foundation에서 Terraform의 포크인 OpenTofu를 유지하고 있으며, 이 포크는 진정한 오픈 소스로, 커뮤니티 중심의 공정한 프로젝트 관리와 계층화된 모듈화 구조를 갖추고 있습니다. 이를 통해 미래에 변동 없이 신뢰할 수 있는 환경을 제공하려 합니다.
Work Flow는 Terraform과 동일하게 Write, Plan, Apply로 구성되어 있습니다.
현재 시점으로는 OpenTofu가 나온지 얼마 되지 않아 OpenTofu 1.6.x 버전과 Terrafrom 1.6.x버전과 기능적으로 매우 유사하지만, 앞으로 프로젝트의 기능이 갈라질 것이기 때문에, 향후 관리의 관점에서 어떤 것을 사용할지 노선을 정해야 하지 않을까 싶습니다.
OpenTofu 설치
Tenv를 먼저 설치합니다. Go로 만들어진 오픈소스로써 OpenTofu , Terraform , Terragrunt 및 Atmos의 버전을 관리해준다고 합니다.
# (옵션) tfenv 제거
brew remove tfenv
# Tenv 설치
## brew install cosign # 설치 권장
brew install tenv
# (옵션) Install shell completion
tenv completion zsh > ~/.tenv.completion.zsh
echo "source '~/.tenv.completion.zsh'" >> ~/.zshrc
tenv tofu install 1.7.3
tenv tofu use 1.7.3
tofu -h
tofu version
기존에 사용하고 있던 tfenv를 삭제하고, tenv를 설치후 tofu를 세팅한 결과는 다음과 같습니다.
OpenTofu 1.7.0 실습 시나리오는 여기를 참고했다고 합니다. - Docs
실습 1 - corefunc 프로바이더 사용
다양한 함수들을 제공하고 있으며, 테라폼과 다르게 사용자 정의 함수를 프로바이더 정보를 통해 사용할 수 있는 점이 독특한 것 같습니다.
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
}
프로바이더 정보를 확인하면 다음과 같이 northwood-labs에서 만든 corefunc를 확인할 수 있습니다. 또한 레지스트리도 opentofu로 변경되어 있습니다.
스네이크 표기법으로 Hello world를 변경하는 실습을 진행했습니다.
tfstate 파일이 생성된 것을 확인할 수 있습니다.
해당내용을 카멜 표기법으로 변경을 해보았습니다.
value = provider::corefunc::str_camel("Hello world!")
카멜 표기법으로 변경이 되고, tfstate 백업 파일이 생성된 것을 확인할 수 있습니다.
실습 2 - Loopable import blocks 사용해보기
AWS에 있는 리소스를 openTofu파일로 관리하기 위해 TF state 파일을 생성하려는 경우 import를 사용합니다. 이때 리소스가 여려 개 있을 경우, Loop를 활용하여 여러 리소스를 가져오는 기능을 제공합니다.
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"]
}
#instance_tag의 list를 기반으로 2개의 인스턴스를 생성함
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을 수행한 후 프로바이더를 확인해 보면 registry가 opentofu로 설정된 것을 확인할 수 있습니다.
tree .terraform
tofu를 이용하여 배포하면 다음과 같이 web, app 인스턴스가 생성되는 것을 확인할 수 있습니다.
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
다음으로 실습을 위해 tfstate 파일을 삭제하여 테라폼 상태와 프로비저닝 된 인프라 상황을 격리합니다.
rm -rf .terraform* terraform.tfstate*
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-0ed5d8ee3db3e16df", "i-0e0b508e440311640"]
}
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
}
다음으로 import 블록을 통해 variable instance_ids를 통해 apply를 수행하면 다음과 같이 2개의 인스턴스가 잘 import 된 것을 확인할 수 있습니다.
tofu init
tofu apply -auto-approve
쉽게 Import가 되었습니다!
실습 3 - State file encryption 해보기
OpenTofu에서는 State file을 암호화해서 저장하는 하는 기능을 서드파티 없이 쉽게 제공한다고 합니다.
실습 2의 경로에 backend.tf를 생성한 후 암호화 블록을 추가합니다.
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 = "devlosT101LastLecture!"
}
## 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.
}
}
terraform apply를 수행하면 다음과 같이 tfstate 파일이 암호화되는 것을 확인할 수 있습니다.
암호화된 파일을 show를 이용하면 복호화하여 리소스를 확인할 수 있습니다.
tofu show
이번에는 암호화된 state 파일을 평문으로 마이그레이션 해봅니다.
method.unencrypted를 사용하여 마이그레이션을 수행합니다.
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 = "devlosT101LastLecture!"
}
## Step 3: Add the desired encryption method:
method "aes_gcm" "new_method" {
keys = key_provider.pbkdf2.mykey
}
## Remove this after the migration:
method "unencrypted" "migration" {
}
state {
method = method.unencrypted.migration
## Remove the fallback block after migration:
fallback{
method = method.aes_gcm.new_method
}
# Enable this after migration:
enforced = false
}
## Step 8: Repeat steps 4-8 for plan{} if needed.
}
}
tofu apply -auto-approve
마이그레이션 결과 암호화 되어 있던 파일이 잘 복호화된 것을 확인할 수 있습니다.
실습 4 - State file encryption(AWS KMS를 사용한 암호화) 해보기
Backend state 저장용 버킷을 생성합니다.
aws s3 mb s3://devlos0322-t101 --region ap-northeast-2
# 확인
aws s3 ls
KMS란, 키를 전문적으로 관리해 주는 서비스입니다. 아래의 명령을 통해 실습을 위한 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 생성 (ID 대신 단축키 형태로 사용하기 위해)
export ALIAS_SUFFIX=<각자 닉네임>
export ALIAS_SUFFIX=devlos
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/devlos'].TargetKeyId" --output text
# 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
다음과 같이 키가 잘 생성된 것을 확인할 수 있습니다.
생성된 KMS 키를 통해 텍스트 파일의 암호화 복호화를 수행한 결과는 다음과 같았습니다.
오.. 다시 보아도 신기하군요 :)
실습으로 돌아가, Backend 파일을 수정합니다.
terraform {
backend "s3" {
bucket = "devlos0322-t101" # 각자 자신의 S3 버킷명
key = "terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
}
encryption {
key_provider "aws_kms" "kms" {
kms_key_id = "4db40b48-a5ec-4276-8549-414b2ca3b1ec" # 각자 자신의 KMS ID
region = "ap-northeast-2"
key_spec = "AES_256"
}
## 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 = "devlosT101LastLecture!"
}
## Step 3: Add the desired encryption method:
method "aes_gcm" "new_method" {
keys = key_provider.pbkdf2.mykey
}
## Remove this after the migration:
method "unencrypted" "migration" {
}
state {
method = method.unencrypted.migration
## Remove the fallback block after migration:
fallback{
method = method.aes_gcm.new_method
}
# Enable this after migration:
# enforced = false
}
## Step 8: Repeat steps 4-8 for plan{} if needed.
}
}
tofu init
tofu apply -auto-approve
Apply를 수행하면 KMS를 이용하여 암호화된 tfstate 파일이 s3 버킷에 잘 관리되고 있음을 확인할 수 있습니다.
실습 5 - Removed block 해보기
Removed block은 openTofu로 배포했으나, tfstate 파일로 관리하고 싶지 않을 때, 리소스를 tfstate에서 지울 때 사용합니다.
관리하고 있지 않는 리소스를 구분하기 위해 ssm_parameter를 사용합니다.
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-0012236d8f35816fe", "i-065c8f007a254da23"] # 각자 자신의 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 apply --auto-approve
다음과 같이 기존 2개의 리소스와 신규 2개의 파라미터 스토어 리소스가 잘 생성된 것을 확인할 수 있습니다.
다음으로 주석을 이용하여 파라미터 스토어 리소스만 tfstate상에서 제거합니다.
# 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 #리소스 레퍼런스 참조
}
tfstate에서는 삭제되었으나, 실제로 삭제되지는 않았다는 메시지가 출력됩니다.
tofu state ls로 확인을 해보면 tfstate에서 삭제되었지만, 파라미터 스토어에는 여전히 존재하는 것을 확인할 수 있습니다.
실습 6 - Terraform => OpenTofu 마이그레이션 해보기
해당 실습은 테라폼의 state를 기반으로 OpenTofu로 바로 마이그레이션 하는 방법 하는 방법에 대한 것이었습니다.
(Tenv로 Terraform 설치 : 1.8.5 → 마이그레이션 시, 최소 1.8.2 이상 Terraform 버전 사용 권고한다고 합니다.)
tenv tf -h
tenv tf list
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
다음과 같이 Terrafrom과 OpenTofu 모두 잘 설치된 것을 확인할 수 있습니다.
다음으로는 테라폼을 이용하여 리소스를 배포합니다.
#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]
}
}
terrafrom plan
terraform apply
배포한 리소스의 registry를 확인해 보면 terraform.io를 사용하고 있는 것을 확인할 수 있습니다.
마이그레이션 전 최신 스테이트 여부 확인 및 state file, code 파일을 백업합니다.
tfstae파일이 있는 상황에서 tofu init을 수행합니다. 그렇게 한 후 tree로 providers를 확인해 보면 두 가지 레지스트리가 동시에 출력되게 됩니다.
tofu를 이용하여 배포를 수행한 후 tfstate와 tfstate.backup을 비교해 보면 다음과 같이 serial 카운트가 올라가고, provider가 terrafrom에서 openTofu로 변경된 것을 확인할 수 있습니다.
tofu apply -auto-approve
diff terraform.tfstate terraform.tfstate.backup
Apply가 잘 되는지 한번 더 확인해 봅니다.
# Test out a small change
# EC2 인스턴스에 신규 태그를 추가 후 apply 해서 한번 더 정상 상태 확인 해보기를 권장!
...
tags = {
Name = var.instance_tags[count.index]
T101 = "end"
}
#
tofu apply -auto-approve
실습 7 - OpenTofu variable 변수 선언 및 사용해보기
기존 terraform.backend에서는 variable 변수 사용할 수 없었습니다. variable 변수를 사용할 경우 terraform init 과정에서 백엔드가 초기화되어야 하기 때문에 에러가 발생하게 되는 상황이 발생합니다.
또한 모듈 소스에도 variable 변수를 사용할 수 없기 때문에 리소스가 여러 게일 때 하나하나 생성해 줘야 했었습니다.
OpenTofu에서는 이를 로컬을 통해 사용할 수 있도록 기능이 추가되었습니다.
실습을 위해 다시 버킷을 만들고 리소스를 생성합니다.
variable "s3bucket_myname" {
type = string
default = "devlos-t101" # 각자 자신의 S3 버킷명
}
terraform {
backend "s3" {
bucket = var.s3bucket_myname
key = "terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
}
}
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
}
resource "aws_instance" "this" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name = "final-labs"
}
}
기존 terrafrom init 수행 시에는 변수를 받아서 사용하는 것이 불가능했던 점을 실습을 통해 확인했습니다.
terraform init
하지만 tofu는 이러한 변수를 통한 init을 할 수 있도록 기능이 추가되었고, 계속 개선 중에 있습니다.
tofu init을 수행한 결과는 아래와 같습니다.
tofu init
tofu apply -auto-approve
리소스를 생성하면 버킷에 state가 잘 생성되는 것을 확인할 수 있었습니다.
마치며
이번 스터디에서는 Terraform 기반 오픈소스인 OpenTofu를 이용하여 라이선스 제약을 받지 않고 IaC를 적용하는 법에 대해 배웠습니다. 다 배우고 나니 상당히 유용해 보이고, 도입해보고 싶은 마음이 드네요. 하지만 아무래도 현재까지의 점유율이 Terraform이 높고 레퍼런스가 훨씬 많다 보니, 성급하게 전환하기보다는 조금 더 상황을 지켜보며 테스트를 해볼 필요가 있을 것 같습니다.
이번 블로깅을 끝으로 8주간의 스터디 과정이 마무리가 되었네요. 스터디를 하고 날 때마다 앞으로 해야 할 일들이 머릿속에 그려지며 답답하기도 하지만, 한편으로는 그만큼 성장하고 시야가 트인다는 느낌이 있습니다. 또한 평일에 틈틈이 과제를 하다 보니 시간이 쏜살같이 지나가네요..! (그렇다면 스터디준비하시느라 고생하시는 리드분들의 시간은 도대체..?)
제 개인적인 관점에서는 23년 1월부터 CloudNet@ 스터디를 4번째 참여하고 있다 보니 합산했을 때 거의 2년 중 7개월 정도는 매주 스터디를 위해 공부를 했네요. 정말 혼자서는 해내지 못할 일들을 하고 있는 것 같아 스터디 그룹이 대단하기도 하고 감사하기도 합니다.
또한 저의 꾐(?)에 넘어가 스터디에 참여하게 된 동료들도 무사히 이번 스터디를 마무리할 수 있을 것 같아서(얼른 과제하세요!!) 뿌듯하기도 합니다.
아무튼 스터디가 9월부터 다시 시작된다고 하니, 저는 다시 KCNA 자격을 취득하러 여정을 떠나보겠습니다.
다들 고생 많으셨습니다.
감사합니다 :)
'클라우드 컴퓨팅 & NoSQL > [T101] 테라폼 4기 스터디' 카테고리의 다른 글
[7주차 - 테라폼으로 AWS EKS 배포 ] T101 4기 스터디 (24.07.21) (0) | 2024.07.26 |
---|---|
[6주차 - Well-Architected 방식으로 워크로드를 안전하게 마이그레이션 및 현대화하기 Workshop ] T101 4기 스터디 (24.07.14) (0) | 2024.07.17 |
[5주차 - 모듈 & Runner] T101 4기 스터디 (24.07.07) (1) | 2024.07.13 |
[4주차 - Provider & State] T101 4기 스터디 (24.06.30) (0) | 2024.07.04 |
[3주차 - 기본사용#3] T101 4기 스터디 (24.06.23) (0) | 2024.06.29 |