0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terraform planでAI生成IaCの削除・置換事故を止める7チェック — apply前レビューの実務テンプレ

0
Posted at

Terraform planでAI生成IaCの削除・置換事故を止める7チェック — apply前レビューの実務テンプレ

更新日: 2026-07-25

AIにTerraformを書いてもらうと、10分かかっていた記述が1分で出てきます。

でも、コード差分が小さいことと、インフラ変更が小さいことは同じではありません。数行の変更でデータベースが置換される。名前変更のつもりが削除と再作成になる。レビューでHCLだけ眺めていると、ここを見落とします。

結論はシンプルです。AIが書いたHCLではなく、Terraformが計算したplanをapply前に検査します。

この記事では、fmt、validate、終了コード、削除・置換、変更数、秘密情報、人間承認の7チェックを実務テンプレにします。AIは差分説明を手伝いますが、applyの承認は人間が持ちます。

最初に3つの用語だけ

  • IaC: サーバーやネットワークなどの構成をコードで管理する考え方です。
  • plan: 現在の状態とコードを比較し、applyしたら何が変わるかを計算した結果です。
  • replace: 既存リソースを削除して、新しく作り直す変更です。名前や識別子が変わるだけでも起きます。

僕はapply前の境界を 「適用前レッドライン」 と呼びます。機械判定できる危険と、人間だけが判断できる許容損失を分け、apply前で止める境界です。

人間、AI、決定的ツールの役割

担当 任せること 任せないこと
人間 消えてよい資源、許容停止時間、例外、apply承認 JSONの単純集計
AI plan要約、確認質問、代替案の草案 生planの外部送信、最終承認
Terraform/スクリプト 構文、差分、件数、delete/replaceの検出 変更が業務上妥当か

「危険そう」はAIの感想です。一方、deleteを含むかは決定的に判定できます。先に機械で止め、そのあと人間が理由を読む。この順番が大事です。

10分の初回成功: replaceを終了コード1で止める

Terraform環境がなくても試せるダミーplan JSONを使います。実際のplan JSONには秘密が含まれ得るため、最初は必ずダミーで動かしてください。

{
  "resource_changes": [
    {
      "address": "aws_db_instance.example",
      "change": {
        "actions": ["delete", "create"]
      }
    },
    {
      "address": "aws_s3_bucket.logs",
      "change": {
        "actions": ["update"]
      }
    }
  ]
}

次のチェッカーは、deleteまたはdeletecreateを見つけたら停止します。

#!/usr/bin/env python3
import json
import sys
from pathlib import Path

BLOCKING_ACTIONS = {"delete"}

def inspect_plan(path: str) -> int:
    plan = json.loads(Path(path).read_text(encoding="utf-8"))
    blocked = []

    for item in plan.get("resource_changes", []):
        actions = item.get("change", {}).get("actions", [])
        if BLOCKING_ACTIONS.intersection(actions):
            blocked.append({
                "address": item.get("address", "(unknown)"),
                "actions": actions,
            })

    if blocked:
        print(json.dumps({"status": "blocked", "changes": blocked},
                         ensure_ascii=False, indent=2))
        return 1

    print('{"status":"ok"}')
    return 0

if __name__ == "__main__":
    raise SystemExit(inspect_plan(sys.argv[1]))
python3 check_plan.py sample-plan.json
echo $?

aws_db_instance.example["delete", "create"]が表示され、終了コード1になれば初回成功です。理解ではなく、危険変更を一度止められた状態。ここまでなら10分で進められます。

チェック1: fmtで読みやすい差分にする

terraform fmt -check -recursive

terraform fmtはTerraformコードを標準形式へ整えます。これは安全性そのものではありません。ただ、無関係な空白差分を減らすと、本当に読むべき変更へ注意を使えます。

AIに整形させる必要はありません。決定的なformatterに任せます。

チェック2: validateで構文と内部整合性を確かめる

terraform init -backend=false
terraform validate

validateは構文や引数、属性名、型などの内部整合性を検査します。公式ドキュメントも、remote stateやprovider APIなどの外部サービスまでは検証しないと説明しています。

つまり、validate成功は「本番で安全」の証明ではありません。文法試験に通った状態です。

チェック3: planの終了コードを3値で扱う

set +e
terraform plan -detailed-exitcode -out=tfplan
code=$?
set -e

case "$code" in
  0) echo "変更なし" ;;
  1) echo "planエラー"; exit 1 ;;
  2) echo "変更あり。レビューへ進む" ;;
  *) echo "未知の終了コード"; exit 1 ;;
esac

-detailed-exitcodeでは、0が変更なし、1がエラー、2が変更ありです。 2は失敗ではありません。 ここを普通の成功/失敗だけで処理すると、変更のある正常planを誤って落とします。

なお、-out=tfplanで保存したplanファイルには、画面上で隠される値も平文で含まれ得ます。artifactへ無期限保存したり、チャットへ添付したりしないでください。

チェック4: deleteとreplaceを既定で止める

人間向け画面だけでなく、機械可読JSONを作ります。

terraform show -json tfplan > tfplan.json
python3 check_plan.py tfplan.json

