[Terraform@CloudNet] Terraform과 OpenTofu

2024. 8. 3. 11:07EKS@Terraform

- CloudNet에서 주관하는 Terraform 스터디 내용입니다

- 내용은 위 책 테라폼으로 시작하는 IaC 를 기준으로 정리하였습니다.

- 실습은 M1 macbook air 에서 진행했습니다.

- 매번 좋은 스터디를 진행해 주시는 CloudNet 팀 감사드립니다

- 잘못된 점, 업데이트된 지식, 다른 코멘트 언제나 환영입니다!

 

 

 

OpenTofu 소개

 

테라폼을 포크해서 만든 완전한 오픈소스 IaC 툴 이라고 한줄로 요약할 수 있겠습니다

툴은 바뀌었지만 문법은 기존과 같이 HCL을 사용합니다!

 

기존에는 Terraform도 완전한 오픈소스였으나 아래 타임라인에 따라 점차 변화합니다

23.08 : 해시코프가 테라폼 라이센스를 MPL -> BUSL 로 변경, OpenTF 포크 발표

23.09 : OpenTF 레포 공개, OpenTofu로 이름 변경

24.04 : IBM이 해시코프 인수

24.05 : 오라클이 Terraform -> OpenTofu로 교체

 

 

현재까지 테라폼 1.5~6과 OpenTofu 1.5~6은 대부분 호환되나 마이그레이션 가이드를 제공하고 있습니다.

https://opentofu.org/docs/intro/migration/

 

Migrating to OpenTofu 1.7.x from Terraform | OpenTofu

Learn how to migrate to OpenTofu from Terraform.

opentofu.org

 

 

OpenTofu 설치 및 환경 구성

 

기존 실습에 사용하던 tfenv 를 제거하고 tenv 를 사용합니다

brew remove tfenv
brew install tenv

tenv tofu list-remote
tenv tofu install 1.7.3
tenv tofu use 1.7.3
tenv tofu detect

tofu version

 

 

 

tfenv 가 terraform을 위한 가상환경이라면 tenv는 tofu를 위한 가상환경입니다.

처음에 list 로 사용가능한 버전을 확인했을 때는 아무것도 없짐나 remote-list 로 전체를 확인하고 설치할 수 있습니다 

install -> use -> detect 모두 실행하면 버전이 잘 나오고 tofu를 실행할 준비가 되었습니다

 

 

OpenTofu 실습1: 기본 사용

OpenTofu 는 테라폼과 같은 HCL(Human Centered Language) 문법을 사용하기에 첫인상으로는 유사하게 느껴집니다. 하지만 IaC의 꽃인 프로바이더는 당연하게도 OpenTofu에 맞는 걸 적절하게 찾아서 사용해야 합니다. 입력된 문장을 snake case로 바꿔주는 아래 코드를 main.tf로 저장하고 Tofu로 실행해 보겠습니다

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 사용이 처음이니 스텝바이스텝으로 짚고 넘어가보겠습니다. 전체적으로 테라폼과 유사합니다

tofu init
tofu plan
tofu apply
tofu output

 

 

 

tree 명령어로 찍어보면 terraform 과 동일하게 state, provider 가 생성되어 있습니다. provider에는 위에서 사용한 northwood-labs 의 프로바이더가 들어와 있습니다.

 

tofu와 terraform 의 관계는 tofu -> terraform 을 실행하는 다른 방법! 정도로 우선은 받아들이기로 했습니다.

 

 

 

 

OpenTofu 실습2: aws provider 사용

이번에는 tofu에서 aws provider 를 사용해 보겠습니다.

 

새 실습폴더에 main.tf 파일을 아래와 같이 작성합니다. 아래 main.tf 을 간단하게 살펴보면 ami_id 중 특정 조건을 만족시키는 인스턴스를 선택해 각각에 "web" "app" 이라는 태그를 달고 t3.micro 인스턴스에 배포하는 테라폼... 아니 tofu 코드입니다.

 

특정 조건이란

- 22.04 ubuntu + amd64 형태에 만족

- hvm 가상화 적용

- canonical(ubuntu 개발사) 에서 배포한 이미지

 

를 만족하는 ami 이미지입니다.

바로 ami 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_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 명령어를 사용해서 terraform과 마찬가지로 배포합니다

tofu init
tofu apply -auto-approve

# 확인
tofu state list
tofu show

# 자세한 확인
echo "data.aws_ami.ubuntu" | tofu console

 

아래 코드로도 현재 배포되어 돌고 있는 인스턴스를 확인할 수 있습니다

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

tofu ls (list) 와 tofu console 을 활용해서 배포 상태를 자세하게 확인할 수 있습니다.

 

 

 

 

OpenTofu 실습3: Key provider 사용

OpenTofu는 로컬 스토리지 및 클라우드 백엔드 모두에서  암호화를 지원합니다. 기존 프로젝트에서 암호화를 처음 구성할 때 상태 및 계획 파일은 암호화 되지 않습니다. unencrypted 방법 지정을 통해 (기본적으로 비활성화된) 암호화되지 않은 데이터 읽기를 활성화 시킬 수 있습니다 unencrypted 는 암호화 -> 평문 마이그레이션 또한 지원합니다.

 

이번에는 pbkds2 라는 키 암호화 알고리즘을 사용해서 암호화를 실습하려고 합니다. pbkds2 는 프로바이더로 지정되고, method는 aes_gcm 을 적용합니다. 

 

