OpenTofu
オープンソースのInfrastructure as Codeツールで、Terraformのコミュニティ主導フォーク。
インストール
Section titled “インストール”プラットフォームインストール
Section titled “プラットフォームインストール”| コマンド | 説明 |
|---|---|
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 | 利用可能なコマンドを表示 |
バージョンマネージャーインストール
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
コアコマンド
Section titled “コアコマンド”ワークフローコマンド
Section titled “ワークフローコマンド”| コマンド | 説明 |
|---|---|
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 | 特定のリソースのみを破棄 |
検査コマンド
Section titled “検査コマンド”| コマンド | 説明 |
|---|---|
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)
Section titled “設定(HCL)”プロバイダー設定
Section titled “プロバイダー設定”# 必要なプロバイダーブロック
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"
}
リソース定義
Section titled “リソース定義”# 基本リソース
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 { ... } } | カスタムバリデーション付き変数 |
変数の渡し方
Section titled “変数の渡し方”| コマンド | 説明 |
|---|---|
tofu plan -var="name=value" | CLI経由で変数を渡す |
tofu plan -var-file="prod.tfvars" | ファイルから変数を渡す |
TF_VAR_name=value tofu plan | 環境変数経由で渡す |
terraform.tfvarsファイルを作成 | 自動読み込みの変数値 |
*.auto.tfvarsファイルを作成 | 自動読み込みの変数ファイル |
変数と出力の例
Section titled “変数と出力の例”# 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
}
変数ファイル
Section titled “変数ファイル”# 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
}
状態コマンド
Section titled “状態コマンド”| コマンド | 説明 |
|---|---|
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 | スタックした状態ロックを解除 |
バックエンド設定
Section titled “バックエンド設定”# 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独自機能)
Section titled “状態暗号化(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
}
}
}
モジュール使用
Section titled “モジュール使用”| コマンド | 説明 |
|---|---|
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 | すべてのモジュールを強制更新 |
モジュール構造
Section titled “モジュール構造”modules/
└── vpc/
├── main.tf # リソース
├── variables.tf # 入力変数
├── outputs.tf # 出力値
├── versions.tf # プロバイダー要件
└── README.md # 使用ドキュメント
モジュールの例
Section titled “モジュールの例”# 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からの移行
Section titled “Terraformからの移行”ドロップイン互換性
Section titled “ドロップイン互換性”| コマンド | 説明 |
|---|---|
コマンドのterraformをtofuに置換 | CLIコマンドはドロップイン互換 |
tofu init -upgrade | OpenTofuプロバイダーで再初期化 |
既存の.tfファイルはそのまま保持 | HCL構文は完全互換 |
既存の.tfstateファイルはそのまま保持 | 状態フォーマットは互換 |
既存の.terraform.lock.hclはそのまま保持 | ロックファイルは互換 |
CI/CDをopentofu/setup-opentofuアクションに更新 | GitHub Actionsの切り替え |
移行チェックリスト
Section titled “移行チェックリスト”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
Section titled “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独自機能
Section titled “OpenTofu独自機能”状態ファイルを保存時に暗号化 — Terraformでは利用不可。PBKDF2、AWS KMS、GCP KMS、Azure Key Vaultキープロバイダーをサポート。
クライアントサイド状態暗号化
Section titled “クライアントサイド状態暗号化”Terraform Cloudのサーバーサイド暗号化とは異なり、OpenTofuはマシンを離れる前に状態を暗号化し、サードパーティがインフラストラクチャの状態を読み取れないことを保証します。
レジストリ独立性
Section titled “レジストリ独立性”OpenTofuはregistry.opentofu.orgに独自のプロバイダーとモジュールレジストリを維持し、HashiCorpのライセンス決定に依存しない長期的な可用性を確保しています。
ベストプラクティス
Section titled “ベストプラクティス”-
ロック付きのリモート状態を使用 — 同時変更を防ぎチームコラボレーションを可能にするために、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アクションに注意します。