【OpenTofu 1.12】Terraformが10年間出荷しなかった prevent_destroy 動的設定がついに実現 | IaC モジュール設計

IaC & Automation

この記事の概要

2026年5月14日にリリースされた OpenTofu v1.12.0 では、IaC エンジニアが長年求め続けた「prevent_destroy の動的設定」がついに実現しました。
Terraform 0.7(2016年)から10年間要望されながらHashiCorpが実装しなかった機能で、共有モジュールの本番/開発環境分岐問題を根本的に解決します。
あわせてプロバイダチェックサム管理の改善と、人間用・機械用の出力同時出力フラグも追加されています。


prevent_destroy を変数で制御できる — prod/dev 問題がこれで解消する

OpenTofu 1.12 の最大のアップデートは、lifecycle ブロック内の prevent_destroy 引数に変数や他のシンボルを参照できるようになったことです。

従来の Terraform / OpenTofu では、prevent_destroy = true と書いた場合、その設定はコード上にハードコードされたままでした。
本番環境では誤削除防止のために true にしたい、でも開発環境では気軽にリソースを作り直したい——この当たり前の要求が、共有モジュールで使おうとした瞬間に詰まっていました。

# ❌ 従来:ハードコードしかできなかった
resource "aws_db_instance" "main" {
  # ...
  lifecycle {
    prevent_destroy = true  # devでも本番でも問答無用でtrue
  }
}

OpenTofu 1.12 からは以下のように書けます:

# ✅ OpenTofu 1.12:変数で動的に制御できる
variable "prevent_destroy_db" {
  type    = bool
  default = true
}

resource "aws_db_instance" "main" {
  # ...
  lifecycle {
    prevent_destroy = var.prevent_destroy_db
  }
}

モジュール利用側は環境ごとに値を渡すだけです:

# 本番環境 (prod/main.tf)
module "database" {
  source              = "../../modules/rds"
  prevent_destroy_db  = true   # 本番は守る
}

# 開発環境 (dev/main.tf)
module "database" {
  source              = "../../modules/rds"
  prevent_destroy_db  = false  # devは自由に壊せる
}

コードの分岐もなく、モジュールの fork も不要。
ひとつの共有モジュールが本番・ステージング・開発すべてに対応できるようになります。


なぜ Terraform は10年間この機能を出荷できなかったのか

