OpenTofu
오픈소스 코드형 인프라 도구이자 Terraform의 커뮤니티 주도 포크.
플랫폼별 설치
섹션 제목: “플랫폼별 설치”| 명령어 | 설명 |
|---|---|
brew install opentofu | Homebrew로 macOS에 설치 |
snap install --classic opentofu | Snap으로 Linux에 설치 |
choco install opentofu | Chocolatey로 Windows에 설치 |
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh | 셸 스크립트로 설치 |
docker run -it ghcr.io/opentofu/opentofu | Docker로 실행 |
tofu --version | 설치된 버전 표시 |
tofu -help | 사용 가능한 명령어 표시 |
버전 관리자 설치
섹션 제목: “버전 관리자 설치”# tofuenv 사용 (OpenTofu용 tfenv과 유사)
git clone https://github.com/tofuutils/tofuenv.git ~/.tofuenv
echo 'export PATH="$HOME/.tofuenv/bin:$PATH"' >> ~/.bashrc
# 사용 가능한 버전 목록
tofuenv list-remote
# 특정 버전 설치
tofuenv install 1.8.0
tofuenv use 1.8.0
# 프로젝트에서 버전 고정
echo "1.8.0" > .opentofu-version
핵심 명령어
섹션 제목: “핵심 명령어”워크플로우 명령어
섹션 제목: “워크플로우 명령어”| 명령어 | 설명 |
|---|---|
tofu init | 작업 디렉토리 초기화 및 프로바이더 다운로드 |
tofu init -upgrade | 재초기화 및 프로바이더/모듈 버전 업그레이드 |
tofu init -backend-config=prod.hcl | 백엔드 설정 파일로 초기화 |
tofu plan | 인프라 변경 사항 미리보기 |
tofu plan -out=plan.tfplan | 나중에 적용할 계획을 파일에 저장 |
tofu plan -target=aws_instance.web | 특정 리소스만 변경 계획 |
tofu plan -var-file=prod.tfvars | 특정 변수 파일로 계획 |
tofu apply | 인프라 변경 적용 (확인 프롬프트) |
tofu apply -auto-approve | 확인 없이 변경 적용 |
tofu apply plan.tfplan | 저장된 계획 파일 적용 |
tofu destroy | 모든 관리 인프라 삭제 |
tofu destroy -auto-approve | 확인 없이 삭제 |
tofu destroy -target=aws_instance.web | 특정 리소스만 삭제 |
검사 명령어
섹션 제목: “검사 명령어”| 명령어 | 설명 |
|---|---|
tofu validate | 설정 구문 유효성 검사 |
tofu fmt | 설정 파일 포맷팅 |
tofu fmt -check | 파일 포맷팅 확인 (CI 친화적) |
tofu fmt -recursive | 하위 디렉토리의 모든 파일 포맷팅 |
tofu output | 모든 출력 값 표시 |
tofu output -json | JSON 형식으로 출력 표시 |
tofu output db_password | 특정 출력 값 표시 |
tofu show | 현재 상태를 사람이 읽을 수 있는 형태로 표시 |
tofu show -json | JSON 형식으로 상태 표시 |
tofu show plan.tfplan | 저장된 계획 세부 정보 표시 |
tofu graph | 리소스 의존성 그래프 생성 (DOT 형식) |
tofu graph | dot -Tpng > graph.png | 의존성 그래프를 PNG로 렌더링 |
tofu refresh | 실제 인프라와 상태 동기화 |
tofu providers | 설정에서 사용된 프로바이더 목록 |
설정 (HCL)
섹션 제목: “설정 (HCL)”프로바이더 설정
섹션 제목: “프로바이더 설정”# 필수 프로바이더 블록
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.5"
}
}
# 상태 암호화 (OpenTofu 독점 기능)
encryption {
method "aes_gcm" "default" {
keys = key_provider.pbkdf2.mykey
}
state {
method = method.aes_gcm.default
enforced = true
}
plan {
method = method.aes_gcm.default
enforced = true
}
key_provider "pbkdf2" "mykey" {
passphrase = var.state_passphrase
}
}
}
# 기본 설정이 있는 프로바이더
provider "aws" {
region = "us-east-1"
default_tags {
tags = {
ManagedBy = "opentofu"
Environment = var.environment
Project = var.project_name
}
}
}
# 다중 리전용 프로바이더 별칭
provider "aws" {
alias = "west"
region = "us-west-2"
}
# 별칭 프로바이더를 리소스에 사용
resource "aws_instance" "west_server" {
provider = aws.west
ami = "ami-0abcdef1234567890"
instance_type = "t3.micro"
}
리소스 정의
섹션 제목: “리소스 정의”# 기본 리소스
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = {
Name = "${var.project_name}-web"
}
}
# 데이터 소스 (기존 리소스 읽기)
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
# 로컬 값
locals {
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "opentofu"
}
az_count = length(data.aws_availability_zones.available.names)
}
# count와 for_each
resource "aws_subnet" "public" {
count = local.az_count
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = merge(local.common_tags, {
Name = "${var.project_name}-public-${count.index}"
Type = "public"
})
}
resource "aws_security_group_rule" "ingress" {
for_each = {
http = { port = 80, cidrs = ["0.0.0.0/0"] }
https = { port = 443, cidrs = ["0.0.0.0/0"] }
ssh = { port = 22, cidrs = [var.admin_cidr] }
}
type = "ingress"
from_port = each.value.port
to_port = each.value.port
protocol = "tcp"
cidr_blocks = each.value.cidrs
security_group_id = aws_security_group.main.id
}
변수 & 출력
섹션 제목: “변수 & 출력”변수 정의
섹션 제목: “변수 정의”| 명령어 | 설명 |
|---|---|
variable "name" { type = string } | 문자열 변수 정의 |
variable "port" { type = number default = 8080 } | 기본값이 있는 변수 |
variable "tags" { type = map(string) } | 맵 변수 |
variable "cidrs" { type = list(string) } | 리스트 변수 |
variable "config" { type = object({ ... }) } | 구조를 가진 객체 변수 |
variable "db_pass" { sensitive = true } | 민감한 변수로 표시 (출력에서 숨김) |
variable "env" { validation { ... } } | 커스텀 유효성 검사가 있는 변수 |
변수 전달
섹션 제목: “변수 전달”| 명령어 | 설명 |
|---|---|
tofu plan -var="name=value" | CLI로 변수 전달 |
tofu plan -var-file="prod.tfvars" | 파일에서 변수 전달 |
TF_VAR_name=value tofu plan | 환경 변수로 전달 |
terraform.tfvars 파일 생성 | 자동 로드 변수 값 |
*.auto.tfvars 파일 생성 | 자동 로드 변수 파일 |
변수 & 출력 예제
섹션 제목: “변수 & 출력 예제”# variables.tf
variable "environment" {
type = string
description = "Deployment environment"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_config" {
type = object({
instance_type = string
volume_size = number
encrypted = bool
})
default = {
instance_type = "t3.micro"
volume_size = 20
encrypted = true
}
}
variable "database_password" {
type = string
sensitive = true
}
# outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "web_url" {
description = "URL of the web server"
value = "https://${aws_lb.web.dns_name}"
}
output "db_connection_string" {
description = "Database connection string"
value = "postgresql://${var.db_username}:${var.database_password}@${aws_db_instance.main.endpoint}/${var.db_name}"
sensitive = true
}
변수 파일
섹션 제목: “변수 파일”# prod.tfvars
environment = "prod"
instance_config = {
instance_type = "t3.large"
volume_size = 100
encrypted = true
}
# dev.tfvars
environment = "dev"
instance_config = {
instance_type = "t3.micro"
volume_size = 20
encrypted = false
}
상태 관리
섹션 제목: “상태 관리”상태 명령어
섹션 제목: “상태 명령어”| 명령어 | 설명 |
|---|---|
tofu state list | 상태의 모든 리소스 목록 |
tofu state show aws_instance.web | 특정 리소스 세부 정보 표시 |
tofu state mv aws_instance.old aws_instance.new | 상태에서 리소스 이름 변경 |
tofu state rm aws_instance.web | 상태에서 리소스 제거 (실제 인프라는 유지) |
tofu state pull | 원격 상태를 stdout으로 가져오기 |
tofu state push | 로컬 상태를 원격 백엔드로 푸시 |
tofu import aws_instance.web i-1234567 | 기존 리소스를 상태로 가져오기 |
tofu state replace-provider hashicorp/aws registry.opentofu.org/hashicorp/aws | 상태에서 프로바이더 교체 |
tofu force-unlock LOCK_ID | 멈춘 상태 잠금 해제 |
백엔드 설정
섹션 제목: “백엔드 설정”# S3 백엔드 (가장 일반적)
terraform {
backend "s3" {
bucket = "my-tofu-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tofu-locks"
encrypt = true
}
}
# PostgreSQL 백엔드
terraform {
backend "pg" {
conn_str = "postgres://user:pass@db.example.com/tofu_state?sslmode=require"
}
}
# HTTP 백엔드 (GitLab, Terraform Cloud 대안과 호환)
terraform {
backend "http" {
address = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod"
lock_address = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod/lock"
unlock_address = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod/lock"
username = "gitlab-ci-token"
password = var.ci_token
}
}
# 로컬 백엔드 (기본, 개발용)
terraform {
backend "local" {
path = "terraform.tfstate"
}
}
상태 암호화 (OpenTofu 독점)
섹션 제목: “상태 암호화 (OpenTofu 독점)”# AWS KMS를 사용한 상태 암호화
terraform {
encryption {
key_provider "aws_kms" "main" {
kms_key_id = "arn:aws:kms:us-east-1:123456789:key/abcd-1234"
region = "us-east-1"
key_spec = "AES_256"
}
method "aes_gcm" "default" {
keys = key_provider.aws_kms.main
}
state {
method = method.aes_gcm.default
enforced = true
}
}
}
모듈 사용
섹션 제목: “모듈 사용”| 명령어 | 설명 |
|---|---|
module "vpc" { source = "./modules/vpc" } | 로컬 모듈 사용 |
module "vpc" { source = "github.com/org/repo//modules/vpc" } | GitHub 모듈 사용 |
module "vpc" { source = "registry.opentofu.org/..." } | OpenTofu 레지스트리 모듈 사용 |
tofu get | 모듈 다운로드 및 업데이트 |
tofu get -update | 모든 모듈 강제 업데이트 |
모듈 구조
섹션 제목: “모듈 구조”modules/
└── vpc/
├── main.tf # 리소스
├── variables.tf # 입력 변수
├── outputs.tf # 출력 값
├── versions.tf # 프로바이더 요구사항
└── README.md # 사용법 문서
모듈 예제
섹션 제목: “모듈 예제”# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = merge(var.tags, { Name = var.name })
}
resource "aws_subnet" "public" {
count = length(var.public_subnets)
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnets[count.index]
availability_zone = var.azs[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.name}-public-${count.index}"
})
}
# modules/vpc/variables.tf
variable "name" { type = string }
variable "cidr" { type = string }
variable "azs" { type = list(string) }
variable "public_subnets" { type = list(string) }
variable "tags" { type = map(string) default = {} }
# modules/vpc/outputs.tf
output "vpc_id" { value = aws_vpc.this.id }
output "public_subnet_ids" { value = aws_subnet.public[*].id }
# 모듈 사용
module "vpc" {
source = "./modules/vpc"
name = "production"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
tags = local.common_tags
}
Terraform에서 마이그레이션
섹션 제목: “Terraform에서 마이그레이션”드롭인 호환성
섹션 제목: “드롭인 호환성”| 명령어 | 설명 |
|---|---|
명령어에서 terraform을 tofu로 교체 | CLI 명령어가 드롭인 호환 |
tofu init -upgrade | OpenTofu 프로바이더로 재초기화 |
기존 .tf 파일 변경 없이 유지 | HCL 구문이 완전히 호환 |
기존 .tfstate 파일 유지 | 상태 형식이 호환 |
기존 .terraform.lock.hcl 유지 | 잠금 파일이 호환 |
CI/CD에서 opentofu/setup-opentofu 액션 사용으로 업데이트 | GitHub Actions 전환 |
마이그레이션 체크리스트
섹션 제목: “마이그레이션 체크리스트”Terraform에서 OpenTofu로 마이그레이션:
1. Terraform과 함께 OpenTofu 설치
2. 프로젝트 디렉토리에서 실행: tofu init
3. 실행: tofu plan
- terraform plan 출력과 비교
- 동일해야 함 (변경 없음)
4. CI/CD 파이프라인에서 'terraform'을 'tofu'로 교체
5. GitHub Actions 업데이트:
- 이전: hashicorp/setup-terraform
- 신규: opentofu/setup-opentofu
6. 잠금 파일 프로바이더 소스 업데이트 (필요시):
- tofu providers lock
7. 상태 암호화 활성화 (OpenTofu 독점):
- terraform {} 블록에 encryption 블록 추가
8. 모듈 레지스트리 참조 전환:
- registry.terraform.io → registry.opentofu.org
- (둘 다 작동하지만 OpenTofu 레지스트리 권장)
GitHub Actions CI/CD
섹션 제목: “GitHub Actions CI/CD”name: OpenTofu Plan & Apply
on:
push:
branches: [main]
pull_request:
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: opentofu/setup-opentofu@v1
with:
tofu_version: 1.8.0
- name: Init
run: tofu init
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Plan
run: tofu plan -out=plan.tfplan -no-color
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Apply (main only)
if: github.ref == 'refs/heads/main'
run: tofu apply -auto-approve plan.tfplan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
OpenTofu 독점 기능
섹션 제목: “OpenTofu 독점 기능”상태 암호화
섹션 제목: “상태 암호화”상태 파일을 저장 시 암호화 — Terraform에서는 사용 불가. PBKDF2, AWS KMS, GCP KMS, Azure Key Vault 키 프로바이더를 지원합니다.
클라이언트 측 상태 암호화
섹션 제목: “클라이언트 측 상태 암호화”Terraform Cloud의 서버 측 암호화와 달리, OpenTofu는 상태가 머신을 떠나기 전에 암호화하여 제3자가 인프라 상태를 읽을 수 없도록 보장합니다.
레지스트리 독립성
섹션 제목: “레지스트리 독립성”OpenTofu는 registry.opentofu.org에 자체 프로바이더 및 모듈 레지스트리를 유지하여 HashiCorp의 라이선스 결정과 독립적인 장기 가용성을 보장합니다.
모범 사례
섹션 제목: “모범 사례”-
잠금이 있는 원격 상태 사용 — S3 + DynamoDB 백엔드(또는 동등한 것)를 설정하여 동시 수정을 방지하고 팀 협업을 지원합니다.
-
상태 암호화 활성화 — OpenTofu의 독점 암호화 기능을 KMS 또는 PBKDF2와 함께 사용하여 상태 파일의 민감한 데이터를 보호합니다.
-
프로바이더 및 모듈 버전 고정 —
~>제약 조건(예:~> 5.0)을 사용하여 패치 업데이트는 허용하면서 주요 변경을 방지합니다. -
환경별 워크스페이스 또는 디렉토리 분리 사용 — dev, staging, production 상태를 분리하여 환경 간 실수로 인한 변경을 방지합니다.
-
재사용 가능한 패턴을 위한 모듈 작성 — 일반적인 패턴(VPC, 데이터베이스, Kubernetes 클러스터)을 명확한 입력과 출력이 있는 모듈로 추출합니다.
-
제약 조건으로 변수 유효성 검사 — 변수에
validation블록을 추가하여 잘못된 값이 클라우드 프로바이더에 도달하기 전에 잡습니다. -
적용 전 계획 저장 —
tofu plan -out=plan.tfplan후tofu apply plan.tfplan을 사용하여 검토한 것과 정확히 동일한 것을 적용합니다. -
프로바이더에
default_tags사용 — 프로바이더 수준에서 조직 전체 태그(ManagedBy, Environment, Project)를 설정하여 일관된 태깅을 보장합니다. -
기존 리소스 가져오기 —
tofu import를 사용하여 관리되지 않는 인프라를 재생성하지 않고 OpenTofu 제어 하에 가져옵니다. -
CI에서
tofu fmt -check실행 — 포맷되지 않은 파일에 CI 빌드를 실패시켜 모든 팀원 간 일관된 포맷팅을 시행합니다. -
외부 참조에 데이터 소스 사용 — ID를 하드코딩하는 대신
data블록으로 기존 인프라(AMI, VPC, DNS 영역)를 참조합니다. -
계획을 신중하게 검토 — 적용 전에 항상 계획 출력을 읽습니다. 다운타임을 유발할 수 있는
destroy와replace작업에 주의합니다.