terraform.tfstate を消してしまったときの復旧 — バックアップファイル、S3 バージョニング、再 import の順で

読了 5分

terraform plan を実行したら、昨日まで問題なく管理されていたインフラの全部を新しく作ると言い出しました。

出力例
Plan: 47 to add, 0 to change, 0 to destroy.

インフラは AWS にそのまま生きているのに、Terraform だけが記憶を失った状態、つまり state が削除されたか破損した状況です。結論を先に置きます。いま絶対にやってはいけないのは apply で、復旧ルートは優先順位つきで 3 つあります。ほとんどの場合、思ったより軽く復旧できます。

なぜ apply してはいけないのか #

Terraform は state が空だと「何も管理していない」と信じます(Terraform 基礎 #5)。この状態で apply すると、すでに存在するリソースをもう一度作ろうとして、結果は 2 つのうちどちらかです。名前が一意でなければならないリソース(S3 バケット、IAM ロール)は衝突エラーで失敗し、名前の制約がないリソース(EC2 インスタンスなど)は実際に重複して作成されます。本番インフラの隣に複製インフラが生まれ、料金も 2 倍になる事故です。復旧が終わるまで apply は封印します。

ルート 1: ローカルのバックアップファイル #

ローカルバックエンド(リモートバックエンド設定前)だったなら、Terraform が残した直前のバックアップから確認します。

バックアップファイル確認
ls -la terraform.tfstate*
# terraform.tfstate          ← 消えたか破損したファイル
# terraform.tfstate.backup   ← 直前の状態の自動バックアップ

Terraform は state を書き込むたびに、直前の内容を terraform.tfstate.backup として残します。このファイルがあるなら、復旧はコピー 1 回です。

バックアップから復元
cp terraform.tfstate.backup terraform.tfstate
terraform plan   # "No changes" か、最後の作業 1 回分の diff なら成功

バックアップは最後の apply の直前の時点なので、plan に最後の作業 1 件分の変更が現れることがあります。その程度なら正常な復旧です。

ルート 2: S3 バージョニングから以前のバージョンを復元する #

S3 バックエンドでバージョニングを有効にしていたなら(Terraform 基礎 #9で「必須」と強調した理由がこの瞬間です)、削除・破損より前のバージョンがバケットにそのまま残っています。バージョンの一覧を確認します。

バージョン一覧確認
aws s3api list-object-versions \
  --bucket myapp-tfstate \
  --prefix myapp/prod/terraform.tfstate \
  --query 'Versions[].{Id:VersionId, Time:LastModified, Latest:IsLatest}'

事故より前の時刻の VersionId を選び、そのバージョンを最新に戻します。同じキーに該当バージョンをコピーする方式です。

バージョン復元
aws s3api copy-object \
  --bucket myapp-tfstate \
  --key myapp/prod/terraform.tfstate \
  --copy-source "myapp-tfstate/myapp/prod/terraform.tfstate?versionId=<VersionId>"

ファイルが削除されたケースなら、削除マーカー(DeleteMarker)が最新バージョンとして座っている形なので、delete-object --version-id で削除マーカーを消すだけでも直前のバージョンが生き返ります。復元後に terraform plan で状態を確認し、復元時点より後にあった apply 分の食い違いがあれば、その差分だけ整理すれば済みます。

ルート 3: バックアップが何もないなら再 import #

ローカルバックアップもバージョニングもないなら、残る道は state の再構築です。コードは残っているので(コードすらないなら、まずそちらを復旧します)、コードのリソース 1 つ 1 つを実際のリソースにつなぎ直す作業になります。道具は import ブロックで、手順は terraform import のやり方に整理してあります。コツだけ抜き出すとこうなります。

  • まず一覧を作ります: コードのリソースアドレスの一覧を抜き出し(grep -r "^resource" 程度で十分です)、それぞれの import ID をコンソール・CLI で確認して表にします。
  • 依存関係の順で進めます: VPC のように参照される側を先に、参照する側を後に取り込むほうが、途中の plan が荒れにくいです。
  • 完了基準は「変更なし」の plan です: 全部取り込んだあと、plan が import 以外の変更 0 なら再構築完了です。

リソースが数十個あれば半日以上かかる労働ですが、インフラを壊さずに復旧する確実な道です。

再発を構造で防ぐ #

復旧を終えたら、同じ事故が二度と起きない構造を確認します。

  1. リモートバックエンド + バージョニング: ローカル state はこの事故に無防備です。S3 バックエンドに移してバケットのバージョニングを有効にします。バージョニングが有効になった瞬間から、この記事の復旧はコマンド 2 行に縮みます。
  2. state バケットの削除防止: バケット自体の誤削除を防ぐポリシー(パブリックブロックは基本、必要なら MFA Delete やバケットポリシーでの削除制限)を検討します。
  3. 人が state ファイルを直接触らない: 破損事故のかなりの部分は手動編集から来ます。操作が必要なら state コマンドと moved・removed ブロックを使います(Terraform 運用 #2)。

まとめ #

  • state が消えると plan が全部新しく作ると言い出します。ここで apply すると衝突か重複作成になるので、復旧まで封印します
  • 復旧の優先順位は、ローカルの terraform.tfstate.backup のコピー → S3 バージョニングの以前のバージョン復元(削除マーカーの除去を含む) → import での再構築の順です
  • バージョニング復元後は、復元時点より後の変更分だけを plan で整理すれば済みます
  • 再 import はリソース一覧の表を作り、依存関係の順で、「変更なし」の plan を完了基準に進めます
  • 再発防止はリモートバックエンドとバージョニングです。この構造なら state の紛失は事故ではなく、コマンド 2 行のイベントになります
X