コンテンツにスキップ

OpenTofu

オープンソースのInfrastructure as Codeツールで、Terraformのコミュニティ主導フォーク。

プラットフォームインストール

Section titled “プラットフォームインストール”
コマンド説明
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利用可能なコマンドを表示

バージョンマネージャーインストール

Section titled “バージョンマネージャーインストール”
# 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後でapplyするためにプランをファイルに保存
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設定で使用されているプロバイダーを一覧表示
# 必要なプロバイダーブロック
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リモート状態を標準出力に取得
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キープロバイダーをサポート。

クライアントサイド状態暗号化

Section titled “クライアントサイド状態暗号化”

Terraform Cloudのサーバーサイド暗号化とは異なり、OpenTofuはマシンを離れる前に状態を暗号化し、サードパーティがインフラストラクチャの状態を読み取れないことを保証します。

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.tfplanを使い、次にtofu 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アクションに注意します。