암호화에서 프로바이더와 메소드가 헷갈려서 추가로 정리하면 프로바이더는 암호화에 사용할 키를 생성하는 방법을 정의하고, 메소드는 실제 암호화를 수행하는 방법을 정의합니다. 따라서 키 프로바이더가 먼저 실행되고 나서 메소드가 실행된 프로바이더를 사용해 암호화를 수행합니다.

 

새 디렉토리(8.3)를 만들고 실습2.aws provider 사용 에서 사용한 main.tf 를 복사해서 재사용합니다.

여기에 추가로 backend.tf 파일을 생성합니다. 이 파일은 pbkds2 프로바이더와 aes_gcm 메소드가 적용되어 terraform.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.aes_gcm.my_method

      ## Remove the fallback block after migration:
      fallback{
        method = method.unencrypted.migration
      }
      ## Enable this after migration:
      #enforced = true
    }
  }
}

 

8.2의 실습과 유사하지만 backend.tf 가 추가된 구조입니다

실습2와 동일하게 배포를 진행합니다

tofu init
tofu apply -auto-approve

 

배포를 성공했습니다. 아까와 동일하게 각각 web/app 라벨을 단 인스턴스가 확인됩니다

그러나 둘의 terraform.tfstate 파일을 비교해보면 암호화 적용 여부가 차이가 있음을 볼 수 있습니다.

위 backend.tf 에서 enforced=True 의 주석을 제거하면 암호화되지 않은 환경변수가 저장되는 것을 차단하는 보다 강력한 보안정책을 적용하게 됩니다. tofu apply -auto-approve 후 확인해보면 마찬가지로 암호화가 잘 수행된 것을 확인할 수 있습니다

이번에는 반대로 암호화된 tfstate 파일을 평문으로 마이그레이션 해 보겠습니다. backend.tf 파일을 아래와 같이 바꿔줍니다. unencrypted 와 aes_gcm 의 위치가 서로 바뀐 것을 확인할 수 있습니다

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

# 암호-> 평문
	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후 terraform.state 를 확인하면 다시 평문으로 돌아온 것을 확인할 수 있습니다

 

OpenTofu 실습4: AWS KMS 를 통한 키관리

AWS 는 KMS(Key Management System) 을 통해서 키를 관리합니다. 이번에는 AWS KMS를 통해 암호화를 수행하고 backend state 를 s3에 저장해 보겠습니다.

 

사전준비로 tf.state file을 저장할 S3 bucket을 생성합니다. mb는 make bucket 의 약자입니다

aws s3 ls
aws s3 mb s3://hitherex-tofu --region ap-northeast-2
aws s3 ls

 

사전준비로 KMS 를 생성합니다. 이 대칭 키로 평문 파일을 암호화/복호화 할 수있게 됩니다

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=MYNICKNAME
aws kms create-alias --alias-name alias/$ALIAS_SUFFIX --target-key-id $KEY_ID

# 키 alias 확인
aws kms list-aliases \
--query "Aliases[?AliasName=='alias/<각자 닉네임>'].TargetKeyId" \
--output text

 

키가 잘 생성된 것을 볼수 있습니다. 이 키를 이용해서 암호화/복호화를 수행할 것이므로 alias를 저장해 둡니다

dd88705f-135f-4108-9482-8717926d053d

 

backend 암호화/복호화 전 평문으로 먼저 실습해보겠습니다

# 암호화
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 을 암호화/평문으로 모두 볼 수 있습니다

아래와 같이 terraform.tfstate 파일을 s3 bucket에 저장하고 ams_kms 를 통해 암호화하도록 backend.tf 파일을 변경합니다.

main.tf 는 기존과 동일하게 사용합니다

terraform {
  backend "s3" {
    bucket = # 각자 자신의 S3 버킷명
    key = "terraform.tfstate"
    region = "ap-northeast-2"
    encrypt = true
  }

  encryption {
    key_provider "aws_kms" "kms" {
      kms_key_id =  # 각자 자신의 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
    }
  }
}

 

initialize -> apply 합니다

tofu init
tofu apply -auto-approve

 

실행 후 로컬을 확인하면 terraform.tfstate 파일이 생성되지 않음을 확인할 수 있습니다 (errored.tfstate는 타 실습에서 생김)

대신 방금 지정한 s3 bucket 에 terraform.tfstate 파일이 위치합니다.

 

aws s3 ls s3://my-bucket --recursive

이 tfstate 파일이 암호화되었는지 확인하기 위해 s3 bucket에 접근해서 다운로드 받은 후 내용을 확인해 보겠습니다

aws s3 cp s3://my-bucket/terraform.tfstate .
cat terraform.tfstate

 

 

정상적으로 암호화가 수행되었음을 볼 수 있습니다

사용한 리소스 삭제를 마지막으로 실습을 종료합니다

# tofu 배포 리소스 삭제
tofu destroy -auto-approve

# 버킷 삭제
aws s3 rm s3://hitherex-tofu/terraform.tfstate
aws s3 rb s3://hitherex-tofu --force

 

 

 

마치며

개인적으로는 이번이 세번째 CloudNet 팀의 스터디였습니다. 이번에는 테라폼과 opentofu을 이용해서 리소스를 관리하고 코드화하는 방법을 배우고 실습하였습니다. 시기적절하게(?) 스터디 시작직전 개인 계정에서 인스턴스를 종료하지 않아 치킨 두마리 값을 날렸고, 덕분에 더욱 필요성에 공감하며 즐겁게 테라폼 스터디를 진행할 수 있었습니다.

 

혼자라면 절대 못했을 분량의 학습을 이끌어주신 CloudNet 팀과 가시다님께 감사드립니다!