こんにちは、バックエンドエンジニアの田中 湧大 (@Romira915)です。
今回は、Terraformで管理しているFastlyの設定をコンソールから直接変更したことがきっかけで、TerraformのStateが壊れてしまった事例を紹介します。
この記事では、実際にどう復旧したのか、何を学んだのかを共有します。
PR TIMESでのFastly運用フロー
PR TIMESでは、Fastlyの設定をすべてTerraformでコード化して管理しています。
運用フローは以下のとおりです。
- Pull Requestを作成 → GitHub Actionsが terraform plan を実行
- 問題なければmainにマージ → terraform apply が自動実行
- 本番適用は activate=false で安全に確認してから手動アクティベート
これにより、VCLの変更を安全にデプロイできる体制を整えています。
※ VCL(Varnish Configuration Language)は、Fastlyのキャッシュ動作やリクエスト処理をカスタマイズするためのドメイン固有言語です。ルーティング、キャッシュ制御、認証フローなどを実装できます。
参考:
About Fastly VCL | Fastly Documentation
何が起きたのか?
背景
ある日、特定のIPをブロックする必要があり、Terraformを介さずにFastlyコンソールから直接操作し、IP block listを有効化しました。
この操作により、Fastly内部では次の自動生成リソースが作られます。
- ACL (Generated_by_IP_block_list)
- response_object (Generated by IP block list)
Terraformは当然これを知らないままです。
Stateが壊れていくまでの流れ
Fastly Providerは「前回のTerraform適用時点のVersionをクローンしてapply」する仕組みです。
途中でコンソールから変更を加えた場合、その変更はFastly側では次のバージョンに引き継がれますが、TerraformのStateには記録されません。
そのため、次のterraform applyは成功しますが、実際にはStateと実体がズレた状態になります。
この挙動により、以下のような問題が発生しました。
| ステップ | 操作内容 | 状態 |
|---|---|---|
| ① v175 → v176 | コンソールでIP block listを ONにしてIPを追加 | Terraform未認識 |
| ② v176 → v177 | 別のPRで terraform apply 実行(成功) | Stateと実体がズレ始める |
| ③ 問題発覚 | planで差分を検知 | コンソール作成リソースが 表示される |
| ④ 対処試行 | HCL追記してapply | 一時的に成功 |
| ⑤ 削除失敗 | 不要リソース削除を試行 | エラー: ACL entries not found |
下の図は、コンソール操作からState不整合に至るまでの流れを示しています。

③ 問題発覚: VCL実装への切り替えを決定
後日、IP block listではなくVCLでIPブロックロジックを実装する方針に変更することになりました。
PR TIMESではルーティングなどほとんどの設定をVCLで実装しているため、IPブロックもVCLに寄せて管理を統一したかったためです。
そのため、コンソールで作成されたGenerated_by_IP_block_listを削除する必要がありました。
しかし、planを実行すると差分が検知されます(Terraformが知らないリソースなので)。
④ 対処試行: まずTerraform管理下に入れる
「Terraformが知らないリソースは削除できない。先に管理下に入れてから削除すればいい」と考え、以下のようなHCLを追記しました。
resource "fastly_service_vcl" "cdn" {
...
condition {
name = "Generated by IP block list"
priority = 0
statement = "client.ip ~ Generated_by_IP_block_list"
type = "REQUEST"
}
response_object {
content_type = "text/html"
name = "Generated by IP block list created by terraform"
request_condition = "Generated by IP block list"
response = "Forbidden"
status = 403
}
acl {
name = "Generated_by_IP_block_list"
force_destroy = false
}
}
この追記で apply は成功し、差分も消えました。Terraform管理下に入れることができました。
⑤ 削除時の問題
次に、このリソースを削除しようとしたところ、エラーが発生しました。
│ Error: error creating ACL entries: 400 - Bad Request:
│ Detail: 1 ACL entries for delete not foundエラーメッセージを見ると「削除対象のACLエントリが見つからない」とのこと。
おそらく、複数回のapply実行や、Fastly側での内部的な状態変更によって、TerraformのStateが記憶しているリソースIDと、Fastly実体側のIDが食い違ってしまったものと推測されます。
詳細な原因は特定できませんでしたが、結果としてStateと実体の整合が完全に崩れていたことは確かです。
方針転換:Stateを修正して整合を取り戻す
Terraformでの操作をあきらめ、次のように進めました。
- Fastlyコンソールで理想的な状態を再構築
- IP block listをOFF(自動生成リソースを削除)
- VCLコードでIPブロックロジックを実装(Terraform管理下のVCLファイルに条件分岐を追加)
- TerraformのStateを実体に合わせて修正
PR TIMESでは tfmigrate を使ってState操作を管理しています。
tfmigrateは、Terraform state操作を宣言的にコード化し、履歴管理するGitOpsツールです。
以下のようなマイグレーションファイルを作成しました。
migration "state" "fastly" {
actions = [
"rm 'fastly_service_acl_entries.ip_block_list_entries[\\\\"Generated_by_IP_block_list\\\\"]'",
"rm fastly_service_vcl.cdn",
"import fastly_service_vcl.cdn <your-service-id>",
]
}- 不要なACLエントリをStateから削除
- Fastlyコンソール上で正しい状態にしたservice_vclを再import
ただし、importした後もどうしても差分が出てしまう属性(active_version、cloned_version、dynamicsnippetなど)がありました。
terraform planで差分が出なくなることを最優先し、これらの属性をlifecycle.ignore_changesで無視するようにしました。
resource "fastly_service_vcl" "cdn" {
# ...
lifecycle {
ignore_changes = [
active_version,
cloned_version,
activate,
force_destroy,
stage,
dynamicsnippet,
image_optimizer_default_settings,
product_enablement,
]
}
}これにより、planがクリーンに戻りました。
なお、このlifecycle.ignore_changesは一時的な措置であり、次のバージョン更新時に削除しました。
結果と学び
学び①:コンソール操作は極力行わない
Terraform管理下のリソースを手動変更すると、ほぼ確実にStateと実体の不整合が発生します。
やむを得ずコンソール操作した場合は、直後にimportしてStateを整合させるか、Terraformの状態と合うように手動変更を戻す運用を必須にすべきです。
学び②:Fastly Providerの「最初のapplyは通る」挙動に注意
Fastly Providerは古いバージョンをクローンする仕組みのため、
実体の変更をTerraformが見落とすことがあります。
学び③:Fastlyの自動生成リソースはTerraform管理に含めない
IP block list のような自動生成リソースをTerraformに含めると、削除時に壊れやすいです。
VCLなどに寄せてロジックをコード化する方が再現性も高く、管理が安定します。
終わりに
今回は、Fastlyコンソールでの手動操作がきっかけでTerraform Stateが壊れてしまった事例と、tfmigrateを使った復旧方法を紹介しました。
Stateが壊れたときは、慌てずに”実体を正としてStateを合わせる”のが最短ルートです。
この記事がStateトラブル復旧の参考になれば幸いです。

