Terraformの事故の多くは、コードではなくstate(状態ファイル)の扱いから生まれます。stateとは何か、なぜ機微情報を含むのか、remote backendとlockでどう守るのかを、チーム運用の勘所として整理します。

Terraformを本格的に運用し始めると、必ず突き当たるのがstate(状態ファイル)の扱いです。コードのレビューはできても、stateの設計とロックの運用が曖昧なまま複数人・複数パイプラインで回すと、いつか必ず事故が起きます。この記事では、HashiCorp公式の定義を土台に、SIer・エンタープライズの実務でつまずきやすいポイントを順に整理します。

01stateとは何か — 実世界と構成の「対応表」

HashiCorp公式は、stateの役割を「実世界のリソースを構成にマッピングし、メタデータを追跡し、大規模インフラのパフォーマンスを改善する」と定義しています(State | Terraform)。つまりstateは、あなたが書いた.tfのリソースアドレス(aws_instance.webなど)と、クラウド上に実在するリソースID(i-0abc...)を結びつける対応表です。

Terraformがplanで差分を計算できるのは、この対応表があるからです。stateがなければ、Terraformは「このコードに対応する実物がどれか」を知る手段を失います。裏を返せば、stateが壊れる・食い違うと、Terraformは既存リソースを認識できず、作り直そうとしたり削除しようとしたりします。stateはコードと同格、あるいはそれ以上に守るべき資産だと考えてください。

現場のコツ:「コードがマスターでstateは副産物」という理解は危険です。実務では、stateこそがTerraformにとっての現実認識であり、コードとstateがズレた瞬間にdriftや破壊的変更が始まります。

02なぜstateは機微情報を含むのか

stateはJSONであり、リソースの属性値をそのまま保持します。ここに落とし穴があります。公式も明言している通り、ローカルで開発するとTerraformはstateを平文ファイルに保存し、構成で定義したシークレット値もそこに含まれます(Manage sensitive data | Terraform)。

具体的には、初期DBパスワード、生成されたAPIトークン、TLS秘密鍵、IAMアクセスキーなどが、リソースの属性としてstateに書き込まれます。variableoutputsensitive = trueを付けても、それはCLI出力やUIでのマスキングにすぎず、state内には値が残ります。stateファイルは「機微情報の塊」だと前提を置くのが安全です。

03ローカルstateの危険 — なぜ本番で使ってはいけないか

デフォルトのローカルstate(terraform.tfstate)は、学習や単独検証には便利ですが、チーム運用では致命的です。理由は3つに整理できます。

「とりあえずローカルで始めて後で移す」は、移行時にstateマイグレーションという別の作業を生みます。最初からremote backendを前提に設計するのが、結局は最短です。

04remote backend — S3 + ロックの定番構成

remote backendは、stateをリモートに保存し、必要なときだけメモリ上に展開する仕組みです。AWSでの定番はS3バックエンドです。stateをS3に置き、暗号化とロックを組み合わせます(Backend Type: s3 | Terraform)。

ここで押さえるべき最新の重要ポイントがあります。従来はロック用にDynamoDBテーブル(dynamodb_table)を併用する構成が定番でしたが、公式ドキュメントは「DynamoDBベースのロックは非推奨(deprecated)であり、将来のマイナーバージョンで削除される」と明記しています。代わりに、S3のネイティブロック機能であるuse_lockfile = trueが推奨されています。これはS3上に<key>.tflockというロックファイルを作る方式です。

現場のコツ:既存プロジェクトでDynamoDBロックを使っている場合、use_lockfiledynamodb_tableは移行目的で同時併記できます(公式)。いきなり切り替えるのではなく、両方有効な期間を設けて安全に移行してください。バージョン依存のため、採用可否は公式で要確認です。

暗号化はencrypt = trueでサーバサイド暗号化を有効化し、鍵管理を厳密にしたい場合はkms_key_idでKMS鍵を指定します。エンタープライズでは、stateバケットへのアクセスを最小権限で絞ることが監査上ほぼ必須です。IAMの実運用についてはマルチアカウント統制の観点も合わせて設計してください。

開発者 terraform apply CIパイプライン terraform apply state lock key.tflock 同時実行を防止 S3バケット tfstate(暗号化)
図:開発者とCIの両方がremote stateにアクセスするため、ロックで同時applyを直列化する。

