ECSのデプロイワークフローにecspressoを組み込んだ直後、こうした「tfstateと本番の食い違い」に直面するチームは多くあります。原因はシンプルです。TerraformもecspressoもどちらもECSのリソースに触れるのに、それぞれの担当領域を分けていないためです。
この記事では、ecspresso導入時に起きるtfstate競合問題の実態と、
lifecycleブロックを使った根本解決策、そしてTerraformとecspressoそれぞれが「何を担当すべきか」の設計指針を、具体的なHCL設定例とともに解説します。動作確認環境: Terraform 1.9 / ecspresso v2.4 / AWS provider 5.x
この記事のポイント
・ecspressoがデプロイしたタスク定義リビジョンはtfstateと食い違いが生じる
・lifecycle { ignore_changes }でecspresso管轄フィールドをTerraformから切り離せる
・Terraformはインフラ基盤を、ecspressoはECSデプロイをそれぞれ担うのが正しい設計
・terraform state showでtfstateの現在値を確認してから設計を決める
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
ecspressoとTerraformは「何を管理するか」が根本的に違う
TerraformはHCLで記述したリソース定義をtfstateと突き合わせて差分を管理するツールです。一方ecspressoは、ECSサービスのデプロイに特化したCLIツールで、ecs.json(サービス定義)とecs-task-def.json(タスク定義)を読み込んでECS APIを直接操作します。問題の本質は、この2つのツールが「重複してECSリソースに触れる」点にあります。
・Terraform:
aws_ecs_task_definition・aws_ecs_serviceをtfstateで管理する・ecspresso:タスク定義の新リビジョンを登録し、ECSサービスのタスク定義を更新する
ecspressoが
my-app:5(リビジョン5)にサービスを更新した後、Terraformのtfstateにはmy-app:3(リビジョン3)が残ります。この状態でterraform applyを実行すると、Terraformは「リビジョン3に戻すべき変更がある」と判断し、デプロイを巻き戻します。これがtfstate競合問題の正体です。tfstate競合問題の実態|どんな場面で起きるか
1. ecspressoデプロイ後にterraform applyがタスク定義を上書きする
もっとも典型的なパターンです。CI/CDパイプラインでecspressoを使って新しいDockerイメージをデプロイした後、別のパイプラインやオペレーターがterraform applyを実行します。# ecspressoでデプロイ(タスク定義 my-app:5 に更新) $ ecspresso deploy --config ecspresso.yml # terraform planで差分が出る(Terraformはtfstateのリビジョン3を正とする) $ terraform plan # aws_ecs_service.app will be updated in-place ~ resource "aws_ecs_service" "app" { ~ task_definition = "arn:aws:ecs:ap-northeast-1:123456789012:task-definition/my-app:5" -> "arn:aws:ecs:ap-northeast-1:123456789012:task-definition/my-app:3" }
terraform applyを実行するたびにリビジョン3に戻るため、ecspressoで進めたデプロイが無意味になります。2. desired_countの競合
ecspressoは--desired-countオプションやサービス定義ファイルで実行タスク数を指定できます。オートスケーリングと組み合わせている場合、スケールアウト後のタスク数(例: 10台)をTerraformが定義値(例: 2台)に戻すという事故も起きます。# Terraformのリソース定義(desired_count = 2 で固定) resource "aws_ecs_service" "app" { name = "my-app" cluster = aws_ecs_cluster.main.id task_definition = aws_ecs_task_definition.app.arn desired_count = 2 # オートスケール後に terraform apply で2に戻る }
3. terraform planが常に差分を出し続ける
上記2つの競合が起きると、terraform planを実行するたびに変更差分が表示され続けます。「planがクリーンにならない」状態はTerraformの運用ルールを守りにくくし、チーム全体の認知負荷を高めます。根本解決策|lifecycle ignore_changesでフィールドを除外する
1. 競合するフィールドをignore_changesに列挙する
lifecycleブロックのignore_changesは、指定したフィールドをtfstate差分比較から除外します。ecspressoが管理するフィールドをここに列挙することで、Terraformとの競合を解消できます。resource "aws_ecs_service" "app" { name = "my-app" cluster = aws_ecs_cluster.main.id task_definition = aws_ecs_task_definition.app.arn desired_count = 2 lifecycle { ignore_changes = [ task_definition, # ecspressoがリビジョンを更新するため desired_count, # オートスケーリング・ecspressoが変更するため ] } }
terraform planを実行すると、ecspressoが更新したタスク定義リビジョンや実行タスク数は変更対象から除外され、planがクリーンになります。2. aws_ecs_task_definitionの扱い方
ecspressoはecs-task-def.jsonでタスク定義を完全に管理します。Terraformでもaws_ecs_task_definitionリソースを宣言していると、二重管理になります。設計の選択肢は2つです。
・A案:Terraformからタスク定義を完全に削除する(ecspressoに一本化)
・B案:Terraformはタスク定義の「初期バージョン」だけを管理し、以降のリビジョン更新はecspressoに委ねる
本番運用ではB案が現実的です。初回の
terraform applyでタスク定義のリビジョン1を作成し、以降のデプロイはecspressoが担当します。Terraform側ではaws_ecs_serviceのみignore_changes = [task_definition]を設定します。なお、TerraformとecspressoのECSデプロイ連携設計を体系的に学ぶには、TerraformのIaC設計コースも参考にしてください。
Terraform vs ecspresso|役割分担の設計指針
競合問題の根本対策は、2つのツールの担当領域を明確に分けることです。| 管理ツール | 担当リソース | 変更タイミング |
|---|---|---|
| Terraform | VPC・サブネット・セキュリティグループ・ECSクラスター・IAMロール・ALB・ECRリポジトリ・CloudWatchロググループ | インフラ変更時(低頻度) |
| ecspresso | ECSサービスのタスク定義リビジョン更新・desired_count・デプロイ戦略(rolling update) | アプリデプロイ時(高頻度) |
この設計にすることで得られるメリットは次のとおりです。
・terraform planがクリーンになる:デプロイのたびに差分が出なくなる
・デプロイ速度が上がる:terraform applyを経由せず ecspresso deploy で即時反映できる
・権限分離が明確になる:インフラ変更はTerraform管理者、デプロイはアプリチームという分業が可能
・ロールバックが容易になる:ecspressoの
ecspresso rollbackコマンドで直前のタスク定義リビジョンに戻せるterraform state showでtfstateの実態を確認する
設計変更の前に、現在のtfstateが何を記録しているかを確認します。# ECSサービスのtfstate内容を確認 $ terraform state show aws_ecs_service.app # aws_ecs_service.app: resource "aws_ecs_service" "app" { cluster = "arn:aws:ecs:ap-northeast-1:123456789012:cluster/my-cluster" desired_count = 2 id = "arn:aws:ecs:ap-northeast-1:123456789012:service/my-cluster/my-app" name = "my-app" task_definition = "arn:aws:ecs:ap-northeast-1:123456789012:task-definition/my-app:3" ... }
task_definitionフィールドに表示されているリビジョンが本番で実際に動いているリビジョンと一致しているかを確認します。食い違いがある場合、ecspressoとTerraformの競合がすでに発生しています。# AWS CLIで本番の実際のタスク定義リビジョンを確認 $ aws ecs describe-services \ --cluster my-cluster \ --services my-app \ --query "services[0].taskDefinition" \ --output text arn:aws:ecs:ap-northeast-1:123456789012:task-definition/my-app:5
:3、本番が:5ならば競合が起きています。この状態でterraform applyを実行するとリビジョン3に巻き戻ります。ignore_changesを設定した後にterraform applyを1回実行し、その後terraform planでdiff=0になることを確認してください。これで設定が正しく機能しています。本記事のまとめ
| 問題 | 原因 | 解決策 |
|---|---|---|
| terraform applyがタスク定義を巻き戻す | tfstateが古いリビジョンを正として保持している | aws_ecs_serviceにignore_changes = [task_definition]を追加 |
| desired_countが定義値に戻る | オートスケール・ecspressoの変更をTerraformが上書きする | ignore_changes = [desired_count]を追加 |
| terraform planが常に差分を出す | TerraformとecspressoがECSリソースを二重管理している | 役割分担を明確化してignore_changesで境界を宣言する |
lifecycleブロックで明示することが、競合問題の根本解決です。lifecycle { ignore_changes = [task_definition, desired_count] }の1設定で、terraform planが常にクリーンになり、ecspressoのデプロイが安全に機能します。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:Terraform planを正確に読む方法|変更・追加・削除・再作成の差分記号とknown after applyの解説
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます