본문 바로가기
IaC/Terraform

[Terraform] 8-1. What is OpenTofu ?

by okms1017 2024. 8. 4.
728x90

✍ Posted by Immersive Builder  Seong

 

 

1. OpenTofu 소개 

OpenTofu 란?

  • HashiCorp에서 Terraform 라이선스를 MPL(Mozilla Public License)에서 BSL(Business Source License)로 변경하는 결정에 대응하여 OpenTF 이니셔티브에서 Terraform의 포크 버전인 OpenTF를 발표함('23.08)
  • OpenTF 저장소가 공개되고, 포크 버전의 명칭을 OpenTF에서 OpenTofu로 변경함('23.09)
  • IBM사에서 HashiCorp사를 64억 달러에 인수 계약 체결함('24.04)
  • Oracle E-Business Suite Cloud Manager 제품의 최신 버전에서 사용중인 Terraform을 OpenTofu로 대체함('24.05)
  • 엔터프라이즈 수준에서 사용 가능한 첫 번째 버전인 OpenTofu v1.7.0이 출시됨('24.05) 
  • OpenTofu는 오픈소스로 커뮤니티에서 주도적으로 개발하고 리눅스 재단(Linux Foundation)에서 관리함 
  • 다양한 유형의 리소스와 서비스를 관리하기 위해 수천 개의 프로바이더를 작성함
  • Early Evaluation, Provider Mocking, Coder-Friendly Future 등 기능이 추가된 OpenTofu v1.8.0이 출시됨('24.07)

 

OpenTofu vs Terraform 

  • OpenTofu와 Terraform은 동작 방식과 사용 방법이 거의 동일함 
  • 사용하는 명령어 정도가 terraform → tofu로 다름 
  • 기술적인 측면에서 OpenTofu v1.6과 Terraform v1.6이 기능적으로 매우 유사하며, 해당 버전을 기점으로 추가되는 기능이 달라질 예정임 
  • OpenTofu의 목표는 단일 회사가 로드맵을 지시할 수 없는 협력적인 방식으로 추진되는 것임 
  • OpenTofu는 Terraform v1.5에서 생성한 상태파일까지만 지원함  
  • OpenTofu는 Terraform과 달리 자체 프로바이더가 없으며 Terraform 프로바이더를 차용함 
  • OpenTofu는 별도의 레지스트리(registry.opentofu.org)를 사용함 

 


2. OpenTofu 설치 

Tenv 설치

Tenv는 Terraform, OpenTofu, Terragrunt, Atmos 버전을 한 번에 관리할 수 있는 도구입니다.

 

# 기존 Terraform 삭제
$ sudo apt remove terraform 

# Tenv 설치 
$ LATEST_VERSION=$(curl --silent https://api.github.com/repos/tofuutils/tenv/releases/latest | jq -r .tag_name)
$ curl -O -L "https://github.com/tofuutils/tenv/releases/latest/download/tenv_${LATEST_VERSION}_amd64.deb"
$ sudo dpkg -i "tenv_${LATEST_VERSION}_amd64.deb"

# Tenv 설치 확인 
$ tenv --version
tenv version v2.7.9
$ tenv tofu -h

# Tenv 자동 완성
$ tenv completion bash > ~/.tenv.completion.bash
$ echo "source '~/.tenv.completion.bash'" >> ~/.bashrc

 

OpenTofu 설치 

# 설치 지원 버전 확인 
$ tenv tofu list-remote

# OpenTofu 설치
$ tenv tofu install 1.7.3
$ tenv tofu list
$ tenv tofu use 1.7.3

# OpenTofu 설치 확인  
$ tofu -h
$ tofu version

 


3. OpenTofu v1.7.0

OpenTofu v1.7.0에서 제공하는 몇 가지 특징적인 기능에 대해서 소개합니다. 

Provider-defined functions

프로바이더에 직접 함수를 정의하여 사용하는 방법입니다. 

달리 말해, 내장 함수가 아닌 사용자 정의함수와 유사한 개념으로 이해하면 쉬울 것입니다. 

 

  •  corefunc 프로바이더 예시  링크

terraform {
  required_providers {
    corefunc = {
      source = "northwood-labs/corefunc"
      version = "1.4.0"
    }
  }
}

provider "corefunc" {
}

output "test_1" {
  value = provider::corefunc::str_snake("Hello world!")
}

output "test_2" {
  value = provider::corefunc::str_camel("Hello world!")
}

 

required_providers 블록 안에 함수를 정의한 프로바이더(corefunc)의 소스와 버전을 명시하며, 함수를 호출할 때에는 provider::corefunc::str_ooooo("Hello world!")와 같은 형식을 사용합니다. 

 

corefunc::str_snake 함수는 문자열을 소문자로 일괄 변경하고, 공백을 '_'로 변경 및 특수문자를 제거하는 함수입니다. 

corefunc::str_camel 함수는 문자열을 소문자로 변경하되 공백으로 구분되는 따라오는 문자열의 첫 문자는 대문자로 변경하고, 공백과 특수문자를 제거하는 함수입니다. 

 

  • 실행 결과 

$ tofu init && tofu plan && tofu apply -auto-approve

$ tofu output

test_1 = "hello_world"
test_2 = "helloWorld"

 

Loopable import blocks

기존 인프라 형상을 상태 파일로 가져오기 위해 구성 파일에 import 블록을 사용할 수 있습니다. 

특히, 유사한 구성의 리소스를 반복적으로 가져와야 할 때 import 블록에 for_each 구문을 활용하면 효과적입니다. 

 

  • import 블록 사용 예시 

이미 web, app 2개의 인스턴스는 실행중이고 상태 파일은 삭제된 것으로 상황을 가정합니다. 

 

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-05682fce159c49186", "i-07beb1f8c2d00ce36"]
}

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
}

 

인스턴스 ID를 리스트 변수에 담고, import 블록 내에서 for_each와 함께 사용하여 인스턴스를 반복하여 가져옵니다. 

 

  • 실행 결과

$ tofu init -json && tofu apply -auto-approve

Apply complete! Resources: 2 imported, 0 added, 2 changed, 0 destroyed.

$ tofu state ls
data.aws_ami.ubuntu
aws_instance.this[0]
aws_instance.this[1]

 

State file encryption

OpenTofu는 실행 계획과 상태 파일을 암호화하는 기능을 지원합니다. 

 

pbkdf2 키 프로바이더는 로컬 백엔드에서 암호화 기능을 제공합니다.

AES-GCM과 같은 암호화 방식으로 키를 생성하기 위해 길고 복잡한 문자열(passphrase)을 사용합니다. 

 

  • State file encrytion 예시 

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

 

pbkdf2 키 프로바이더는 "ChangeIt_123abcd" 문자열을 사용하여 키를 생성합니다. 

method는 AES-GCM 알고리즘으로 암호화하며, fallback은 암호화 과정에서 문제가 발생할 시 이전 암호화 설정으로 변경 적용하는 구문입니다. 

method.unencrypted.migration은 암호화된 상태 파일을 평문으로 마이그레이션함을 의미합니다. 

 

그리고 암호화되지 않은 환경 변수가 저장되는 것을 차단하기 위해서 enforced=true 옵션으로 조금 더 강화된 보안 정책을 적용할 수 있습니다. 

 

  • 실행 결과 

tfstate encrypted_data

 

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

 

method 방식이 method.unencrypted.migration으로 선언되어 암호화된 상태 파일이 평문으로 복호화됩니다. 

 

  • 실행 결과

tfstate unencrypted

 

State file encryption - AWS KMS

aws_kms 키 프로바이더는 원격 백엔드에서 암호화 기능을 제공합니다. 

암/복호화는 대칭키(Symmetric)를 사용합니다. 

 

  • S3 백엔드 State 암호화 예시 

사전에 S3 버킷과 KMS 키를 생성합니다. 

 

# S3 버킷 생성
$ aws s3 mb s3://okms1017 --region ap-northeast-2
$ aws s3 ls | grep okms1017

# 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=okms1017
$ aws kms create-alias --alias-name alias/$ALIAS_SUFFIX --target-key-id $KEY_ID
$ aws kms list-aliases
$ aws kms list-aliases --query "Aliases[?AliasName=='alias/okms1017'].TargetKeyId" --output text

# CMK로 암호화 테스트 
$ echo "Hello 123123" > secret.txt
$ aws kms encrypt --key-id alias/$ALIAS_SUFFIX --cli-binary-format raw-in-base64-out --plaintext file://secret.txt --output text --query CiphertextBlob | base64 --decode > secret.txt.encrypted

# 암호문 확인
$ cat secret.txt.encrypted

# 복호화 테스트
$ aws kms decrypt --ciphertext-blob fileb://secret.txt.encrypted --output text --query Plaintext | base64 --decode

 

AWS KMS Key

terraform {
  backend "s3" {
    bucket = "okms1017" 
    key = "terraform.tfstate"
    region = "ap-northeast-2"
    encrypt = true
  }

  encryption {
    key_provider "aws_kms" "kms" {
      kms_key_id = "63483d75-ade8-4a0a-8e**********" 
      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
    }
  }
}

 

aws_kms 키 프로바이더는 AES-256 알고리즘을 적용하여 S3 버킷에 저장되는 상태 파일을 암호화합니다. 

 

  • 실행 결과

S3 - terraform.tfstate

S3 버킷의 상태 파일을 로컬로 내려받아 암호화 여부를 확인합니다. 

 

$ aws s3 ls s3://okms1017 --recursive

$ aws s3 cp s3://okms1017/terraform.tfstate .

download: s3://okms1017/terraform.tfstate to ./terraform.tfstate 

 

tfstate encrypted by kms key

 

Removed block 

OpenTofu의 관리 대상에서 인프라의 특정 리소스를 제외하고자 할 때 유용한 방법입니다. 

 

  • Removed block 사용 예시 

우선 Systems Manager Parameter를 생성하고 상태 파일에서 존재 여부를 확인합니다. 

 

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 && tofu apply -auto-approve

$ tofu state ls

data.aws_ami.ubuntu
aws_instance.this[0]
aws_instance.this[1]
aws_ssm_parameter.this[0]
aws_ssm_parameter.this[1]

 

aws ssm parameter

Systems Manager Parameter Store 리소스를 그대로 유지하되, 상태 파일에서만 제거하여 관리 대상에서 제외합니다. 

 

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

 

기존 리소스를 주석처리하고 removed 블록을 선언하여 상태 파일에서 제거합니다. 

 

  • 실행 결과 

$ 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

 

$ tofu state ls

data.aws_ami.ubuntu
aws_instance.this[0]
aws_instance.this[1]

 

aws ssm paramter store

 

Tofu Test

tofu test 명령어를 사용하여 실제 인프라를 생성하고 필수 조건을 충족하는지 구성 파일을 테스트할 수 있습니다. 

테스트가 완료되면 OpenTofu는 생성한 리소스를 삭제합니다. 

 

tofu test 명령어는 *.tftest.hcl 파일 또는 tests 디렉토리 내 파일들을 실행합니다. 

 

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

 

  • test/terraform.tfvars

test = "t101-study-end"

 

  • 결과 확인

$ tofu init && tofu test

tests/main.tftest.hcl... pass
  run "test"... pass
Success! 1 passed, 0 failed.

 

Built-in functions changes

  • 새로 추가된 내장 함수 : templatestring, base64gunzip, cidrcontains, urldecode, issensitive
  • 기능이 변경·추가된 내장 함수 : nonsensitive, templatefile

CLI  changes

  • tofu init : -json 옵션 지원 
  • tofu plan : -concise 옵션 지원(refreshing log 생략 및 플랜 간소화)
  • tofu console : Solaris/AIX 운영체제 지원 
  • XDG 디렉토리 사양 지원 
  • Alias 명령어 
state list state ls
state move state mv
state remove state rm

 

Migrating to OpenTofu 1.7.x from Terraform

Terraform 여러 버전에서 OpenTofu 1.7 버전으로 마이그레이션하는 방법입니다. 링크

 

  • Terraform v1.8.5 ▶ OpenTofu v1.7
# Terraform v1.8.5 설치
$ tenv tf list-remote
$ tenv tf install 1.8.5
$ tenv tf list
$ tenv tf use 1.8.5

$ terraform -version
Terraform v1.8.5
on linux_amd64

 

테라폼으로 실행한 현재 프로바이더의 정보와 상태 파일 정보는 다음과 같습니다. 

 

$ tree .terraform
.terraform
└── providers
    └── registry.terraform.io
        └── hashicorp
            └── aws
                └── 5.61.0
                    └── linux_amd64
                        ├── LICENSE.txt
                        └── terraform-provider-aws_v5.61.0_x5

$ cat terraform.tfstate 

```

      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",

```


마이그레이션 

 

1. 최신 상태 여부 확인

$ terraform plan 

 

2. 상태 파일 및 구성 파일 백업 

$ cp terraform.tfstate ../../backup/terraform.tfstate

$ cp *.tf ../../backup/

 

3. 코드 변경 

A. S3 backend : skip_s3_checksum 옵션 사용 중이면 삭제

                           endpoints  → sso 옵션 또는 AWS_ENDPOINT_URL 환경 변수 사용 중이면 삭제 

B. Removed block : lifecycle 블록 삭제

                                만약 lifecycle 블록에서 destroy=true 설정이 되어 있을 시, 전체 removed 블록을 삭제할 것

C. Testing changes :  mock_provider를 사용 중이면 이 기능없이 동작하도록 재구성 필요 

                      override_resource, override_data, override_module을 사용 중이면 이 기능없이 동작하도록 재구성 필요 

 

4. OpenTofu 초기화 및 플랜 

$ tofu init 

$ tree .terraform 

.terraform
└── providers
    ├── registry.opentofu.org
    │   └── hashicorp
    │       └── aws
    │           └── 5.61.0
    │               └── linux_amd64
    │                   ├── CHANGELOG.md
    │                   ├── LICENSE
    │                   ├── README.md
    │                   └── terraform-provider-aws
    └── registry.terraform.io
        └── hashicorp
            └── aws
                └── 5.61.0
                    └── linux_amd64
                        ├── LICENSE.txt
                        └── terraform-provider-aws_v5.61.0_x5

$ 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-02d67039b2e178dac]
aws_instance.this[1]: Refreshing state... [id=i-0b7bfa7ddc9094e60]

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. Apply 

$ tofu apply -auto-approve

data.aws_ami.ubuntu: Reading...
data.aws_ami.ubuntu: Read complete after 0s [id=ami-0443a434a57db296a]
aws_instance.this[1]: Refreshing state... [id=i-0b7bfa7ddc9094e60]
aws_instance.this[0]: Refreshing state... [id=i-02d67039b2e178dac]

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.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

 

$ diff terraform.tfstate terraform.tfstate.backup

4c4

<   "serial": 4,
---
>   "serial": 3,
12c12
<       "provider": "provider[\"registry.opentofu.org/hashicorp/aws\"]",
---
>       "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
111c111
<       "provider": "provider[\"registry.opentofu.org/hashicorp/aws\"]",
---
>       "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",

 

6. Tag 추가하여 테스트

 
   tags = {
     Name = var.instance_tags[count.index]
     Env = "dev"
   }
 

$ tofu apply -auto-approve 

 

 


[출처]

1) CloudNet@, T1014 실습 스터디

2) https://opentofu.org/

 

OpenTofu

The open source infrastructure as code tool.

opentofu.org

3) https://tofuutils.github.io/tenv/

 

tenv

OpenTofu / Terraform / Terragrunt and Atmos version manager

tofuutils.github.io

728x90