🔧 「書き終えた後」が運用の本番
本記事はシリーズ 「Terraform IaC 実践ガイド」第 5 回(運用編・最終回) です。第 4 回 CI/CD パイプライン編 までで、コードは安全に本番へ流れるようになりました。しかし Terraform 運用の事故は、**コードを書くフェーズではなく「運用フェーズ」**で起きます。
- 誰かが GCP コンソールから手で直した → State と実態がズレる(State drift)
terraform planが突然「3 リソース作成」と言い出す- apply 直前に
Saved plan is staleで止まる allUsersへの公開を入れたら組織ポリシーで弾かれる
本記事は、これらの実際に踏んだ運用トラブルと、その解消手順をまとめます。State は本番インフラの「正本」です。壊すと復旧が高くつくため、操作前の手順を徹底します。
🌊 State drift とは何か
State drift = Terraform の State(管理台帳)と、クラウドの実態がズレた状態です。主な原因は 3 つ。
| 原因 | 例 |
|---|---|
| 手動変更 | コンソール / CLI でリソースを直接変更・削除した |
| 外部自動化 | オートスケーラや別ツールが属性を書き換えた |
| import 漏れ | 既存リソースを State に取り込まずコード化した |
drift は 3 つの型に分類でき、型ごとに解消手段が違います。
| 型 | 症状 | 解消 |
|---|---|---|
| State にあるが実体がない | plan が「N 個を新規作成」と表示 | terraform state rm で台帳から外す |
| 実体があるが State にない | コンソールにあるのに plan に出ない | terraform import で台帳に取り込む |
| 属性のズレ | CPU・メモリ等が plan で差分表示 | コードを実態に合わせて修正 → apply |
🛑 State を触る前の「必須 4 ステップ」
terraform state rm / import / mv は台帳を直接書き換える破壊的操作です。実行前に、必ずこの順で確認します。この手順を飛ばした state 操作が、運用事故の最大の発生源です。
# ① State を確認(台帳側の現状)
terraform state list
# ② 実態を確認(クラウド側の現状)— これを必ずやる
gcloud run services list --project="$PROJECT" --region=asia-northeast1
aws lightsail get-instances --region us-east-1
# ③ バックアップを取る(戻せるようにする)
terraform state pull > state-backup-$(date +%Y%m%d-%H%M%S).json
# ④ ①②③の後で初めて操作する
terraform state rm 'module.xxx.resource.name'
②の「実態確認」を省略しないこと。State だけ見て state rm すると、「実は実体が存在していた」場合にリソースが Terraform 管理外で放置されます。
📋 State コマンド早見表
| 安全(読み取り専用) | 用途 |
|---|---|
terraform state list | 管理中リソースの一覧 |
terraform state show 'addr' | 1 リソースの属性詳細 |
terraform state pull > backup.json | State をローカルに退避 |
| 破壊的(GCS 上の State を書き換える) | 用途 |
|---|---|
terraform state rm 'addr' | 台帳から削除(実体は残す) |
terraform import 'addr' 'id' | 既存リソースを台帳に取り込む |
terraform state mv 'old' 'new' | 台帳上のアドレスを改名 |
State はリモート(GCS)にあり、git にはコミットしません。 ローカルの
.terraform/は一時ファイルです。「State 変更をコミットしなきゃ」と思ったら、それは設計を誤解しています。State の正本は GCS バックエンド側にあります。
🧯 実例:State にあるが実体がない drift を解消する
あるクライアントの Lightsail スタックで、terraform plan が「3 リソースを新規作成」と表示しました。コードは変えていないのに新規作成 = drift のサインです。
# ① State 確認 → 該当リソースは台帳にある
terraform state list | grep lightsail
# ② 実態確認 → AWS には存在しない(手動削除されていた)
aws lightsail get-instances --region us-east-1
# ③ バックアップ
terraform state pull > state-backup-20260512.json
# ④ 台帳から外す(実体が無いので rm が正しい)
terraform state rm 'module.subsystem.aws_lightsail_instance.main' # 3 リソース分
# ⑤ 再 plan → 「6 個を作成」(外した 3 + 元から未作成の 3)= 整合
terraform plan
学び:
- AWS/GCP CLI での実態確認は必須。State だけ見て判断しない
state rmは不可逆。必ず ③ のバックアップを先に取る- state 変更は git コミット不要(GCS が正本)
🚑 よくある障害と対処
障害 1:Saved plan is stale
apply ジョブが、保存済み plan を使おうとして止まる。
- 原因: plan を作った後に State が変わった(別の apply・import・state rm が走った)
- 対処: 同一ジョブ内で plan し直すか、apply 前に新しい plan を作る。第 4 回の plan/apply 分離 + plan artifact の TTL(
expire_in: 1 day) はこの事故を減らすための設計
障害 2:State lock のタイムアウト
前のジョブがクラッシュしてロックを掴んだまま死んだ。
# 他のプロセスが本当に動いていないことを確認してから
terraform force-unlock <LOCK_ID>
force-unlockは本当に誰も apply していない確証があるとき限定。並行 apply 中に外すと State が壊れます。GCS バックエンドの state lock + GitLabresource_group:の二重化(第 4 回参照)で、そもそもロック競合を起こさない設計にしておくのが先決です。
障害 3:403 SERVICE_DISABLED
リソース作成時に API 無効エラー。対象プロジェクトでは API を有効化済みなのに出る。
- 原因: bootstrap プロジェクト側で API が有効化されていない。マルチプロジェクト構成では、対象プロジェクトと bootstrap プロジェクトの両方で API 有効化が要る場合がある
- 対処: bootstrap の
required_apisに該当 API を追加して apply。準備の落とし穴は第 2 回 準備編 で詳説
障害 4:allUsers IAM が組織ポリシーで弾かれる
公開エンドポイントに allUsers(誰でも invoke 可)を付けたらエラー。
- 原因: 組織ポリシーが
allUsersをデフォルトでブロック。解放用の Tag が未設定、または Tag 伝播待ち - 対処: 解放用の Tag を先に付与し、公開 IAM を後に適用する順序制御がカギ。Terraform では
depends_onで「Tag → IAM」の順序を固定する
順序を固定しないと、Tag の伝播前に公開 IAM が走り、組織ポリシー違反で失敗します。組織ポリシーによる allUsers 制御の有無と解放手段は、組織ごとに設定が異なるため、自組織のポリシーに合わせて準備段階で確認してください(第 2 回 準備編)。
🔁 環境移行(dev → stg → prod)の作法
環境を増やす・移すときは、環境差分を変数で吸収します。コードを分岐コピーしないのが原則です。
WIF の認可条件を環境で分岐
認証境界も環境変数で切り替えます。prod は保護ブランチからしか apply できないよう絞り、stg は MR からも回せるよう緩める——という条件分岐を、同じモジュールで環境ごとに変えます(具体の条件式は自組織のブランチ運用に合わせて定義)。
ドメイン命名を環境で分岐
domain = var.env_name == "prod"
? "api.${var.client_domain}"
: "${var.env_name}-api.${var.client_domain}" # stg-api.example.com
移行手順
environments/stg/<stack>/を作成(main.tf/variables.tf/outputs.tf/backend.hcl)— 専用 Terraform エージェントが既存の類似構成を手本に規約どおり生成- 環境用の GCP プロジェクトを分離(stg と prod は別プロジェクト)
- Secret Manager に環境分の tfvars を登録(命名規約は第 2 回参照)
- WIF 条件・ドメインを env で分岐
- 既存リソースがあれば
terraform import、無ければ新規作成 - DNS レコード・SSL 証明書を検証
🚫 運用フェーズの禁止事項
| 禁止 | 理由 |
|---|---|
| gcloud / aws CLI でリソースを直接作成・変更 | 即 State drift の原因 |
| ローカルから prod へ直接 apply | レビュー・監査が残らない。必ず CI 経由 |
terraform state rm を勢いで実行 | 不可逆。①②③の確認とバックアップが先 |
force-unlock を確認なしで実行 | 並行 apply 中だと State 破損 |
| State ファイルを git にコミット | State は GCS が正本。機密も含む |
🤖 全工程を Claude Code で管理・運用する
本基盤は、インフラの開発から運用までを一貫して Claude Code(AI エージェント)で管理しています。コード生成だけでなく、ローカルでの plan 確認 → git push → MR 作成までを Claude Code が運用し、人間はレビューと本番承認のゲートを担います。
一連の運用ループ
① タスク受領 → サイズ判定(S / M / L)
② M 以上はステアリング(要件・設計・決定の SSoT)を起こす
③ 専用エージェントが既存規約を手本に tf を生成 / 変更
④ ローカルで terraform plan を実行し、差分を確認
⑤ pre-push チェック → git push → MR を作成 ← ここまで Claude Code が運用
⑥ 人間が MR をレビュー・承認 ← ヒューマンゲート
⑦ CI が plan/apply(stg 自動・prod は手動承認) ← 第 4 回の動的 child pipeline
③〜⑤は Claude Code が一貫して回します。一方で、本番 apply は必ず人間の承認を通る(第 4 回の when: manual)よう、ゲートはモデル速度や自動化と独立に効かせます。「自動化するほど、ゲートは構造で固める」のが原則です。
ただし、品質を作り込むのは最後の承認ゲートではなく、その手前の「壁打ち」とステアリングです。①②の段階で AI と十分に壁打ちし、要件・設計・手順を固めておくほど、③以降の生成・plan・apply は素直に通ります。迷ったら手を止めて壁打ちする——この往復が不具合と手戻りを最も効率よく減らす、というのがステアリング駆動開発の核心です(壁打ちの位置づけ)。
サイジングで扱いを変える
インフラ変更は「サイズ」で扱いを変えます。
| サイズ | 基準 | ステアリング | 例 |
|---|---|---|---|
| S | バグ修正・単一ファイル | 不要 | WIF 条件の 1 行修正 |
| M | 単一スタック・モジュール追加 | 必要 | あるスタックへのリソース追加 |
| L | 複数スタック・アーキ変更・クラウド統合 | 必要 | クラウド統合(複数フェーズ) |
M / L のタスクは、いきなり apply せず、ステアリングを起こしてから着手します。State drift 解消のような不可逆操作こそ、「実態確認 → バックアップ → 操作」の手順を AI に逸脱させないためにステアリングが効きます。
このインフラ基盤は、リポジトリ直下に CLAUDE.md(禁止事項・必須ルール)、.claude/(Skill / Agent)、.steering/(タスク単位の SSoT)を備え、「ローカルから prod へ直接 apply 禁止」「state 操作前の実態確認必須」といった運用ルールを AI に守らせる設計にしています。手法そのものの詳細は AI 開発シリーズに譲ります。
インフラ × AI 駆動開発の接続点は、第 1 回 概要 の「ステアリング駆動で開発する」節と、ステアリング駆動開発シリーズ を参照してください。
🎯 まとめ
| 運用の落とし穴 | 構造的な対策 |
|---|---|
| 手動変更による State drift | CLI 直接操作を禁止し、全変更を Terraform 経由に |
| State 操作の事故 | 「list → 実態確認 → backup → 操作」の 4 ステップ厳守 |
stale plan | plan/apply 分離 + plan artifact の TTL |
| state lock 競合 | GCS lock + resource_group: の二重化 |
403 SERVICE_DISABLED | bootstrap プロジェクトでも API 有効化 |
allUsers ブロック | Tag Binding → IAM の順序を depends_on で固定 |
| 不可逆操作を AI が逸脱 | ステアリングで手順を SSoT 化し逸脱を防ぐ |
Terraform は「書く」より「運用する」フェーズの設計が事故を分けます。State を正本として丁寧に扱い、不可逆操作には必ず手順を踏む——これが IaC を 3 年運用しても壊れない基盤に保つ作法です。本シリーズはこれで完結です。
📚 シリーズ記事
| # | タイトル | 内容 |
|---|---|---|
| 1 | 概要 | マルチクラウド構成の全体像と AI 駆動運用 |
| 2 | 準備編 | アカウント設計・WIF 認証・bootstrap・State 管理 |
| 3 | モジュール設計編 | 再利用可能なモジュールの設計原則 |
| 4 | CI/CD パイプライン編 | 動的 child pipeline・WIF キーレス認証・GCP↔AWS |
| 5 | 運用編(本記事) | State drift 解消・障害対応・AI 運用ループ |
関連記事
- GitLab → GCP 認証方式の 3 世代と CTS の選択 — 運用で使う認証方式の技術選定
- GCP → AWS キーレス認証の落とし穴 — マルチクラウド運用で踏む罠
- ステアリング駆動開発とは:概要 — 本基盤を AI と運用する手法
- ブレインストーミングとサイジング — S/M/L サイジングの考え方
- Claude Code 7層ハーネスエンジニアリング — 運用ルールを AI に守らせる仕組み
外部資料