05state lock — 同時実行から守る最後の砦

state lockは、誰かがapplyしている間、別の実行がstateを触れないようにする仕組みです。これがないと、2つのapplyが同じstateを読み書きし、対応表が半分書き換わった壊れたstateが生まれます。壊れたstateからの復旧は、実務で最も神経を使う作業のひとつです。

特にCI/CD化した環境では、人間の手動applyとパイプラインの自動applyが競合しやすくなります。ロックは「行儀の良い運用ルール」ではなく、機械的に競合を防ぐガードレールです。前述のuse_lockfile(S3ネイティブロック)を有効化し、ロック取得失敗時にジョブが安全に失敗する設計にしておきます。CI/CDでの実行順序やゲートの設計は、IaCのCI/CDパイプライン設計と合わせて考えると全体像が掴めます。

現場のコツ:ロックが残留(stale lock)する事故は必ず起きます。誰がいつロックを取得したかを記録し、force-unlockは限られた担当者だけが、状況を確認したうえで実行する運用にしてください。安易な強制解除は、実行中のapplyと衝突して事態を悪化させます。

06workspaceとstateの分割 — blast radiusを制御する

Terraformには複数のstateを扱う仕組みとしてworkspaceがあります。同一構成コードに対して、開発・検証・本番といった別々のstateを持てます。ただし、workspaceだけで環境分離を完結させるのは、エンタープライズでは推奨しにくい場面が多いです。理由は権限分離とblast radius(影響範囲)にあります。

より重要なのは、stateをどう分割するかという設計です。単一の巨大stateにすべてのリソースを詰め込むと、次の問題が起きます。

実務では、ライフサイクルと責任境界でstateを分けるのが定石です。例えば「ネットワーク基盤」「共有サービス」「各アプリケーション」といった単位で分割し、変更頻度と影響範囲を揃えます。分割した各stateは、後述のterraform_remote_stateデータソースなどで疎に参照します。

単一の巨大state ネットワーク 共有サービス アプリA アプリB 責任境界で分割 state: ネットワーク state: 共有サービス state: アプリA state: アプリB 分割
図:巨大な単一stateを責任境界で分割し、blast radiusとロック競合を小さくする。

07import / move — 既存資産と構成変更に追従する

stateを扱う実務では、コードとstateの対応をあとから直す操作が避けられません。代表的なのがimportとmoveです。

公式は、movedブロックを使うと、Terraformはfromで指定した既存オブジェクトをtoの名前へリネームしたうえでplanを作るため、リソースを破棄せずに済むと説明しています(moved block reference | Terraform)。リファクタリング時に「リネームしただけのつもりが、destroy → create の破壊的変更になる」事故を防ぐのがmoveの役割です。

現場のコツ:コマンドでのstate mvは手元操作で履歴が残りにくいのが難点です。チーム運用では、可能な限りmovedブロックのようにコードとして残る方法を選び、変更の意図をレビュー可能にしてください。stateへの手作業は、レビューされない変更として事故の温床になります。

08チーム運用の勘所 — ルールを仕組みに落とす

ここまでの要素を、SIer・エンタープライズのチーム運用として束ねると、次の原則に集約されます。

これらは一度に完璧を目指すものではありません。既存環境の制約に合わせて、リスクの高い箇所から順に「仕組み」に落としていくのが現実的です。私たちEMWは、こうしたstate設計とパイプライン整備を、既存の運用を尊重しながら一緒に組み立てています。過去の導入事例も合わせてご覧ください。

まとめ

Terraformのstateは、実世界と構成をつなぐ対応表であり、機微情報を含む守るべき資産です。ローカルstateを避け、S3などのremote backendに置き、ロック(現在はS3ネイティブのuse_lockfileが推奨、DynamoDBロックは非推奨)で同時実行を防ぐ。そのうえで、責任境界でstateを分割してblast radiusを制御し、import/moveは可能な限りコードとして残す。この4点を仕組みに落とせば、Terraform運用の事故は大きく減らせます。バージョンや価格に依存する挙動は、必ず公式ドキュメントで最新情報を確認してください。

参考(一次情報)

stateの分割方針やremote backendの設計でお困りの際は、お問い合わせください。現場の運用に合わせて一緒に整理します。

相談する
← ブログ一覧へ戻る