TerraformのJSONでは、actions["delete"]なら削除、["delete","create"]または["create","delete"]なら置換です。どちらも既定で停止し、次を人間が確認します。

  • なぜ置換が必要なのか
  • データは退避されるか
  • 停止時間は許容範囲か
  • rollbackは可能か
  • 同じ目的をupdateで達成できないか

AIには理由の候補を出させてよい。でも、消えてよいかは持ち主が決めます。

チェック5: 変更数の上限で想定外の広がりを止める

小さなPRで100リソースが変わるなら、依存関係やprovider更新を疑う余地があります。固定の万能値はないので、チームの通常変更から上限を決めます。

def count_changed_resources(plan: dict) -> int:
    return sum(
        1
        for item in plan.get("resource_changes", [])
        if item.get("change", {}).get("actions") != ["no-op"]
    )

limit = 20  # 例。実績から人間が決める
count = count_changed_resources(plan)
if count > limit:
    raise SystemExit(f"blocked: {count} resources exceed limit {limit}")

大規模移行では20件を超えることもあります。その場合は上限を消すのではなく、変更を分割するか、理由・期間・承認者つき例外にします。

チェック6: plan JSONを秘密情報として扱う

ここは見落とされやすいです。公式のterraform showドキュメントは、-jsonでsensitive値が平文表示されると警告しています。sensitive = trueは、すべての保存先から値を消す魔法ではありません。

最低限、次を守ります。

  • planとJSONをGitへcommitしない
  • CI artifactへ保存するなら短い保持期間と限定権限にする
  • 外部AIへ生JSONを貼らない
  • AIへ渡すなら、アドレス・actions・件数だけのローカル要約にする
  • ログと一時ファイルをジョブ終了時に破棄する

AIにレビューさせるために秘密を外へ出したら、本末転倒なんです。

チェック7: applyを人間承認の後ろへ置く

自動化の最後に、責任の境界を残します。

name: terraform-plan
on:
  pull_request:
    paths:
      - "infra/**"

permissions:
  contents: read

jobs:
  plan:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: infra
    steps:
      - uses: actions/checkout@v4 # 本番運用ではcommit SHA固定を検討
      - uses: hashicorp/setup-terraform@v3
      - run: terraform fmt -check -recursive
      - run: terraform init -backend=false
      - run: terraform validate
      - run: echo "実planは認証済みの隔離環境で生成し、適用は別jobの承認後に行う"

この例は構造を示す最小形で、実クラウドplanとapplyは動作未確認です。provider認証、backend、lock、environment protection、actionのSHA固定は自分の環境に合わせて設計してください。

planとapplyを同じ無承認jobで連続実行すると、レビューは儀式になります。applyはprotected environmentなどの承認後へ分け、レビューした保存planだけを適用します。

AIへ任せるプロンプト3本

プロンプト1: 変更要約

次のTerraform plan要約をレビューしてください。
生の値やsecretは渡していません。

入力:
- resource address
- actions
- replace_paths
- 変更件数

出力:
1. delete / replace / update別の件数
2. 人間が確認すべき質問
3. 影響を小さくする代替案
4. 不明点

applyの可否は決めず、確認できないことを推測で埋めないでください。

プロンプト2: 例外申請レビュー

Terraformの危険変更ゲートを一時的に例外化する申請をレビューしてください。

必須項目:
- 対象resource
- delete / replaceが必要な理由
- データ退避
- 停止時間
- rollback
- 例外の期限
- 承認者

欠落項目だけを質問にしてください。
承認・apply・権限変更は実行しないでください。

プロンプト3: PR差分とplanの照合

HCLのPR差分と、値を除いたplan要約を照合してください。

探すもの:
- PRの意図に書かれていない変更
- delete / replace
- 変更件数の急増
- providerやmodule更新による広範囲変更
- planでは判断できず人間確認が必要な点

事実、推定、未確認を分けて出力してください。
最終判断は人間に残してください。

この方法が効かない条件

planは未来予知ではありません。plan後に外部状態が変わる、provider/APIの挙動が変わる、Terraform管理外の資源へ副作用が出る、といったケースは残ります。

また、terraform testはmoduleやroot moduleを検査できますが、設定によって実リソースを作成し、費用が発生する場合があります。安全な専用アカウントとcleanup確認なしに本番で試すものではありません。

なので高リスク変更では、適用前レッドラインに加えて次も必要です。

  • 非本番で先に適用する
  • 小さな単位へ分割する
  • backupとrollbackを実際に確認する
  • apply後の監視と停止条件を決める

逆に、使い捨ての個人検証環境で、消失を許容できる場合は7つ全部が重いこともあります。その場合でも、delete/replaceの確認と秘密情報の扱いだけは残す。軽量化と無防備は別です。

今日10分でやること

まずは記事のダミーJSONとcheck_plan.pyを保存し、終了コード1を確認してください。その次に、手元の 非本番planを1本だけ 同じチェッカーへ通します。

全部を一日で導入しなくても大丈夫です。未来の自分がapplyボタンの前で「これ、ほんまに消してええんやったっけ」と悩む時間を、今日の小さな境界が省いてくれます。

参考リンク(一次情報・2026-07-25確認)

生成AI活用エンジニア&3児のパパ。AI×開発の実践知を毎日発信しています → X

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?