콘텐츠로 이동

OpenTofu

오픈소스 코드형 인프라 도구이자 Terraform의 커뮤니티 주도 포크.

명령어설명
brew install opentofuHomebrew로 macOS에 설치
snap install --classic opentofuSnap으로 Linux에 설치
choco install opentofuChocolatey로 Windows에 설치
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh셸 스크립트로 설치
docker run -it ghcr.io/opentofu/opentofuDocker로 실행
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 -jsonJSON 형식으로 출력 표시
tofu output db_password특정 출력 값 표시
tofu show현재 상태를 사람이 읽을 수 있는 형태로 표시
tofu show -jsonJSON 형식으로 상태 표시
tofu show plan.tfplan저장된 계획 세부 정보 표시
tofu graph리소스 의존성 그래프 생성 (DOT 형식)
tofu graph | dot -Tpng > graph.png의존성 그래프를 PNG로 렌더링
tofu refresh실제 인프라와 상태 동기화
tofu providers설정에서 사용된 프로바이더 목록
# 필수 프로바이더 블록
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"
  }
}
# 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
}
명령어설명
명령어에서 terraformtofu로 교체CLI 명령어가 드롭인 호환
tofu init -upgradeOpenTofu 프로바이더로 재초기화
기존 .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 레지스트리 권장)
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 }}

상태 파일을 저장 시 암호화 — Terraform에서는 사용 불가. PBKDF2, AWS KMS, GCP KMS, Azure Key Vault 키 프로바이더를 지원합니다.

Terraform Cloud의 서버 측 암호화와 달리, OpenTofu는 상태가 머신을 떠나기 전에 암호화하여 제3자가 인프라 상태를 읽을 수 없도록 보장합니다.

OpenTofu는 registry.opentofu.org에 자체 프로바이더 및 모듈 레지스트리를 유지하여 HashiCorp의 라이선스 결정과 독립적인 장기 가용성을 보장합니다.

  1. 잠금이 있는 원격 상태 사용 — S3 + DynamoDB 백엔드(또는 동등한 것)를 설정하여 동시 수정을 방지하고 팀 협업을 지원합니다.

  2. 상태 암호화 활성화 — OpenTofu의 독점 암호화 기능을 KMS 또는 PBKDF2와 함께 사용하여 상태 파일의 민감한 데이터를 보호합니다.

  3. 프로바이더 및 모듈 버전 고정~> 제약 조건(예: ~> 5.0)을 사용하여 패치 업데이트는 허용하면서 주요 변경을 방지합니다.

  4. 환경별 워크스페이스 또는 디렉토리 분리 사용 — dev, staging, production 상태를 분리하여 환경 간 실수로 인한 변경을 방지합니다.

  5. 재사용 가능한 패턴을 위한 모듈 작성 — 일반적인 패턴(VPC, 데이터베이스, Kubernetes 클러스터)을 명확한 입력과 출력이 있는 모듈로 추출합니다.

  6. 제약 조건으로 변수 유효성 검사 — 변수에 validation 블록을 추가하여 잘못된 값이 클라우드 프로바이더에 도달하기 전에 잡습니다.

  7. 적용 전 계획 저장tofu plan -out=plan.tfplantofu apply plan.tfplan을 사용하여 검토한 것과 정확히 동일한 것을 적용합니다.

  8. 프로바이더에 default_tags 사용 — 프로바이더 수준에서 조직 전체 태그(ManagedBy, Environment, Project)를 설정하여 일관된 태깅을 보장합니다.

  9. 기존 리소스 가져오기tofu import를 사용하여 관리되지 않는 인프라를 재생성하지 않고 OpenTofu 제어 하에 가져옵니다.

  10. CI에서 tofu fmt -check 실행 — 포맷되지 않은 파일에 CI 빌드를 실패시켜 모든 팀원 간 일관된 포맷팅을 시행합니다.

  11. 외부 참조에 데이터 소스 사용 — ID를 하드코딩하는 대신 data 블록으로 기존 인프라(AMI, VPC, DNS 영역)를 참조합니다.

  12. 계획을 신중하게 검토 — 적용 전에 항상 계획 출력을 읽습니다. 다운타임을 유발할 수 있는 destroyreplace 작업에 주의합니다.