prevent_destroy に変数を使いたいという Issue は、Terraform 0.7 がリリースされた 2016年 に最初に報告されています(Issue #30957)。
約10年間、このリクエストは放置されてきました。

HashiCorp 側の公式な説明は「lifecycle ブロックは plan/apply のごく初期フェーズで評価されるため、動的な値を参照するには評価タイミングのアーキテクチャ変更が必要」というものでした。
設計上の制約はあったにせよ、優先度が上がらないまま時が流れていったという側面も否めません。

一方 OpenTofu は、Linux Foundation 配下のオープンガバナンスのもとで コミュニティ投票によって優先度が決まります
Issue #1329 として傘(umbrella)Issue が立てられ、prevent_destroy は「最も設計上の課題が少ない」という判断から v1.12 でトップバッターとして実装されました。

この一件は、単なる機能追加以上の意味を持ちます。
Terraform と OpenTofu の 技術的ギャップが「追いつく」フェーズから「追い越す」フェーズに移行したことを示しているからです。

OpenTofu 1.11 で導入されたエフェメラル値(ephemeral values)、1.7 で実装されたネイティブ State 暗号化に続き、1.12 の動的 prevent_destroy により、OpenTofu の機能セットは Terraform の上位互換ではなく独自の進化を遂げています。


実際に OpenTofu 1.12 の新機能を使い倒す

実装例・コード例

① 動的 prevent_destroy の実用パターン

マルチ環境対応モジュールの典型構成です:

# modules/rds/variables.tf
variable "prevent_destroy" {
  type        = bool
  default     = true
  description = "本番環境では true に設定してください"
}

variable "env" {
  type = string
}
# modules/rds/main.tf
resource "aws_db_instance" "this" {
  identifier           = "app-db-${var.env}"
  engine               = "postgres"
  instance_class       = "db.t3.medium"
  allocated_storage    = 20
  db_name              = "appdb"
  username             = "admin"
  password             = var.db_password
  skip_final_snapshot  = !var.prevent_destroy

  lifecycle {
    prevent_destroy = var.prevent_destroy  # ← ここがポイント
  }
}

skip_final_snapshotprevent_destroy と連動させることで、本番は最終スナップショットを取り、dev はスキップするという挙動も一元管理できます。

② -json-into フラグで CI/CD の可読性を両立する

OpenTofu 1.12 では、-json-into=<ファイル名> オプションが追加されました。
これにより、ターミナルには人間が読みやすい通常出力を表示しながら、同時に CI/CD パイプライン向けの JSON ログをファイルに書き出せます:

# plan の結果を JSON と人間用出力で同時に取得
tofu plan -json-into=/tmp/plan_output.json -out=tfplan

# named pipe を使ってリアルタイムに後続処理へ渡す例
mkfifo /tmp/tofu_json_pipe
cat /tmp/tofu_json_pipe | jq '.changes | select(.action == "destroy")' &
tofu apply -json-into=/tmp/tofu_json_pipe tfplan

GitHub Actions での活用イメージ:

# .github/workflows/tofu-plan.yml
- name: OpenTofu Plan
  run: |
    tofu plan \
      -json-into=plan_result.json \
      -out=tfplan \
      -var="env=prod" \
      -var="prevent_destroy_db=true"

- name: Parse Plan Result
  run: |
    # 削除対象リソースがあれば PR にコメント
    DESTROYS=$(jq '[.[] | select(.type=="planned_change" and .change.action=="delete")] | length' plan_result.json)
    echo "削除予定リソース数: ${DESTROYS}"

③ プロバイダチェックサムの自動補完

以前は tofu init 後に tofu providers lock を別途実行しないと、グローバルキャッシュや local mirror 環境でチェックサムエラーが発生していました。

OpenTofu 1.12 からは tofu init だけで zh:h1: 両方のハッシュが .terraform.lock.hcl に自動追記されます:

# .terraform.lock.hcl(OpenTofu 1.12以降の自動生成)
provider "registry.opentofu.org/hashicorp/aws" {
  version = "5.98.0"
  hashes = [
    "h1:abc123...",  # ← 1.12から自動追加されるh1:ハッシュ
    "zh:def456...",
    "zh:ghi789...",
  ]
}

チームで共有キャッシュを使っている場合、この改善による tofu providers lock の省略効果は CI/CD の実行時間短縮にも直結します。

実務での活用シナリオ

シナリオ A:マルチアカウント AWS 構成

environments/
├── prod/
│   ├── main.tf       # prevent_destroy = true
│   └── terraform.tfvars
├── staging/
│   ├── main.tf       # prevent_destroy = true(ステージも守る)
│   └── terraform.tfvars
└── dev/
    ├── main.tf       # prevent_destroy = false
    └── terraform.tfvars

modules/
└── rds/
    ├── main.tf       # 共通モジュール(変数で制御)
    └── variables.tf

モジュールを一切変更せず、呼び出し側の変数指定だけで保護レベルを切り替えられます。

シナリオ B:Atlantis + OpenTofu の CI/CD パイプライン

Atlantis でプルリクごとに tofu plan を走らせる場合、-json-into で plan の JSON ログを S3 に保存してダッシュボード化する、という構成が現実的になります。
従来は -json を使うと人間向けのログが消えてしまうため、Atlantis の PR コメント生成と JSON 解析の両立が難しい状況でした。


OpenTofu 1.12 への移行は今がちょうどいい

OpenTofu は Terraform との HCL 互換性を維持しており、既存の .tf ファイルはほぼそのまま動作します。
移行ステップは大きく3つです。

ステップ 1:バイナリの差し替え

# Homebrew (macOS)
brew install opentofu

# Linux (公式インストーラ)
curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opentofu.sh | sh

# バージョン確認
tofu version
# OpenTofu v1.12.0

ステップ 2:State の互換確認

# 既存の terraform.tfstate はそのまま利用可能
# backend 設定を変更する必要はない
tofu init
tofu plan  # エラーがないことを確認

ステップ 3:prevent_destroy を段階的に動的化

既存コードで prevent_destroy = true をハードコードしている箇所を確認し、共有モジュール化のタイミングで変数に切り出します。
既存の prevent_destroy = true は OpenTofu 1.12 でもそのまま動作するため、一度にすべて変更する必要はありません

また、OpenTofu は Linux Foundation 配下のガバナンスで運営されており、BSL(Business Source License)の制約を受けません。
社内ポリシーや商用利用のライセンス観点でも Terraform より自由度が高い選択肢です。


まとめ

OpenTofu 1.12 は「機能キャッチアップ」ではなく「Terraform を超えた進化」を示すリリースです。

  • prevent_destroy の動的設定:共有モジュールで本番/開発の保護レベルをコード分岐なしに管理できる
  • プロバイダチェックサム自動補完tofu providers lock の手動実行が不要になり CI/CD がシンプルに
  • -json-into フラグ:人間用ログと機械用ログを同時出力でき、可観測性と可読性を両立
  • destroy = false ライフサイクル:State からリソースを除外するだけで実物を壊さない柔軟な運用が可能

10年越しの要望を実現した prevent_destroy の動的化は、TerraformからOpenTofuへ切り替えを検討する理由として十分な価値があります。
移行コストは低く、得られるメリットは大きい——今がちょうど移行のタイミングです。


参考リンク

コメント