- 1 1. なぜecspresso×Terraform本番運用を選ぶか
- 2 2. ecspresso v2 責任境界設計 — Terraform との5軸比較
- 3 3. Terraform 基盤 × ecspresso × jsonnet 完全実装
- 4 4. GitHub Actions OIDC CI/CD 最新パターン
- 5 5. Blue/Green デプロイ + CodeDeploy 連携
- 6 6. 詰まりポイント10選 — ecspresso v2 × Terraform × CodeDeploy
- 6.1 ケース1: ecspresso init 失敗 (–cluster ARN 未指定)
- 6.2 ケース2: diff で差分検知漏れ (TaskDef INACTIVE 大量)
- 6.3 ケース3: deploy Stuck (HealthCheck 失敗でタスクが STOPPED ループ)
- 6.4 ケース4: CodeDeploy FAILED (AppSpec containerName 不一致)
- 6.5 ケース5: jsonnet 生成エラー (import パス解決失敗)
- 6.6 ケース6: GitHub Actions OIDC 認証失敗 (Trust Policy Condition 不一致)
- 6.7 ケース7: ECR push 失敗 (IAM 権限不足)
- 6.8 ケース8: Blue/Green ロールバック手順 (CodeDeploy コンソール操作)
- 6.9 ケース9: ecspresso rollback でバージョン確認
- 6.10 ケース10: Terraform と ecspresso の状態ズレ復旧
- 7 7. Copilot × CDK × ecspresso × Terraform × CodePipeline 5軸選定基準
- 8 8. まとめ + Container 本番運用シリーズ全14軸クロスリンク
1. なぜecspresso×Terraform本番運用を選ぶか
ecspresso は kayac が開発・公開している ECS 専用デプロイツールだ。ECS Service と Task Definition の設定をローカルの JSON/jsonnet ファイルで管理し、1 コマンドで AWS ECS へデプロイできる。v2.4.x では jsonnet ネイティブサポートが成熟し、複数環境 (dev/stg/prd) の設定を DRY に管理できる構造が整っている。GitHub Stars は 2,000 を超えており、国内大手スタートアップから上場企業まで幅広い採用実績がある。2024 年以降も活発にメンテナンスが続いており、ECS Fargate の新機能 (Sidecar コンテナ、Task Connect 等) にも迅速に対応している。
Terraform で AWS インフラを管理している場合、ecspresso の最大の強みは 責任境界の明確さ だ。Terraform はネットワーク・ロードバランサ・IAM などのインフラ層を担当し、ecspresso は ECS Service と Task Definition のみを担当する。この分担により、Terraform の tfstate を汚染せずに ECS デプロイを自動化できる。既存の Terraform 環境に ecspresso を 追加するだけで動く のが最大の利点で、既存 VPC や ALB の設定を変えることなく導入を完了できる。
デプロイの観点では、ecspresso diff コマンドがローカル設定と AWS 現在の差分を可視化する点が特に強力だ。GitHub Actions のワークフローに組み込むと、PR ごとに「何が変わるか」をコメントで自動投稿できる。CodePipeline の全量置換アプローチとは異なり、変更点のみを確認してからデプロイする安全なフローを構築できる。PR レビュー担当者が ECS の差分をコード変更と同じ GitHub 画面で確認できる体験は、チームの運用品質を大きく向上させる。
ECS デプロイ後の状態管理も ecspresso の特徴だ。ecspresso verify コマンドで ECS Service の現在の状態 (タスク数・ヘルスチェック状態・デプロイ状態) を確認できる。ecspresso rollback で直前のタスク定義へのロールバックも 1 コマンドで実行できる。手動の aws ecs update-service --task-definition によるロールバックと比べて、操作ミスのリスクが大幅に低い。
ECS デプロイツールとして選択肢は複数あるが、「Terraform で既に AWS 基盤を運用している」という前提がある場合、ecspresso が最も低コストで導入できる。AWS Copilot は VPC や Cluster を含む全スタックを管理しようとするため既存 Terraform 環境と競合しやすく、AWS CDK は Terraform との二重管理になりがちだ。ecspresso はその点で異なる哲学を持つ — 他のツールが管理するリソースには一切触れない という設計方針が、Terraform 運用チームへの导入コストを構造的に最小化する。
本記事では ecspresso v2.4.x / Terraform 1.9.x / AWS Provider 6.x の組み合わせで実装する。v2.x は v1 から設定ファイル形式を刷新しており、新規導入するなら v2 一択だ。既存 v1 環境からの移行手順も §2 でカバーする。Terraform AWS Provider 6.x では Security Group のルール定義が aws_vpc_security_group_ingress_rule / aws_vpc_security_group_egress_rule リソースに分離され、インライン定義より変更管理がしやすくなっている。
- Terraform 1.9.x + AWS Provider 6.x 基盤 (VPC/ALB/ECS Cluster/ECR/IAM) + outputs.tf 最小構成
- ecspresso v2 設定3ファイル (ecspresso.yml / ecs-service-def.jsonnet / ecs-task-def.jsonnet) 雛形
- jsonnet 3ファイル構成 (base.libsonnet + env/dev + env/prd) で環境別DRY管理
- Blue/Green + CodeDeploy 連携 Terraform HCL + appspec.yml 完全実装
- GitHub Actions OIDC v4+ CI/CD (PR diff自動投稿 + merge時 terraform apply → ecspresso deploy)
- Terraform で AWS インフラを運用中で、ECS Fargate のデプロイを自動化したい
- AWS Copilot を試したら Terraform と競合して困った
- ECS デプロイを
aws ecs update-serviceで手動実行しており CI/CD に移行したい - 複数環境 (dev/stg/prd) の Task Definition を JSON で別管理しており変更漏れが多い
1-1. Terraform 既存環境に ecspresso を追加する判断基準
ECS デプロイを自動化するツールを選ぶとき、多くのチームが最初に検討するのは AWS Copilot か AWS CDK だ。どちらも AWS が公式に提供しており、ドキュメントも充実している。しかし Terraform で基盤を管理している環境では、それぞれに固有の競合リスクがある。ecspresso の選択基準を正確に理解するために、まず競合ツールとの比較から整理する。
Copilot との競合問題: AWS Copilot は VPC・Subnet・ECS Cluster・ALB を含めた全スタックを CloudFormation で管理する設計だ。Terraform で既にこれらのリソースを構築していると、Copilot が同名リソースを新規作成しようとして競合する。copilot init 実行時に既存 VPC を --import-vpc で取り込む方法もあるが、ALB や ECS Cluster の競合は解決しにくく、最終的に Terraform と Copilot が同じリソースを二重管理する状態になるケースが多い。ロールバック時に Copilot の CloudFormation スタックと Terraform の tfstate がどちらが正しいか判断できなくなる事態も発生しうる。具体的には copilot svc deploy が Terraform で作成した Listener ルールの Priority を上書きする、あるいは Target Group の Health Check 設定が Copilot の manifest.yml で上書きされる、といった問題が報告されている。
CDK との混在コスト: AWS CDK は TypeScript や Python でインフラを定義するため、Terraform HCL との二重管理が発生する。CDK が CloudFormation を経由してリソースを管理するため、Terraform の tfstate と CDK の CloudFormation スタックが別々の状態管理を持つ。「VPC は Terraform、ECS は CDK」という分割も技術的には可能だが、どのリソースをどちらで管理するかの設計に相当のコストがかかり、チームの認知負荷が高まる。Terraform を使い続けるチームにとって CDK の導入は技術スタックの増加を意味する。CDK の Construct ライブラリのバージョン管理も別途必要になり、依存関係の複雑度が増す。
ecspresso を選ぶ理由: ecspresso は ECS Service と Task Definition のみを管理し、それ以外のリソースには一切書き込みを行わない。Terraform が管理する Cluster ARN や Target Group ARN は tfstate プラグインで参照するだけで、所有権の競合が起きない。デプロイコマンドの習得は ecspresso diff と ecspresso deploy の 2 コマンドだけで済む。ecspresso のバイナリ 1 本と設定ファイル 3 点でデプロイが完結するシンプルさが、Terraform 運用チームへの導入コストを最小化する。
ecspresso が向いていないケース: 新規プロジェクトで Terraform をまだ使っていない場合は AWS Copilot の方がセットアップが速い。ECS Service と同時に大量の関連リソース (SQS キュー、DynamoDB テーブルなど) を一括デプロイしたい場合は CDK や Terraform 単独の方が管理しやすい。ECS Service を Terraform で宣言的に管理し「git push = インフラ変更」のフローを崩したくない場合も、Terraform 単独で Service まで管理する選択肢がある。ecspresso は「Terraform 既存環境への追加」という特定のユースケースに最適化されている。
移行コストも低い。ecspresso init コマンドで既存 ECS Service の設定をローカルファイルに書き出し、terraform state rm aws_ecs_service.app で Terraform の管理から除外するだけで移行できる。ECS Service 自体の削除・再作成は不要で、稼働中のサービスをダウンタイムなく ecspresso 管理に切り替えられる。移行作業は 1〜2 時間で完了するケースが多い。
バージョン固定の重要性: ecspresso は活発に開発が続いており、マイナーバージョン間でも挙動が変わることがある。本番環境では GitHub Actions の uses: kayac/ecspresso-action@v2 のようにバージョンを固定し、予期しないアップデートによるデプロイ挙動の変化を防ぐことを推奨する。ecspresso version コマンドで現在のバージョンを確認できる。
エラー時の調査コマンド: デプロイが FAILED になった場合、ecspresso deployments で直近のデプロイ履歴を確認し、ecspresso verify で Service の現在状態 (タスク起動数 / ヘルスチェック状態) を確認する。ECS Exec が有効な場合は ecspresso exec で稼働中コンテナに直接接続してデバッグできる。
1-2. AWS 本番運用 全14軸ロードマップ — Container軸の位置づけ
- 第1軸 IAM (4記事): Vol1 ポリシー設計入門 / Vol2 マルチアカウント IAM 設計 / Vol3 Permission Inventory 自動化 / Vol4 STS クロスアカウント
- 第2軸 EKS 本番運用 (3記事): Vol1 クラスター設計/IRSA/ALB Ingress / Vol2 Observability/Fluent Bit/Container Insights / Vol3 GitOps/ArgoCD
- 第3軸 復旧・運用 (4記事): Vol1 Backup×Cross-Region DR / Vol2 Chaos Engineering×FIS / Vol3 インシデント対応ランブック自動化 / Vol4 Multi-Region Active-Active
- 第4軸 生成AI (2記事): Vol1 Bedrock Agents 本番運用 / Vol2 Knowledge Bases×RAG
- 第5軸 セキュリティ (2記事): Vol1 セキュリティ運用入門 / Vol2 SOC 統合運用
- 第6軸 コスト最適化 (1記事): Vol1 Cost Explorer×Budgets×Compute Optimizer
- 第7軸 マルチアカウント運用 (1記事): Vol1 Organizations×Control Tower×Landing Zone
- 第8軸 Observability (1記事): Vol1 Application Signals×SLO×X-Ray
- 第9軸 Network/VPC 設計 (2記事): Vol1 Transit Gateway×VPC Lattice×PrivateLink / Vol2 Hybrid Connectivity (Direct Connect×VPN)
- 第10軸 DevOps/CI/CD (2記事): Vol1 CodePipeline×CodeBuild×CodeDeploy×GitHub Actions OIDC / Vol2 Container×CodeArtifact×SAM×Amplify
- 第11軸 Database 本番運用 Vol1: RDS×Aurora×DynamoDB 基礎運用
- 第12軸 Database 本番運用 Vol2: DMS×Aurora Global Database×DynamoDB Streams×Backup 戦略
- 第13軸 Serverless 本番運用 Vol1: Lambda×API GW×Step Functions
- 第14軸 Serverless 本番運用 Vol2: EventBridge×SQS×SNS×Kinesis
- 基盤: Terraform 基礎入門: Terraform 1.x×AWS Provider 6.x (S3×EC2×Remote State×Module化)
1-3. ecspresso v2 セットアップとコア CLI コマンド
ecspresso のインストールは Homebrew または Go の go install で行う。バイナリ 1 本で完結するため、CI 環境への導入も容易だ。
# Homebrew (macOS / Linux)
brew install kayac/tap/ecspresso
# Go install
go install github.com/kayac/ecspresso/v2@latest
# バージョン確認 (v2.4.x 以上を推奨)
ecspresso version
# ecspresso v2.4.5 (go1.22.0 linux/amd64)
既存 ECS Service がある場合は ecspresso init で設定を自動生成できる。初期化後に jsonnet 変数化と tfstate plugin 設定を手動で追加する流れが標準だ。
# 既存 ECS Service から ecspresso 設定ファイルを自動生成
ecspresso init \
--region ap-northeast-1 \
--cluster myapp-cluster \
--service myapp-service \
--config ecspresso.yml
# 生成: ecspresso.yml + ecs-service-def.json + ecs-task-def.json
ecspresso diff— ローカル設定と AWS 現在の ECS Service / Task Def の差分を表示 (PR コメント自動投稿に活用)ecspresso deploy [--rollout-wait]— Task Def 登録 + Service 更新。--rollout-waitでデプロイ完了まで待機 (CI に推奨)ecspresso rollback— 直前のタスク定義に即時ロールバック (1 コマンドで完了)ecspresso verify— ECS Service の現在状態 (タスク数 / ヘルスチェック状態) を確認ecspresso exec— ECS Exec (稼働中コンテナへのシェル接続 / デバッグ)ecspresso run— ECS タスクの単発実行 (DB マイグレーション / バッチ処理)ecspresso init— 既存 ECS Service から設定ファイルを自動生成
--rollout-wait フラグを付けると、ECS Service の Rolling Update が完了して RUNNING タスク数が desiredCount に達するまで CLI がブロックする。GitHub Actions では ecspresso deploy --rollout-wait を推奨する。デプロイ失敗時 (HealthCheck NG / タスク起動エラー) は非ゼロ終了コードで CI に伝達されるため、失敗検知が自動化される。
2. ecspresso v2 責任境界設計 — Terraform との5軸比較
ecspresso と Terraform を同一プロジェクトで併用するとき、どのリソースをどのツールが管理するかの境界が曖昧なままだと、デプロイ時に設定が競合したり、terraform apply が ECS Service の設定を意図せず上書きしたりする問題が起きる。特に ECS Service の desiredCount は、手動スケールアウト後に terraform apply を実行すると Terraform が tfstate の値に戻してしまう「構成ドリフト (configuration drift)」問題として現れやすい。
この章では責任境界を5軸で整理し、実装に必要な Terraform outputs.tf パターンと ecspresso v2 のデプロイフローを確認する。

- Terraform が担当: インフラ層 (VPC / ALB / ECS Cluster / ECR / IAM Role / Security Group / Log Group) — 変更頻度は低く、tfstate で一元管理する
- ecspresso が担当: サービス層 (ECS Service 定義 / Task Definition) のみ — デプロイごとに変更が発生する設定を管理する
- 境界接続: ecspresso は Terraform の outputs.tf 値を
{{ tfstate "output.xxx" }}記法で参照する。所有権は Terraform 側にあり、読み取りのみを ecspresso が行う
2-1. ecspresso v2 の責任境界
ecspresso v2 が管理するのは ECS Service と ECS Task Definition の2リソースのみだ。それ以外の AWS リソースには一切書き込みを行わない。これが Terraform との共存を可能にする設計の核心で、「ecspresso を追加したら既存 Terraform リソースが壊れた」という事態が構造的に起きない。
| AWS リソース | 管理ツール | 変更頻度 | 理由 |
|---|---|---|---|
| VPC / Subnet / IGW / Route Table | Terraform | 低 (月単位) | ネットワーク基盤。変更は稀で tfstate 管理が最適 |
| ALB / Target Group / Listener | Terraform | 低 (月単位) | ロードバランサは ECS Service と独立して作成。ecspresso は ARN を参照するのみ |
| ECS Cluster | Terraform | 低 (再作成は稀) | Cluster は ECS の基盤リソース。ecspresso はクラスター名/ARN を参照するのみ |
| ECR Repository | Terraform | 低 (初回のみ) | Repository は一度作れば恒久的。ecspresso は URL を push/pull で利用するのみ |
| IAM Role (Task / Execution) | Terraform | 低〜中 (権限追加時) | IAM Role は権限設計に関わるため変更履歴が重要。tfstate で一元管理する |
| Security Group | Terraform | 低 (要件変更時) | ポートルールはネットワーク設計の一部。TF で一元管理する |
| CloudWatch Log Group | Terraform | 低 (初回のみ) | ログ保持設定は運用ポリシーで決定。ECS デプロイとは独立している |
| ECS Service | ecspresso | 高 (デプロイごと) | desiredCount / deploymentConfig はデプロイごとに変わる。TF 管理では drift 問題が起きやすい |
| ECS Task Definition | ecspresso | 高 (デプロイごと) | コンテナイメージタグはデプロイごとに変わる。TF state に image tag を持つと CI/CD が複雑化する |
ecspresso v2 が管理するリソースの詳細:
aws_ecs_service— desiredCount / deploymentConfiguration / loadBalancers / networkConfiguration / serviceRegistries / capacityProviderStrategyaws_ecs_task_definition— コンテナイメージタグ / CPU・メモリ割り当て / 環境変数 / シークレット参照 / ボリューム設定 / ログ設定
ecspresso は Terraform が作成したリソースを 参照するだけ で所有権は Terraform 側にある。ecspresso.yml の cluster フィールドに {{ tfstate "output.cluster_arn" }} を書けば Terraform の outputs.tf から値を動的に取得できるため、Cluster ARN のハードコードが不要になる。Terraform 側でリソースを変更して terraform apply を実行すれば、次の ecspresso deploy で自動的に最新の ARN を参照する。
- 設定ファイル形式の変更: v1 の
ecspresso.ymlではcluster/service/regionを文字列で直書きするケースが多かったが、v2 ではregionを省略して環境変数 (AWS_DEFAULT_REGION) から取得する構成が標準になった。v1 の設定をそのまま v2 で使うと region 解決の順序が変わり接続先が意図と違うリージョンになる場合がある - JSON テンプレート → jsonnet 移行: v1 で使っていた
{{ }}テンプレート記法は v2 では jsonnet のstd.extVar()に置き換わる。v1 形式のecs-service-def.jsonはそのまま動くが、jsonnet 機能 (条件分岐・変数化) は使えない。新規作成は.jsonnet拡張子を推奨する - –latest-task-definition の廃止: v1 の
--latest-task-definitionフラグは v2 で削除された。v2 ではecs-task-def.jsonnetに image タグを明示的に記述するか、std.extVar("IMAGE_TAG")で CI/CD から外部注入する方式に変更する - ecspresso run の引数変更:
ecspresso runコマンドの--task-defフラグ構文が v2 で変更された。v1 の記法がそのまま動かないケースがあるため、ecspresso run --helpで現在の仕様を確認してから CI/CD を更新すること - tfstate プラグインの設定キー変更: v1 では
url:だったリモートバックエンドの S3 設定が、v2 ではbackend: s3+backend_config:構造に変わった。ecspresso.ymlの plugins セクションを v2 形式に書き直す必要がある
2-2. Terraform + ecspresso + 代替ツール 5軸比較表
| 比較軸 | Terraform単独 | ecspresso+TF | AWS Copilot | AWS CDK | CodePipeline |
|---|---|---|---|---|---|
| 責任範囲 | インフラ+ECS Service/TaskDef 全管理 | TF=インフラ / ecspresso=ECS Service+TaskDef | 全スタック (VPC〜Service) を自動管理 | TypeScript/Pythonで全リソース定義 | CI/CDパイプライン全体を管理 |
| 学習コスト | 中 (HCL習得が必要) | 低〜中 (HCL+JSON/jsonnet) | 中 (Copilot CLI+manifest.yml) | 高 (CDK API+TypeScript/Python) | 高 (複数サービス連携) |
| TF共存性 | ◎ (当然) | ◎ ECS層のみ担当・競合なし | △ VPC/Clusterを上書きしやすい | △ TFとの二重管理になりがち | ○ Pipelineリソースは TF管理可 |
| デプロイ速度 | 遅 (plan→applyが毎回必要) | 速 (ecspresso deploy は単一CLI) | 中 (複数API呼び出し) | 中〜遅 (Synth+CloudFormation) | 中〜遅 (Pipeline起動オーバーヘッド) |
| カスタマイズ性 | ◎ HCLで全リソース制御可 | ◎ jsonnetで柔軟な環境別設定 | △ manifest の範囲内に限定 | ◎ コードで全制御可 | ○ buildspec.yml で拡張可 |
Terraform 既存環境では ecspresso+TF の組み合わせが最優先候補だ。Copilot と CDK は既存 Terraform リソースとの競合リスクが高い。CodePipeline は Pipeline 構築コストが高く、小〜中規模では ecspresso+GitHub Actions の方が運用負荷が低い。ECS 専用ツールとして必要十分な機能を持ちながら、他ツールへの干渉がゼロなのが ecspresso の根本的な優位点だ。
なお「Terraform 単独で ECS Service まで管理する」選択肢も有力だ。デプロイのたびに terraform apply が必要になる点と、image タグ変更のたびに TF 差分が出る点を許容できるチームなら、Terraform 単独でも運用できる。ecspresso を選ぶのは「ECS デプロイをより速く・安全に行いたい」という明確な要件がある場合だ。
2-3. outputs.tf による境界接続パターン
Terraform が管理するリソースの ARN や URL を ecspresso に渡すには、outputs.tf で値を公開し、ecspresso の tfstate プラグインで参照する。outputs.tf は ecspresso との 契約インターフェース として機能する。outputs.tf に定義した値が変わると、次の ecspresso deploy で自動的に最新値が使われる。Terraform モジュールを使っている場合は root モジュールの outputs.tf で子モジュールの outputs を再エクスポートする必要がある点に注意する。
# outputs.tf — ecspresso 参照用エクスポート (Terraform 1.9.x / AWS Provider 6.x)
output "ecs_cluster_arn" {
description = "ECS Cluster ARN — ecspresso.yml の cluster フィールドで参照"
value = aws_ecs_cluster.main.arn
}
output "alb_target_group_arn" {
description = "ALB Target Group ARN — ecs-service-def.jsonnet の loadBalancers で参照"
value = aws_lb_target_group.app.arn
}
output "ecr_repository_url" {
description = "ECR Repository URL — ecs-task-def.jsonnet の image ベース URL で参照"
value = aws_ecr_repository.app.repository_url
}
output "execution_role_arn" {
description = "ECS Task Execution Role ARN — ecs-task-def.jsonnet で参照"
value = aws_iam_role.ecs_execution.arn
}
output "task_role_arn" {
description = "ECS Task Role ARN — ecs-task-def.jsonnet で参照"
value = aws_iam_role.ecs_task.arn
}
output "log_group_name" {
description = "CloudWatch Log Group 名 — ecs-task-def.jsonnet の logConfiguration で参照"
value = aws_cloudwatch_log_group.app.name
}
ecspresso.yml 側では tfstate プラグインを設定し、S3 バックエンドのリモート State から値を取得する。ローカル tfstate ファイルを使う場合は path: で指定する:
region: ap-northeast-1
cluster: '{{ tfstate "output.ecs_cluster_arn" }}'
service: myapp-service
service_definition: ecs/ecs-service-def.jsonnet
task_definition: ecs/ecs-task-def.jsonnet
plugins:
- name: tfstate
config:
# リモートバックエンド (S3) の場合:
backend: s3
backend_config:
bucket: myapp-terraform-state
key: prd/terraform.tfstate
region: ap-northeast-1
# ローカル tfstate ファイルの場合 (開発環境など):
# path: ../terraform/terraform.tfstate
outputs.tf に出力を追加した後は必ず terraform apply を実行して tfstate を更新すること。ecspresso は tfstate ファイルから値を読み取るため、terraform output で確認できる状態でないと参照できない。初期構築時は terraform apply → terraform output で値確認 → ecspresso diff で差分なし確認、という順序で進める。
ecspresso diff がエラーなく実行できれば tfstate プラグインの接続が成功している証拠だ。差分出力に「ローカル設定と AWS 現在の状態が一致している」旨が表示されれば、次の ecspresso deploy の準備が整っている。
tfstate 参照構文の種類: ecspresso の {{ tfstate "..." }} は以下の 3 パターンをサポートする。
# ① outputs 参照 (推奨) — terraform output で確認できる値
cluster: '{{ tfstate "output.ecs_cluster_arn" }}'
# ② リソース属性参照 — outputs.tf を経由せず直接リソースを参照
cluster: '{{ tfstate "aws_ecs_cluster.main.arn" }}'
# ③ 動的フォーマット参照 — 環境変数を組み合わせて動的に参照
cluster: '{{ tfstatef "aws_ecs_cluster.%s.arn" .Env.CLUSTER_NAME }}'
outputs 参照 (①) を推奨する理由は、Terraform リソース名のリファクタリング (例: main → primary) に対して ecspresso 設定の変更が不要になるためだ。outputs.tf が「契約インターフェース」として機能し、Terraform の内部実装の変更から ecspresso を保護する。
なお、リスト型の outputs (例: subnet_ids) は {{ (tfstate "output.private_subnet_ids.value")[0] }} のようにインデックスアクセスで参照できる。jsonnet 側では std.extVar() との組み合わせでより柔軟な参照も可能だ。
複数環境 (dev / stg / prd) に対応する場合は、key: "envs/{{ must_env "ENV" }}/terraform.tfstate" のように環境変数で tfstate パスを切り替えるパターンが最もシンプルだ。
2-4. ecspresso v2 デプロイフロー (mermaid01)
sequenceDiagram
participant Dev as 開発者
participant GHA as GitHub Actions
participant ECR as Amazon ECR
participant ECS as ECS Cluster
Dev->>GHA: git push (PR open)
GHA->>GHA: ecspresso diff → PR コメント自動投稿
Dev->>GHA: PR merge to main
GHA->>ECR: docker build & push (SHA tag)
GHA->>ECS: ecspresso deploy --rollout-wait
ECS-->>GHA: Service 安定確認 (HealthCheck PASS)
GHA-->>Dev: デプロイ完了通知
PR を開いた時点で ecspresso diff が走り、「ECS Service / Task Definition の何が変わるか」を PR コメントで可視化する。レビュアーはコード変更と ECS 設定変更を同じ GitHub 画面で確認できる。差分が意図通りであることを確認してからマージするフローが、ECS 設定の意図しない変更を防ぐ。
マージ後は ECR へのイメージプッシュ (SHA タグ固定) と ecspresso deploy が順次実行される。--rollout-wait オプションにより、ECS Service の Rolling Update が完了して RUNNING タスク数が desiredCount に達するまで GitHub Actions ジョブがブロックされる。デプロイ失敗 (HealthCheck NG / タスク起動失敗) は CI レベルで検出でき、失敗した場合は ecspresso rollback で前のタスク定義に戻すステップを後続 job として追加できる。
イメージタグに latest を使うとデプロイのたびに ecspresso diff で差分が出なくなるため、必ず SHA タグ (または semver タグ) を使うことを推奨する。${{ github.sha }} で GitHub Actions から SHA を取得し、docker build -t myapp:$SHA → ecspresso deploy 時に std.extVar("IMAGE_TAG") で注入する構成が標準だ。
デプロイ失敗時のロールバックは ecspresso rollback 1 コマンドで完了する。ECS の直前タスク定義に即時切り戻しできるため、Blue/Green 以外の Rolling デプロイでも迅速な復旧が可能だ。ecspresso deploy が --rollout-wait でタイムアウトした場合も、後続 GitHub Actions ステップで ecspresso rollback を自動実行する構成を推奨する。
2-5. セキュリティグループ — Provider 6.x 新仕様での定義
Terraform AWS Provider 6.x では、Security Group のルール定義が独立リソースに分離された。ECS タスクが ALB からの通信を受け、ECR / CloudWatch Logs へのアウトバウンドを行うためのルール設定は以下の形式になる。
# Security Group 本体 (Provider 6.x: インライン ingress / egress ブロックなし)
resource "aws_security_group" "ecs" {
name = "${var.app_name}-ecs"
description = "Security group for ECS tasks"
vpc_id= aws_vpc.main.id
tags = {
Name = "${var.app_name}-ecs"
Managed = "terraform"
}
}
# ALB セキュリティグループからの受信を許可 (aws_vpc_security_group_ingress_rule)
resource "aws_vpc_security_group_ingress_rule" "ecs_from_alb" {
security_group_id= aws_security_group.ecs.id
referenced_security_group_id = aws_security_group.alb.id
from_port = 8080
to_port = 8080
ip_protocol= "tcp"
description= "Allow traffic from ALB to ECS tasks"
}
# アウトバウンド全許可 (ECR pull / CloudWatch Logs / Secrets Manager 等)
resource "aws_vpc_security_group_egress_rule" "ecs_all" {
security_group_id = aws_security_group.ecs.id
cidr_ipv4= "0.0.0.0/0"
ip_protocol = "-1"
description = "Allow all outbound from ECS tasks"
}
aws_security_group リソース内の ingress {} / egress {} ブロック (インライン形式) はAWS Provider 6.x で非推奨 (deprecated) となった。既存 HCL からの移行手順:
- 既存
aws_security_groupの inlineingress / egressブロックを削除 aws_vpc_security_group_ingress_rule/egress_ruleリソースを追加terraform planで SG の再作成が発生しないことを確認してからterraform apply
- Terraform 管理リソース確認: VPC / Subnet / IGW / Route Table / ALB / Target Group / ECS Cluster / ECR / IAM Role / Security Group / Log Group がすべて tfstate で管理されているか —
terraform state listで確認する - ecspresso 管理リソース除外確認:
terraform state listにaws_ecs_service/aws_ecs_task_definitionが含まれていないか — 含まれている場合はterraform state rmで ecspresso 管理に移譲する - outputs.tf 公開確認: ecs_cluster_arn / alb_target_group_arn / ecr_repository_url / execution_role_arn / task_role_arn が
terraform outputで取得できるか確認する - tfstate プラグイン接続確認:
ecspresso.ymlのplugins[].name: tfstateとbackend_configが正しく設定されているか —ecspresso diffでエラーなく差分取得できれば接続成功 - v2 バージョン確認:
ecspresso versionで v2.4.x 以上であることを確認 — v1 の設定ファイルは v2 でそのまま動かないケースがある - Provider 6.x SG確認:
aws_vpc_security_group_ingress_rule/egress_ruleを使用し、inline ingress / egress ブロックが残っていないか確認する - デプロイ事前テスト: 本番適用前に
ecspresso diffで差分が意図通りか確認し、ステージング環境でecspresso deploy --rollout-waitを実行してから本番に展開する
3. Terraform 基盤 × ecspresso × jsonnet 完全実装

ecspresso は ECS Service と Task Definition のライフサイクルを管理するが、その基盤となる VPC・ALB・ECS Cluster・ECR・IAM は Terraform で先に構築する。本章では Terraform 1.9.x + AWS Provider 6.x の最小構成を組み上げ、ecspresso 初期化から jsonnet 変数化まで一気通貫で実装する手順を示す。
3-1. Terraform 1.9.x + AWS Provider 6.x 基盤構築
Provider 宣言で version = "~> 6.0" を指定する。VPC・Subnet・IGW・Route Table は標準パターンで構築し、SG ルール定義のみ Provider 6.x の新記法に移行する。
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" { region = "ap-northeast-1" }
locals {
common_tags = { Project = "ecspresso-demo", Environment = var.environment }
}
Provider 6.x では
aws_security_group のインラインブロック (ingress / egress) が非推奨となり、独立リソース aws_vpc_security_group_ingress_rule / aws_vpc_security_group_egress_rule への移行が推奨される。インラインブロックを残したまま独立リソースを追加するとルールが重複登録されてしまうため、移行時はインラインブロックを先に削除してから独立リソースを追加する。# ALB SG — Provider 6.x 推奨: 独立リソースで ingress/egress を定義
resource "aws_security_group" "alb" {
name= "ecspresso-demo-alb-sg"
vpc_id = aws_vpc.main.id
tags= merge(local.common_tags, { Name = "ecspresso-demo-alb-sg" })
}
resource "aws_vpc_security_group_ingress_rule" "alb_http" {
security_group_id = aws_security_group.alb.id
from_port= 80
to_port = 80
ip_protocol = "tcp"
cidr_ipv4= "0.0.0.0/0"
}
resource "aws_vpc_security_group_egress_rule" "alb_all" {
security_group_id = aws_security_group.alb.id
ip_protocol = "-1"
cidr_ipv4= "0.0.0.0/0"
}
# ECS Task SG — ALB からのトラフィックのみ許可
resource "aws_security_group" "ecs_task" {
name= "ecspresso-demo-ecs-task-sg"
vpc_id = aws_vpc.main.id
tags= merge(local.common_tags, { Name = "ecspresso-demo-ecs-task-sg" })
}
resource "aws_vpc_security_group_ingress_rule" "ecs_task_from_alb" {
security_group_id= aws_security_group.ecs_task.id
from_port = 8080
to_port = 8080
ip_protocol= "tcp"
referenced_security_group_id = aws_security_group.alb.id
}
resource "aws_vpc_security_group_egress_rule" "ecs_task_all" {
security_group_id = aws_security_group.ecs_task.id
ip_protocol = "-1"
cidr_ipv4= "0.0.0.0/0"
}
ALB・ECS Cluster・ECR・IAM は標準パターンで定義する。outputs.tf に ecspresso が参照する値を出力する。
# outputs.tf — ecspresso の tfstate プラグインで参照する値
output "cluster_arn" { value = aws_ecs_cluster.main.arn }
output "alb_target_group_arn" { value = aws_lb_target_group.app.arn }
output "ecr_repository_url" { value = aws_ecr_repository.app.repository_url }
output "task_role_arn"{ value = aws_iam_role.ecs_task_role.arn }
output "execution_role_arn" { value = aws_iam_role.ecs_execution_role.arn }
output "private_subnet_ids" { value = [aws_subnet.private_a.id, aws_subnet.private_c.id] }
output "ecs_task_sg_id" { value = aws_security_group.ecs_task.id }
terraform apply 後は terraform output で各値を確認し、次節の ecspresso 設定に進む。
3-2. ecspresso v2 初期化 + 設定 3 ファイル
Terraform で基盤を構築したら ecspresso init で設定ファイルを自動生成し、tfstate プラグイン連携に書き換える。
ecspresso init \
--config ecspresso.yml \
--region ap-northeast-1 \
--cluster $(terraform -chdir=terraform output -raw cluster_arn) \
--service ecspresso-demo-svc
# ecspresso.yml — tfstate プラグインで Terraform output を参照
region: ap-northeast-1
cluster: '{{ tfstate "output.cluster_arn" }}'
service: ecspresso-demo-svc
service_definition: ecs-service-def.jsonnet
task_definition: ecs-task-def.jsonnet
timeout: 10m
plugins:
- name: tfstate
config:
path: ../terraform/terraform.tfstate
ecspresso diff --config ecspresso.yml を実行すると AWS 上の現在設定とローカル jsonnet の差分が unified diff 形式で表示される。差分なし = 終了コード 0、差分あり = 終了コード 1 が返るため、CI で ecspresso diff || ecspresso deploy とパイプすると差分があるときのみデプロイできる。3-3. jsonnet 変数化 — base.libsonnet + env 別設定
dev / stg / prd で JSON を 3 コピーすると変更漏れが生じる。jsonnet の import + + マージで共通設定を 1 ファイルに集約し、環境差分だけを env/ 以下で上書きする。
ecspresso/
├── ecspresso.yml
├── ecs-service-def.jsonnet
├── ecs-task-def.jsonnet
├── base.libsonnet
└── env/
├── dev.libsonnet
└── prd.libsonnet
// base.libsonnet — 全環境共通設定
{
containerName: 'app',
containerPort: 8080,
imageUri: std.extVar('ECR_IMAGE'),
environment: std.extVar('ENVIRONMENT'),
logGroup: '/ecs/ecspresso-demo',
region: 'ap-northeast-1',
cpu: '256',
memory: '512',
desiredCount: 1,
}
// env/dev.libsonnet // env/prd.libsonnet
{ cpu: '256', { cpu: '512',
memory: '512', memory: '1024',
desiredCount: 1, desiredCount: 3,
logGroup: '/ecs/ecspresso-demo-dev', }
}
// ecs-service-def.jsonnet
local base = import 'base.libsonnet';
local env = std.extVar('ENVIRONMENT');
local envConfig = import ('env/' + env + '.libsonnet');
local config = base + envConfig;
{
desiredCount: config.desiredCount,
launchType: 'FARGATE',
networkConfiguration: {
awsvpcConfiguration: {
assignPublicIp: 'DISABLED',
securityGroups: [std.extVar('ECS_TASK_SG_ID')],
subnets: std.split(std.extVar('SUBNET_IDS'), ','),
},
},
loadBalancers: [{
containerName: config.containerName,
containerPort: config.containerPort,
targetGroupArn: std.extVar('TARGET_GROUP_ARN'),
}],
deploymentConfiguration: { maximumPercent: 200, minimumHealthyPercent: 100 },
deploymentController: { type: 'ECS' },
}
// ecs-task-def.jsonnet
local base = import 'base.libsonnet';
local env = std.extVar('ENVIRONMENT');
local envConfig = import ('env/' + env + '.libsonnet');
local config = base + envConfig;
// 設定パラメータ ARN を環境別プレフィックスで展開する例
local paramPrefix = 'arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:ecspresso-demo/' + env;
{
family: 'ecspresso-demo-' + env,
cpu: config.cpu,
memory: config.memory,
networkMode: 'awsvpc',
requiresCompatibilities: ['FARGATE'],
executionRoleArn: std.extVar('EXECUTION_ROLE_ARN'),
taskRoleArn: std.extVar('TASK_ROLE_ARN'),
containerDefinitions: [{
name: config.containerName,
image: config.imageUri,
portMappings: [{ containerPort: config.containerPort, protocol: 'tcp' }],
essential: true,
environment: [{ name: 'APP_ENV', value: config.environment }],
secrets: [
{ name: 'DATABASE_PASSWORD', valueFrom: paramPrefix + '/db-password' },
{ name: 'API_KEY', valueFrom: paramPrefix + '/api-key' },
],
logConfiguration: {
logDriver: 'awslogs',
options: {
'awslogs-group': config.logGroup,
'awslogs-region': config.region,
'awslogs-stream-prefix': 'ecs',
},
},
}],
}
import ('env/' + env + '.libsonnet') は ecspresso の実行ディレクトリを基点に解決される。ecspresso/ ディレクトリ外から実行すると RUNTIME ERROR: couldn't open import "env/dev.libsonnet" が出る。対策は 2 つ: (1) cd ecspresso/ && ecspresso deploy ... と実行ディレクトリを合わせる。(2) GitHub Actions では step に working-directory: ./ecspresso を追加するのが最も確実だ。# dev 環境へのデプロイ実行例
cd ecspresso/
ecspresso deploy --config ecspresso.yml \
--ext-str ENVIRONMENT=dev \
--ext-str ECR_IMAGE=123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ecspresso-demo-app:v1.0.0 \
--ext-str EXECUTION_ROLE_ARN=$(terraform -chdir=../terraform output -raw execution_role_arn) \
--ext-str TASK_ROLE_ARN=$(terraform -chdir=../terraform output -raw task_role_arn) \
--ext-str ECS_TASK_SG_ID=$(terraform -chdir=../terraform output -raw ecs_task_sg_id) \
--ext-str "SUBNET_IDS=$(terraform -chdir=../terraform output -json private_subnet_ids | jq -r 'join(",")' )" \
--ext-str TARGET_GROUP_ARN=$(terraform -chdir=../terraform output -raw alb_target_group_arn)
4. GitHub Actions OIDC CI/CD 最新パターン
IAM User のアクセスキーを GitHub Secrets に保管する旧来パターンは鍵漏洩リスクがある。本章では OIDC (OpenID Connect) を使って長期クレデンシャル不要の CI/CD パイプラインを Terraform + GitHub Actions で実装する。
4-1. OIDC Trust Policy — Terraform HCL
- 一時クレデンシャルのみ使用: GitHub Actions が OIDC トークンを発行し、AWS STS が短期間の一時トークンに交換する。長期クレデンシャルが GitHub に保存されることはない
- リポジトリ単位の絞り込み: Trust Policy の
sub条件でブランチ・タグなど発火条件を細かく制御できる - ローテーション不要: アクセスキーが存在しないため鍵管理・失効リスクが発生しない
# OIDC Provider + デプロイ用 IAM Role
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
resource "aws_iam_role" "gha_deploy" {
name = "gha-ecspresso-deploy"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Federated = aws_iam_openid_connect_provider.github.arn }
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:myorg/myrepo:*"
}
}
}]
})
tags = local.common_tags
}
resource "aws_iam_role_policy_attachment" "gha_deploy_ecs" {
role = aws_iam_role.gha_deploy.name
policy_arn = "arn:aws:iam::aws:policy/AmazonECS_FullAccess"
}
output "gha_deploy_role_arn" { value = aws_iam_role.gha_deploy.arn }
- StringEquals vs StringLike の誤用:
subにワイルドカード (repo:myorg/myrepo:*) を含む場合はStringLikeを使う。StringEqualsではワイルドカードが文字列として評価され完全一致のみ許可となる - aud 条件の省略:
token.actions.githubusercontent.com:aud = sts.amazonaws.comの条件を省略すると意図しないサービスからの AssumeRole を許可してしまう。必ず明示する - サムプリントの陳腐化:
thumbprint_listは GitHub OIDC プロバイダーの TLS 証明書ルート CA フィンガープリントだ。証明書更新時に変わるためaws iam get-open-id-connect-providerで現在値を確認して最新化する
4-2. PR ワークフロー: ecspresso diff を PR コメントに自動投稿
PR マージ前に ecspresso diff でインフラ差分を可視化し、レビュアーが変更内容を確認できるようにする。
# .github/workflows/ecspresso-diff.yml
name: ecspresso diff
on:
pull_request:
branches: [main]
paths: ['ecspresso/**', 'terraform/**']
permissions:
id-token: write
contents: read
pull-requests: write
jobs:
diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
aws-region: ap-northeast-1
- name: ecspresso diff
id: diff
working-directory: ecspresso
run: |
diff_output=$(ecspresso diff --config ecspresso.yml \
--ext-str ENVIRONMENT=dev \
--ext-str ECR_IMAGE=dummy \
--ext-str EXECUTION_ROLE_ARN=${{ secrets.EXECUTION_ROLE_ARN }} \
--ext-str TASK_ROLE_ARN=${{ secrets.TASK_ROLE_ARN }} \
--ext-str ECS_TASK_SG_ID=${{ secrets.ECS_TASK_SG_ID }} \
--ext-str SUBNET_IDS=${{ secrets.SUBNET_IDS }} \
--ext-str TARGET_GROUP_ARN=${{ secrets.TARGET_GROUP_ARN }} \
2>&1 || true)
echo "diff_output<<EOF" >> $GITHUB_OUTPUT
echo "$diff_output" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: PR コメント投稿
uses: actions/github-script@v7
with:
script: |
const diff = `${{ steps.diff.outputs.diff_output }}`;
const body = diff.trim()
? `## ecspresso diff\n\`\`\`diff\n${diff}\n\`\`\``
: '## ecspresso diff\n差分なし';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
});
4-3. merge デプロイワークフロー: terraform apply + ecspresso deploy
main ブランチへのマージをトリガーに、Terraform でインフラを更新してから ecspresso でアプリをデプロイする。
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
paths: ['ecspresso/**', 'terraform/**', 'Dockerfile']
permissions:
id-token: write
contents: read
env:
AWS_REGION: ap-northeast-1
ECR_REPOSITORY: ecspresso-demo-app
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- uses: aws-actions/amazon-ecr-login@v2
id: ecr-login
- name: Docker build and push
env:
ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:${{ github.sha }} .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:${{ github.sha }}
echo "IMAGE_URI=$ECR_REGISTRY/$ECR_REPOSITORY:${{ github.sha }}" >> $GITHUB_ENV
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.9.x"
- name: Terraform init and apply
working-directory: terraform
run: |
terraform init
terraform apply -auto-approve -var="environment=prd"
- name: ecspresso deploy
working-directory: ecspresso
run: |
ecspresso deploy --config ecspresso.yml \
--ext-str ENVIRONMENT=prd \
--ext-str ECR_IMAGE=${{ env.IMAGE_URI }} \
--ext-str EXECUTION_ROLE_ARN=${{ secrets.EXECUTION_ROLE_ARN }} \
--ext-str TASK_ROLE_ARN=${{ secrets.TASK_ROLE_ARN }} \
--ext-str ECS_TASK_SG_ID=${{ secrets.ECS_TASK_SG_ID }} \
--ext-str SUBNET_IDS=${{ secrets.SUBNET_IDS }} \
--ext-str TARGET_GROUP_ARN=${{ secrets.TARGET_GROUP_ARN }}
- name: Post-deploy diff check
working-directory: ecspresso
run: |
ecspresso diff --config ecspresso.yml \
--ext-str ENVIRONMENT=prd \
--ext-str ECR_IMAGE=${{ env.IMAGE_URI }} \
--ext-str EXECUTION_ROLE_ARN=${{ secrets.EXECUTION_ROLE_ARN }} \
--ext-str TASK_ROLE_ARN=${{ secrets.TASK_ROLE_ARN }} \
--ext-str ECS_TASK_SG_ID=${{ secrets.ECS_TASK_SG_ID }} \
--ext-str SUBNET_IDS=${{ secrets.SUBNET_IDS }} \
--ext-str TARGET_GROUP_ARN=${{ secrets.TARGET_GROUP_ARN }} \
&& echo "Deploy verified: no config drift"
- [ ]
aws_iam_openid_connect_provider.githubのthumbprint_listが最新か確認 - [ ] Trust Policy の
sub条件がリポジトリ名と一致しているか確認 - [ ] Trust Policy に
aud = sts.amazonaws.com条件が含まれているか確認 - [ ] GitHub Secrets に
AWS_DEPLOY_ROLE_ARNが登録されているか確認 - [ ] ワークフローの
permissionsブロックにid-token: writeがあるか確認 - [ ]
aws-actions/configure-aws-credentials@v4を使用しているか確認 (v3 以前は禁止) - [ ]
aws-actions/amazon-ecr-login@v2を使用しているか確認 - [ ] デプロイ後の
ecspresso diffでドリフトがないか確認
5. Blue/Green デプロイ + CodeDeploy 連携
ECS の本番運用でダウンタイムゼロと即時ロールバックを両立するには Blue/Green デプロイが最適解だ。ALB の 2 系統ターゲットグループ (Blue=現行 / Green=新版) を CodeDeploy が制御することで、新バージョンの動作確認後にトラフィックを瞬時に切り替える。問題発生時は数十秒で旧バージョンへ戻せるため、本番 API サービスで広く採用されている。

5-1. デプロイ戦略の選択基準
Rolling / Blue-Green / Canary の 3 戦略はそれぞれ用途が異なる。ダウンタイム許容度・ロールバック要件・運用コストに基づいて選択する。
- Rolling: 停止許容あり / シンプル構成 / Fargate 標準機能で追加リソース不要 / 開発環境向け
- Blue/Green: 無停止必須 / 即時ロールバック要件 / CodeDeploy 統合が必要 / 本番推奨
- Canary: 段階的トラフィック移行 / リスク最小化 / CodeDeploy 設定が複雑 / フロントエンド向け
| 判断軸 | Rolling | Blue/Green | Canary |
|---|---|---|---|
| 無停止保証 | △ (DRAINING依存) | ○ | ○ |
| ロールバック速度 | 遅い (再deploy) | 即時 (ALB切替) | 段階的 |
| CodeDeploy必要 | 不要 | 必要 | 必要 |
| ALB TargetGroup | 1本 | 2本必須 | 2本必須 |
| 設定複雑度 | 低 | 中 | 高 |
| 自動ロールバック | Circuit Breaker | alarm連動可 | alarm連動可 |
| 本番推奨 | △ | ○ | 条件付き |
5-2. Terraform HCL — CodeDeploy リソース定義
Blue/Green デプロイには ALB TargetGroup 2本 + CodeDeploy アプリ/デプロイグループ + IAM Role の 3 リソースセットが必要だ。Terraform 1.9.x / AWS Provider 6.x での定義を示す。
ALB TargetGroup 2系統 (blue + green)
resource "aws_lb_target_group" "blue" {
name = "myapp-blue"
port = 8080
protocol = "HTTP"
vpc_id= aws_vpc.main.id
target_type = "ip"
health_check {
path = "/health"
healthy_threshold= 2
unhealthy_threshold = 3
interval= 30
timeout = 5
matcher = "200"
}
tags = { Env = "prd", Role = "blue" }
}
resource "aws_lb_target_group" "green" {
name = "myapp-green"
port = 8080
protocol = "HTTP"
vpc_id= aws_vpc.main.id
target_type = "ip"
health_check {
path = "/health"
healthy_threshold= 2
unhealthy_threshold = 3
interval= 30
timeout = 5
matcher = "200"
}
tags = { Env = "prd", Role = "green" }
}
CodeDeploy App + Deployment Group
resource "aws_codedeploy_app" "myapp" {
compute_platform = "ECS"
name = "myapp-ecs"
}
resource "aws_codedeploy_deployment_group" "myapp" {
app_name= aws_codedeploy_app.myapp.name
deployment_group_name = "myapp-blue-green"
service_role_arn = aws_iam_role.codedeploy.arn
deployment_config_name = "CodeDeployDefault.ECSAllAtOnce"
auto_rollback_configuration {
enabled = true
events = ["DEPLOYMENT_FAILURE"]
}
blue_green_deployment_config {
deployment_ready_option {
action_on_timeout = "CONTINUE_DEPLOYMENT"
}
terminate_blue_instances_on_deployment_success {
action= "TERMINATE"
termination_wait_time_in_minutes = 5
}
}
deployment_style {
deployment_option = "WITH_TRAFFIC_CONTROL"
deployment_type= "BLUE_GREEN"
}
ecs_service {
cluster_name = aws_ecs_cluster.main.name
service_name = aws_ecs_service.myapp.name
}
load_balancer_info {
target_group_pair_info {
prod_traffic_route {
listener_arns = [aws_lb_listener.https.arn]
}
target_group { name = aws_lb_target_group.blue.name }
target_group { name = aws_lb_target_group.green.name }
}
}
tags = { Env = "prd" }
}
CodeDeploy 用 IAM Role
resource "aws_iam_role" "codedeploy" {
name = "myapp-codedeploy-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = { Service = "codedeploy.amazonaws.com" }
Action = "sts:AssumeRole"
}
]
})
tags = { Env = "prd" }
}
resource "aws_iam_role_policy_attachment" "codedeploy_ecs" {
role = aws_iam_role.codedeploy.name
policy_arn = "arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS"
}
Blue/Green デプロイでは ALB TargetGroup が2本必要 (blue用 + green用)。
1本しか定義していないと CodeDeploy がデプロイグループ作成時にエラーになる。
ecspresso.yml の codedeploy 設定と TargetGroup 名を完全一致させること。
- NG:
aws_lb_target_group.blueのみ定義 → デプロイグループ作成時 ValidationError - OK: blue + green の 2本を
target_group_pair_infoブロック内に並べて指定
5-3. ecspresso.yml — codedeploy 設定
ecspresso では codedeploy ブロックで CodeDeploy との連携設定を行う。deployment_config_name を変更するだけで Blue/Green 即時切替 / Canary 段階切替を切り替えられる。
ecspresso.yml (Blue/Green 設定)
region: ap-northeast-1
cluster: "{{ must_env `ECS_CLUSTER` }}"
service: "{{ must_env `ECS_SERVICE` }}"
service_definition: ecs-service-def.jsonnet
task_definition: ecs-task-def.jsonnet
codedeploy:
application_name: myapp-ecs
deployment_group_name: myapp-blue-green
app_spec:
task_definition: "{{ .TaskDefinitionArn }}"
container_name: myapp
container_port: 8080
deployment_config_name 一覧 (Blue/Green → Canary 切替)
| 設定名 | 切替方式 | 用途 |
|---|---|---|
CodeDeployDefault.ECSAllAtOnce | 即時全量 | Blue/Green 標準 |
CodeDeployDefault.ECSLinear10PercentEvery1Minutes | 10%/分 × 10分 | 段階的 Canary |
CodeDeployDefault.ECSLinear10PercentEvery3Minutes | 10%/3分 × 30分 | 慎重な段階リリース |
CodeDeployDefault.ECSCanary10Percent5Minutes | 最初10%→5分後全量 | 軽量 Canary |
CodeDeployDefault.ECSCanary10Percent15Minutes | 最初10%→15分後全量 | 安定確認型 Canary |
ECS サービス定義 — CODE_DEPLOY コントローラ指定
Blue/Green を有効にするには ECS サービスの deploymentController.type を CODE_DEPLOY に設定する (詳細は §5-6 参照)。loadBalancers には blue 側の TargetGroup ARN を指定し、green 側は CodeDeploy が自動で管理する。
5-4. appspec.yml — ECS Blue/Green 定義
appspec.yml は CodeDeploy がデプロイ時に参照する設定ファイルだ。ContainerName と ContainerPort を TaskDef の定義と完全一致させることが最重要ポイントだ。
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: "myapp"
ContainerPort: 8080
PlatformVersion: "LATEST"
NetworkConfiguration:
AwsvpcConfiguration:
Subnets:
- "subnet-xxxxxxxx"
- "subnet-yyyyyyyy"
SecurityGroups:
- "sg-xxxxxxxx"
AssignPublicIp: "DISABLED"
- 症状: Blue/Green デプロイが
FAILED: AppSpec content is invalidで即座に失敗 - 原因1:
ContainerNameが TaskDef のnameフィールドと不一致 (大文字/小文字の差異も不一致と判定) - 原因2:
ContainerPortが TaskDef のportMappings[].containerPortと不一致
TaskDef の値を確認するコマンド:
aws ecs describe-task-definition --task-definition myapp \
--query 'taskDefinition.containerDefinitions[].{name:name,port:portMappings[0].containerPort}'
出力の name / port と appspec.yml の ContainerName / ContainerPort を照合すること。
5-5. ecspresso deploy — Blue/Green 実行フロー
デプロイ実行
ecspresso deploy --config ecspresso.yml --rollout-wait
--rollout-wait を付けると CodeDeploy のデプロイ完了まで ecspresso がブロッキング待機する。CI/CD パイプラインでは必ずこのオプションを指定すること。
デプロイ状況確認
ecspresso deployments --config ecspresso.yml
実行中の CodeDeploy デプロイ ID、ステータス、切替済みトラフィック比率を確認できる。
Blue/Green 切替フロー
[ecspresso deploy 実行]
↓
[新 TaskDef 登録 → Green タスク起動]
↓
[Green タスクがヘルスチェック通過]
↓
[ALB: Blue → Green へトラフィック切替 (数秒)]
↓
[Blue タスク termination_wait_time_in_minutes 後 TERMINATE]
↓
[デプロイ完了]
方法1: CLI からの即時ロールバック
aws deploy stop-deployment \ --deployment-id d-XXXXXXXXX \ --auto-rollback-enabled \ --region ap-northeast-1
方法2: AWS Console からのロールバック
- AWS Console → CodeDeploy → デプロイグループ → 実行中のデプロイを選択
- 「デプロイを停止してロールバック」ボタンをクリック
- ALB ターゲットが Blue (旧) に切り戻されるまで待機 (2〜3分)
方法3: ecspresso によるタスク定義ロールバック
ecspresso rollback --config ecspresso.yml
ALB 切替 (方法1 or 2) と TaskDef 切戻し (方法3) を両方実施することで完全なロールバックが完了する。
auto_rollback_configuration の活用
DEPLOYMENT_FAILURE 時に自動でロールバックさせるには Terraform 定義に以下を追加する。
auto_rollback_configuration {
enabled = true
events = ["DEPLOYMENT_FAILURE"]
}
デプロイ失敗時の自動ロールバックが保証されるため、本番環境では必ず有効化すること。
5-6. Rolling デプロイ設定 (比較参考)
Blue/Green への移行前の動作検証や開発環境の低コスト運用には Rolling Update が適している。追加の AWS リソースが不要で設定量も最小限だ。
ecspresso.yml (Rolling — codedeploy なし)
region: ap-northeast-1
cluster: "{{ must_env `ECS_CLUSTER` }}"
service: "{{ must_env `ECS_SERVICE` }}"
service_definition: ecs-service-rolling.jsonnet
task_definition: ecs-task-def.jsonnet
ecs-service-rolling.jsonnet (Circuit Breaker 設定)
{
deploymentController: {
type: "ECS"
},
deploymentConfiguration: {
maximumPercent: 200,
minimumHealthyPercent: 100,
deploymentCircuitBreaker: {
enable: true,
rollback: true
}
},
desiredCount: 2,
launchType: "FARGATE"
}
| パラメータ | 推奨値 | 意味 |
|---|---|---|
maximumPercent | 200 | 既存タスク数の最大 200% まで一時起動可 |
minimumHealthyPercent | 100 | ヘルシーなタスクを desiredCount の 100% 維持 |
deploymentCircuitBreaker.enable | true | 連続失敗でデプロイを自動停止 |
deploymentCircuitBreaker.rollback | true | 自動停止後に前リビジョンへロールバック |
Rolling → Blue/Green 移行時の注意点
既存 ECS サービスの deploymentController.type は作成後に変更できない。Rolling から Blue/Green へ移行するにはサービスを一度削除して再作成する必要がある。
resource "aws_ecs_service" "myapp" {
# ...
lifecycle {
create_before_destroy = true
}
}
5-7. 本番運用でのデプロイ戦略選定フロー
- ✅ 無停止要件あり → Blue/Green 一択
- ✅ 即時ロールバック必須 → Blue/Green +
auto_rollback_configuration { events = ["DEPLOYMENT_FAILURE"] } - ✅ 段階的トラフィック移行が必要 → Canary (
ECSLinear10Percent系を選択) - ✅ 開発環境・低コスト運用 → Rolling + Circuit Breaker
- ✅ CodeDeploy 導入済みなら Canary も同一 Terraform リソースで対応可 (deployment_config_name 変更のみ)
- ⚠️ Rolling → Blue/Green 移行はサービス再作成が必要 (deploymentController 変更不可)
- ⚠️ Blue/Green は ALB TargetGroup 2本必須。1本のみでは CodeDeploy エラー
- ⚠️ appspec.yml の ContainerName/ContainerPort は TaskDef と完全一致必須 (大文字/小文字区別あり)
6. 詰まりポイント10選 — ecspresso v2 × Terraform × CodeDeploy
- ケース1: ecspresso init 失敗 (–cluster ARN 未指定)
- ケース2: diff で差分検知漏れ (TaskDef INACTIVE 大量)
- ケース3: deploy Stuck (HealthCheck 失敗 / タスク STOPPED ループ)
- ケース4: CodeDeploy FAILED (AppSpec containerName 不一致)
- ケース5: jsonnet 生成エラー (import パス解決失敗)
- ケース6: GitHub Actions OIDC 認証失敗 (Trust Policy Condition 不一致)
- ケース7: ECR push 失敗 (IAM 権限不足)
- ケース8: Blue/Green ロールバック手順 (CodeDeploy コンソール操作)
- ケース9: ecspresso rollback でバージョン確認
- ケース10: Terraform と ecspresso の状態ズレ復旧
ケース1: ecspresso init 失敗 (–cluster ARN 未指定)
Error: InvalidParameterException: cluster not found が返り、ecspresso init が完了しない。原因
一部のリージョンではクラスター名だけでは解決できず、ARN での指定が必要。ecspresso v2.4.x では --cluster に ARN を直接渡すことが推奨される。Terraform 1.9.x の aws_ecs_cluster リソースは arn 属性を output に公開しているため、直接参照できる。
解決策
Terraform output から ARN を取得して ecspresso init に渡す。
# Terraform output からクラスター ARN を取得
CLUSTER_ARN=$(terraform -chdir=infra output -raw ecs_cluster_arn)
# ARN を明示して init
ecspresso init \
--cluster "$CLUSTER_ARN" \
--service myapp-service \
--config ecspresso.yml
terraform output -raw ecs_cluster_arn が空の場合は outputs.tf に value = aws_ecs_cluster.main.arn が定義されているか確認する。
ケース2: diff で差分検知漏れ (TaskDef INACTIVE 大量)
ecspresso diff で「差分なし」と報告されるが、デプロイ後に古い TaskDef が使われてしまう。原因
INACTIVE 状態の TaskDef が AWS 側に大量残存し、最新アクティブ版の検索に時間がかかる。Terraform Provider 6.x はデプロイのたびに新リビジョンを作成するため、管理しないと数百件規模になる。ecspresso diff がリビジョン検索でタイムアウトすると差分なしと誤判定する場合がある。
解決策
アクティブな最新 TaskDef を明示確認し、ecspresso verify で定義ファイルとの整合性をチェックする。
# アクティブな最新 TaskDef リビジョンを確認
aws ecs list-task-definitions \
--family-prefix myapp-task \
--status ACTIVE \
--sort DESC \
--query 'taskDefinitionArns[0]' \
--output text
# ecspresso verify で定義ファイルとの整合性をチェック
ecspresso verify --config ecspresso.yml
INACTIVE TaskDef が大量にある場合は定期的にクリーンアップする:
# INACTIVE な TaskDef を一覧表示
aws ecs list-task-definitions \
--family-prefix myapp-task \
--status INACTIVE \
--query 'taskDefinitionArns' \
--output json
# 特定リビジョンの登録解除 (1件ずつ)
aws ecs deregister-task-definition \
--task-definition myapp-task:10
ケース3: deploy Stuck (HealthCheck 失敗でタスクが STOPPED ループ)
ecspresso deploy が完了せずハングし、ECS コンソールで新タスクが STOPPED を繰り返す。原因
ALB TargetGroup のヘルスチェックパスが変更されたか、コンテナが /health を返せていない。ecspresso v2 のデフォルトタイムアウトは 10 分で、この間デプロイがブロックされる。コンテナ起動直後にクラッシュする場合はアプリログを確認する。
解決策
タスク停止理由を確認してからロールバックする。
# 実行中のタスク ARN を取得
TASK_ARN=$(aws ecs list-tasks \
--cluster myapp-cluster \
--query 'taskArns[0]' \
--output text)
# タスク停止理由を確認
aws ecs describe-tasks \
--cluster myapp-cluster \
--tasks "$TASK_ARN" \
--query 'tasks[0].stoppedReason' \
--output text
# デプロイを中断してロールバック (別ターミナルで実行)
ecspresso rollback --config ecspresso.yml
ヘルスチェックパスを修正する場合は Terraform 側の aws_lb_target_group を更新し terraform apply した後に再デプロイする。
ケース4: CodeDeploy FAILED (AppSpec containerName 不一致)
Blue/Green デプロイが
FAILED: AppSpec content is invalid で失敗する。原因appspec.yml の ContainerName / ContainerPort が TaskDef の定義と大文字小文字を含めて不一致。Terraform Provider 6.x の aws_ecs_task_definition は JSON の name フィールドをそのまま使用するため、テンプレート変数経由で値を取得する。
解決策
TaskDef の name フィールドと大文字小文字まで完全一致させる。
# appspec.yml — ContainerName/Port を TaskDef の定義と完全一致させる
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: "myapp" # TaskDef の name フィールドと完全一致 (大文字小文字区別あり)
ContainerPort: 8080 # TaskDef の portMappings.containerPort と一致
Terraform 管理下の場合は container_definitions.json の name を参照し、typo がないか確認する:
# TaskDef の containerName を確認
aws ecs describe-task-definition \
--task-definition myapp-task \
--query 'taskDefinition.containerDefinitions[*].name' \
--output json
ケース5: jsonnet 生成エラー (import パス解決失敗)
RUNTIME ERROR: couldn't open import "env/prd.libsonnet": no such file or directory原因jsonnet コマンドの実行ディレクトリが .jsonnet ファイルの配置ディレクトリと異なる。ecspresso v2 は内部で jsonnet を呼び出す際に作業ディレクトリの影響を受けるため、CI 環境でリポジトリルートから実行した場合に多発する。
解決策--jpath で import 検索パスを明示するか、ecspresso.yml の templateFile から相対パスを設定する。
# --jpath で import 検索パスを明示 (推奨)
ecspresso deploy \
--config ecspresso.yml \
--ext-str ENV=prd
# 実行前に jsonnet ファイルの場所を確認
ls -la ecspresso/*.jsonnet ecspresso/env/
# ecspresso.yml — templateFile 設定例
region: ap-northeast-1
cluster: myapp-cluster
service: myapp-service
service_definition: ecs-service.jsonnet
task_definition: ecs-task.jsonnet
GitHub Actions で CI 実行する場合は working-directory: ecspresso/ を steps に追加することでも解決できる。
ケース6: GitHub Actions OIDC 認証失敗 (Trust Policy Condition 不一致)
Error: Not authorized to perform sts:AssumeRoleWithWebIdentity原因
IAM ロールの Trust Policy の Condition が GitHub リポジトリ名または ref と一致しない。StringEquals を使うと sub の完全一致が必要で、ブランチ名まで一致させないと認証が通らない。aws-actions v4 では aud が sts.amazonaws.com に変更されているため、旧設定のまま流用すると失敗する。
解決策
ワイルドカードが必要な場合は StringLike を使用し、org/repo 名を正確に記述する。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:*"
}
}
}
]
}
Terraform (Provider 6.x) では aws_iam_openid_connect_provider リソースで OIDC プロバイダーを管理する:
# 現在の Trust Policy を確認
aws iam get-role \
--role-name myapp-github-actions-role \
--query 'Role.AssumeRolePolicyDocument' \
--output json
ケース7: ECR push 失敗 (IAM 権限不足)
Error: denied: User: ... is not authorized to perform: ecr:InitiateLayerUpload on resource: ...原因
GitHub Actions が assume する IAM ロールに ECR push に必要な権限が付与されていない。ecr:GetAuthorizationToken はリソース * で付与が必要な点に注意。aws-actions/amazon-ecr-login@v4 を使う場合も同じポリシーが必要。
解決策
以下の IAM ポリシーを OIDC ロールにアタッチする。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "arn:aws:ecr:ap-northeast-1:123456789012:repository/myapp"
}
]
}
Terraform で管理する場合は aws_iam_role_policy リソースに上記を定義し、OIDC ロールにアタッチする:
# 現在のロールに付与されているポリシーを確認
aws iam list-attached-role-policies \
--role-name myapp-github-actions-role \
--query 'AttachedPolicies[*].PolicyName' \
--output json
ケース8: Blue/Green ロールバック手順 (CodeDeploy コンソール操作)
本番デプロイ後にアプリエラーが発生し、旧版 (Blue 環境) に即時切り戻したい。
原因
新タスク (Green) でバグが発生。CodeDeploy の Traffic Shifting が完了している場合は ALB 側の切り戻しが必要で、ecspresso rollback だけでは ALB トラフィックは切り戻らない。
解決策
- AWS Console → CodeDeploy → アプリケーション → デプロイグループを開く
- 実行中または最新のデプロイを選択
- 「デプロイを停止してロールバック」ボタンをクリック
- ALB ターゲットが Blue (旧) に切り戻されるまで 2〜3 分待機
- ECS コンソールでタスクが旧リビジョンで起動していることを確認
# ecspresso rollback で TaskDef も旧リビジョンに戻す
ecspresso rollback --config ecspresso.yml
# ロールバック後の TaskDef リビジョンを確認
aws ecs describe-services \
--cluster myapp-cluster \
--services myapp-service \
--query 'services[0].taskDefinition' \
--output text
Traffic Shifting が完了する前であれば、コンソールの「デプロイを停止」だけで Blue 環境に自動的に戻る。Traffic Shifting 完了後は ALB の手動切り替えが必要になるため、デプロイ監視を怠らないこと。
ケース9: ecspresso rollback でバージョン確認
rollback 後に「どのリビジョンに戻ったか」が不明で、チームへの報告ができない。
原因ecspresso rollback は直前のアクティブリビジョンを自動選択するため、意図したリビジョンと異なる可能性がある。デプロイ履歴を事前に確認していないと復旧の確証が取れない。
解決策ecspresso deployments でデプロイ履歴を確認してからロールバックする。
# デプロイ履歴を確認 (直前のリビジョンが分かる)
ecspresso deployments --config ecspresso.yml
# 現在 Service で使用中の TaskDef リビジョンを確認
aws ecs describe-services \
--cluster myapp-cluster \
--services myapp-service \
--query 'services[0].taskDefinition' \
--output text
特定のリビジョンに戻す場合は AWS CLI で直接指定する:
# 特定のリビジョンに戻す (例: リビジョン42)
aws ecs update-service \
--cluster myapp-cluster \
--service myapp-service \
--task-definition myapp-task:42
# 更新後のリビジョンを確認
aws ecs describe-services \
--cluster myapp-cluster \
--services myapp-service \
--query 'services[0].taskDefinition' \
--output text
ケース10: Terraform と ecspresso の状態ズレ復旧
terraform apply 後に ecspresso diff で大量の差分が出る。または Terraform と ecspresso が互いに設定を上書きし合う。原因
Terraform で aws_ecs_service も管理していた場合、TF と ecspresso の両方が Service を変更しようとして競合する。Provider 6.x から force_new_deployment の挙動が変わり、意図しないリビジョン更新が発生することがある。
解決策
tfstate から Service リソースを切り離し、ecspresso init で管理権を ecspresso 側に移行する。
# tfstate から ECS Service リソースを切り離す
terraform state rm aws_ecs_service.myapp
# ecspresso init で現在の Service 状態を ecspresso.yml に取り込む
ecspresso init \
--cluster myapp-cluster \
--service myapp-service \
--config ecspresso.yml
# 差分が 0 になることを確認
ecspresso diff --config ecspresso.yml
Terraform 側では aws_ecs_service を削除し、代わりに Service の ARN を data source で参照する形に変更する:
# terraform plan で変更差分がないことを確認
terraform plan -out=tfplan
# 差分なし (No changes) であれば移行完了
| エラー症状 | 主な原因 | 参照ケース |
|———–|———-|———–|
| cluster not found | ARN 未指定 / リージョン名解決失敗 | ケース1 |
| diff 差分なし → 古い TaskDef | INACTIVE TaskDef 大量残存 | ケース2 |
| deploy Stuck / STOPPED ループ | ALB ヘルスチェックパス不一致 | ケース3 |
| AppSpec content is invalid | ContainerName/Port 不一致 | ケース4 |
| jsonnet import エラー | 実行ディレクトリ / –jpath 未設定 | ケース5 |
| AssumeRoleWithWebIdentity 失敗 | Trust Policy Condition 不一致 | ケース6 |
| ecr:InitiateLayerUpload 権限エラー | IAM ポリシー不足 | ケース7 |
| Blue/Green ロールバックしたい | CodeDeploy コンソール + rollback | ケース8 |
| rollback 後のリビジョン不明 | deployments コマンドで履歴確認 | ケース9 |
| ecspresso diff で大量差分 | Terraform と ecspresso の二重管理 | ケース10 |
7. Copilot × CDK × ecspresso × Terraform × CodePipeline 5軸選定基準

ECS デプロイツールの選定は、既存の Terraform 資産があるかとチームのスキルセットの2点で大半が決まる。5ツールを同一の選定軸で横比較し、「どのチームにどのツールが合うか」の判断フレームワークを提示する。どのツールも「ECS を動かす」という目的は達成できるが、運用フェーズに入ったときの保守性・デプロイ速度・Terraform との親和性で大きく差が出る。
7-1. 5軸比較マトリクス
- Terraform で VPC/Cluster/IAM を管理済みなら ecspresso が最もスムーズに追加できる。既存 TF コードに手を入れず、
terraform state rmで ECS Service だけを切り離すだけで移行できる - ゼロから始めるなら Copilot が最も学習コストが低い。
copilot init一発で VPC/Cluster/Service 一式を作成できる - TypeScript/Python で全リソースをコード化したいなら CDK が最適解。型安全なコード補完とテストが書けるメリットがある
- デプロイとインフラを統合管理したいなら CodePipeline + Terraform。ただし CI/CD パイプラインのメンテナンスコストが高くなりやすい
| 選定軸 | ecspresso+TF | AWS Copilot | AWS CDK | CodePipeline+TF | Terraform単独 |
|---|---|---|---|---|---|
| Terraform共存 | ◎ 完全共存 | △ 競合リスク大 | △ 設計コスト大 | ○ 共存可 | ◎ |
| 学習コスト | 低〜中 | 低 | 中〜高 | 中 | 低〜中 |
| デプロイ速度 | ◎ 高速 | ○ | ○ | △ 遅い | × (不向き) |
| カスタマイズ性 | ◎ | △ 制約多い | ◎ | ○ | ◎ |
| Blue/Green対応 | ◎ CodeDeploy | ○ | ◎ | ◎ | △ 追加構成必要 |
| diff可視化 | ◎ ecspresso diff | △ | △ cdk diff | △ plan のみ | ○ plan |
| 本番推奨度 | ◎ | ○ 小〜中規模 | ○ | △ 複雑化しやすい | × (デプロイ不向き) |
| ECS Service 権限境界 | ◎ 明確 | △ Copilot 一括管理 | ○ | ○ | △ drift 問題あり |
選定軸の詳解:
Terraform共存
Copilot は VPC/Cluster/ALB を独自管理するため、既存の TF リソースと競合しやすい。CDK は TF と並行管理する場合に責任境界設計のコストが高い。ecspresso は TF output を参照するのみで完全共存できる。{{ tfstate "output.cluster_arn" }} 記法で TF 管理リソースを参照し、所有権は TF が持ち続ける設計だ。
デプロイ速度
Terraform は plan → apply サイクルが必要で、大規模な TF コードでは plan だけで 2〜5 分かかることがある。ecspresso は ECS Service/TaskDef のみを変更するため、diff → deploy が 30 秒〜1 分で完了する。本番デプロイの高速化が求められるチームでは ecspresso の優位性が明確に出る。
カスタマイズ性
Copilot は抽象化レイヤーが厚く、細かい ECS Service パラメータ (deploymentConfiguration の minimumHealthyPercent 等) の調整が難しい場合がある。ecspresso と CDK は生の AWS API パラメータを直接指定できる。特に Blue/Green の詳細設定や Service Connect 統合など、Copilot が対応していない機能を使いたい場合は ecspresso が有利だ。
ECS Service 権限境界
Terraform で ECS Service を管理すると、手動スケール後の terraform apply で desiredCount が上書きされる drift 問題が起きる。ecspresso は ECS Service の所有権を完全に掌握し、TF との干渉なしにデプロイできる。これは特に SRE がピーク時に ecspresso scale --tasks 10 で手動スケールした後、次の CI/CD デプロイで設定が上書きされないという重要なメリットだ。
7-2. ユースケース別推奨ツール
チームの状況別に最適ツールの組み合わせを示す。実際のプロジェクトでは「現在の状況」と「将来の展望」の両方を考慮してツールを選定することが重要だ。
パターン A: Terraform 既存環境 + デプロイ高速化
- 推奨: ecspresso + Terraform
- 理由: TF の tfstate を参照しつつ、Service/TaskDef のみを ecspresso で高速に管理できる。既存 TF コードの大幅変更不要。
- 移行手順:
terraform state rm aws_ecs_service.appで State から切り離すだけで移行完了。ECS Service の実体は削除されないため、無停止で移行できる。 - 期待効果: デプロイ時間 50〜70% 短縮 /
ecspresso diffによる変更内容の事前可視化 / Blue/Green の確実なロールバック
実装の起点となる ecspresso.yml 設定:
region: ap-northeast-1
cluster: '{{ tfstate "output.cluster_arn" }}'
service: myapp-service
service_definition: ecs/ecs-service-def.jsonnet
task_definition: ecs/ecs-task-def.jsonnet
plugins:
- name: tfstate
config:
backend: s3
backend_config:
bucket: myapp-terraform-state
key: prd/terraform.tfstate
region: ap-northeast-1
パターン B: ゼロからスタート / ECS 初学者チーム
- 推奨: AWS Copilot
- 理由:
copilot init → copilot deployの 2 コマンドで VPC/Cluster/Service 一式が作成される。IaC の知識なしで本番環境を構築できる最速の選択肢。 - 注意: 後から Terraform 移行する場合はコスト大。最初から TF を使う計画があるなら Copilot は避け、学習コストをかけても ecspresso + TF を最初から選ぶほうが長期的にコストが低い。
- 推奨移行パス: Copilot で PoC → 3ヶ月以内に ecspresso + TF へ移行(長期運用が決まった段階で移行する)
パターン C: 全インフラを TypeScript/Python でコード化したい
- 推奨: AWS CDK
- 理由: ECS Service / ALB / VPC を同一言語で定義できる。型安全なコード補完が得られる。CDK Pipelines を使えば CI/CD も CDK で完結する。
- 注意: 既存 Terraform との並行管理は責任境界を厳密に設計しないと競合する。「CDK でデプロイ、TF でネットワーク」という分担なら共存可能だが、設計ドキュメントの整備が必須だ。
パターン D: 大規模マイクロサービス / 複数チームでの運用
- 推奨: ecspresso + Terraform + GitHub Actions OIDC
- 理由: サービスごとに ecspresso 設定を分離できる。各チームが自分のサービスだけをデプロイでき、共有インフラ (VPC/Cluster) は Terraform の state backend で一元管理する。
- 推奨ディレクトリ構成:
infra/
terraform/ ← 共有インフラ (VPC/Cluster/ALB/IAM)
main.tf
outputs.tf
terraform.tfstate (S3 backend)
services/
service-a/
ecspresso/ ← サービスAの ecspresso 設定
ecspresso.yml(tfstate plugin で infra の output を参照)
ecs-service-def.jsonnet
ecs-task-def.jsonnet
service-b/
ecspresso/ ← サービスBの ecspresso 設定
ecspresso.yml
この構成により、各サービスチームは services/{service}/ecspresso/ 配下のみを変更してデプロイでき、共有インフラへの影響をゼロにできる。
- Copilot は VPC/Cluster/ALB を独自管理するため、Terraform で作成済みのリソースと競合する
- 既存 TF 環境への Copilot 追加は、TF State からの切り離し作業が必要になり多大なコストがかかる
- 「Copilot でとりあえず動かす → 後で TF 移行」の計画はほぼ確実に失敗する。Copilot が作った VPC/ALB を TF に取り込む作業は import の嵐になる
- Copilot の独自 manifest 形式と TF の HCL が二重管理になり、どちらが正とするかでチーム内に混乱が生じる
- Terraform 資産がある場合は最初から ecspresso を選ぶのが最善策。学習コストは 1〜2 日で回収できる
7-3. チームのスキルセット別選定ガイド
| チームプロファイル | 第1推奨 | 第2推奨 | 避けるべきツール | 移行パス |
|---|---|---|---|---|
| Terraform 経験者 (HCL に慣れている) | ecspresso+TF | CDK | Copilot | 即時移行可能 |
| AWS CLI 経験者 (IaC 未経験) | Copilot | ecspresso+TF | CDK | Copilot → 3ヶ月後に ecspresso+TF |
| ゼロ経験者 (AWS 自体が初めて) | Copilot | (段階学習後に TF) | CDK / ecspresso | Copilot で習得 → TF学習 → ecspresso |
| SRE/DevOps (CI/CD 自動化が主目的) | ecspresso+TF+GHA | CodePipeline+TF | Copilot | GHA OIDC 設定から着手 |
| TypeScript/Python 専門 (インフラも同言語で) | CDK | ecspresso+TF | Copilot | CDK Pipelines と組み合わせ |
| スタートアップ (スピード重視) | Copilot | ecspresso+TF | CDK | PMF後にecspresso+TFへ移行 |
- Terraform で VPC/Cluster/IAM を既に管理している
- ECS Service と TaskDef の変更頻度が高く、都度
terraform applyしたくない - PR 時に「何が変わるか」を
ecspresso diffで可視化したい - Blue/Green デプロイで即時ロールバック能力が必要
4点中3点以上該当する場合、ecspresso + Terraform の組み合わせを強く推奨する。2点以下ならチームの学習コストを考慮し、Copilot や CDK も選択肢に入れること。
ツール選定フロー (テキスト)
START: ECS デプロイツール選定
↓
Terraform で VPC/Cluster を既に管理している?
├── YES → ecspresso + Terraform (推奨)
└── NO
↓
TypeScript/Python でインフラ定義したい?
├── YES → AWS CDK
└── NO
↓
ゼロから素早く動かしたい?
├── YES → AWS Copilot
└── NO → ecspresso + Terraform (TF学習と並行)
既存 Terraform コードに aws_ecs_service がある場合の移行手順:
# STEP 1: 現在の Service 設定を ecspresso で取得
ecspresso init \
--cluster $(terraform -chdir=terraform output -raw cluster_name) \
--service myapp-service \
--config ecs/ecspresso.yml
# STEP 2: TF state から ECS Service を除外 (実リソースは削除しない)
terraform -chdir=terraform state rm aws_ecs_service.app
# STEP 3: TF コードから aws_ecs_service ブロックを削除後、差分なし確認
terraform -chdir=terraform plan
# → No changes. Your infrastructure matches the configuration.
# STEP 4: ecspresso diff で差分ゼロ確認
ecspresso diff --config ecs/ecspresso.yml
# → No changes.
# STEP 5: 初回 deploy で ecspresso が所有権を取得
ecspresso deploy --config ecs/ecspresso.yml --rollout-wait
7-4. GitHub Actions OIDC AssumeRole フロー (mermaid02)
GitHub Actions から AWS に安全に接続するための OIDC フローを示す。長期クレデンシャル (Access Key) を一切使わずに ECR push と ECS deploy を実現する仕組みだ。OIDC により、キーのローテーション・漏洩リスク・シークレット管理のコストがすべて解消される。
sequenceDiagram
participant GHA as GitHub Actions Runner
participant OIDC as GitHub OIDC Provider
participant STS as AWS STS
participant ECR as Amazon ECR
participant ECS as Amazon ECS
GHA->>OIDC: JWT トークン要求 (audience=sts.amazonaws.com)
OIDC-->>GHA: JWT (sub=repo:org/repo:ref:refs/heads/main)
GHA->>STS: AssumeRoleWithWebIdentity (JWT + Role ARN)
STS-->>GHA: 一時クレデンシャル (15分〜1時間)
GHA->>ECR: docker push (IMAGE:SHA)
GHA->>ECS: ecspresso deploy --rollout-wait
ECS-->>GHA: Service 安定確認 PASS
OIDC 設定の要点:
GitHub OIDC Provider を AWS に登録:
https://token.actions.githubusercontent.comを IAM Identity Provider として追加する。Terraform のaws_iam_openid_connect_providerリソースで管理すると宣言的に管理できるTrust Policy の Condition 設定:
token.actions.githubusercontent.com:subをrepo:myorg/myrepo:ref:refs/heads/mainに制限することで、対象ブランチからのみ AssumeRole を許可する。StringLikeを使うと複数ブランチに対応できる一時クレデンシャルの有効期限: デフォルト 1 時間。長い CI パイプラインでは
--duration-seconds 7200で延長可能 (最大 12 時間だが、ロール設定の MaxSessionDuration に依存)最小権限原則: GHA 用 IAM ロールには ECR push 権限と ECS Service 更新権限のみを付与し、VPC/Cluster 等インフラ変更権限は持たせない。Terraform apply 用と ecspresso deploy 用でロールを分けるとより安全だ
Trust Policy の Terraform 実装例:
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
resource "aws_iam_role" "gha_deploy" {
name = "github-actions-ecspresso-deploy"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Federated = aws_iam_openid_connect_provider.github.arn }
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:myorg/myrepo:*"
}
}
}]
})
}
GitHub Actions ワークフローの OIDC 設定例:
name: ECS Deploy
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecspresso-deploy
aws-region: ap-northeast-1
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and Push Docker Image
run: |
IMAGE_URI="123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:${{ github.sha }}"
docker build -t $IMAGE_URI .
docker push $IMAGE_URI
- name: Deploy with ecspresso
env:
ECR_IMAGE: "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:${{ github.sha }}"
run: |
ecspresso deploy \
--config ecs/ecspresso.yml \
--rollout-wait \
--ext-str ECR_IMAGE=$ECR_IMAGE
8. まとめ + Container 本番運用シリーズ全14軸クロスリンク
8-1. 本記事で習得した判断軸の振り返り
本記事を通して習得した判断軸を 1 表に集約する。各章が独立した技術トピックとして読めるよう設計しているが、実際の本番構築では §2 の責任境界設計を起点として、§3→§4→§5 の順に積み上げることで安全な ECS 運用基盤が完成する。
| 章 | 判断軸 | 要点 |
|---|---|---|
| §1 | ツール選択の入口 | Terraform 既存 → ecspresso / ゼロから → Copilot / TypeScript → CDK |
| §2 | 責任境界 | インフラ (VPC/ALB/Cluster/ECR/IAM) = TF / Service+TaskDef = ecspresso |
| §3 | jsonnet変数化 | base.libsonnet + env/{dev,prd}.libsonnet → 環境別 JSON を DRY 生成 |
| §4 | GHA OIDC CI/CD | OIDC v4+ で長期クレデンシャル不要 / PR=diff / merge=apply+deploy |
| §5 | Blue/Green | 無停止必須 → B/G + CodeDeploy / シンプル → Rolling / 段階的 → Canary |
| §6 | Troubleshoot | 10ケースの根本原因と即効解決策 |
| §7 | ツール選定 | Terraform共存・学習コスト・カスタマイズ性の5軸で判断 |
| §8 | 全14軸ハブ | Container軸から AWS 本番運用の全軸へ |
本記事の核心メッセージ: ECS × ecspresso × Terraform の組み合わせは「Terraform でインフラを管理しつつ、デプロイだけを高速化したい」チームの最適解だ。責任境界 (§2) を明確にし、jsonnet で環境差分を吸収 (§3) し、OIDC で安全な CI/CD を構築 (§4) することで、本番クラスの ECS 運用基盤が手に入る。Copilot との比較 (§1/§7) で示したように、既存 Terraform 環境があるなら ecspresso の選択は必然的な帰結だ。
実装を始めるための推奨ステップ:
- Terraform 基盤を確認 —
terraform outputで cluster_arn / alb_target_group_arn が取得できることを確認 - ecspresso init を実行 — 既存 ECS Service の設定をローカルに取得
- jsonnet で環境変数化 — dev/prd の差分を base.libsonnet + env/*.libsonnet に分離
- GHA OIDC を設定 — アクセスキー不要の安全な CI/CD パイプラインを構築
- Blue/Green に移行 —
deploymentController: CODE_DEPLOYでゼロダウンタイムを実現
8-2. ecspresso × Terraform チートシート
| カテゴリ | コマンド | 説明 |
|---|---|---|
| Terraform | terraform init | プロバイダ初期化 |
| Terraform | terraform plan -var="environment=prd" | 変更プレビュー |
| Terraform | terraform apply -auto-approve | リソース作成/更新 |
| Terraform | terraform output cluster_arn | output 値確認 |
| Terraform | terraform state rm aws_ecs_service.app | Service を TF 管理から除外 |
| Terraform | terraform state list | 管理リソース一覧確認 |
| Terraform | terraform import aws_ecs_cluster.main <arn> | 既存リソースを TF 管理に取り込む |
| ecspresso | ecspresso init --cluster X --service Y | 設定ファイル生成 |
| ecspresso | ecspresso diff | AWS との差分確認 |
| ecspresso | ecspresso deploy --rollout-wait | Service/TaskDef 更新 |
| ecspresso | ecspresso rollback | 直前リビジョンに戻す |
| ecspresso | ecspresso scale --tasks N | タスク数変更 |
| ecspresso | ecspresso verify | 設定ファイル構文検証 |
| ecspresso | ecspresso deployments | デプロイ履歴一覧 |
| ecspresso | ecspresso tasks | 実行中タスク一覧 |
| ecspresso | ecspresso register | TaskDef のみ登録 (Service更新なし) |
| jsonnet | jsonnet -S ecs-service.jsonnet | JSON 生成 |
| jsonnet | jsonnet -S -V ENV=prd ecs-service.jsonnet | 環境変数付き JSON 生成 |
| GHA | act pull_request -n | PR ワークフロー dry-run |
| GHA | act push --secret-file .secrets -n | push ワークフロー dry-run |
よく使う ecspresso オプション:
# デプロイ時に特定の image タグを jsonnet 外部変数で指定
ecspresso deploy \
--config ecs/ecspresso.yml \
--rollout-wait \
--ext-str ECR_IMAGE="123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:abc123"
# 複数設定ファイルの切り替え (環境別)
ecspresso diff --config ecspresso-prd.yml
ecspresso deploy --config ecspresso-prd.yml --rollout-wait
# デプロイタイムアウトを延長 (デフォルト10分)
ecspresso deploy --config ecspresso.yml --rollout-wait --timeout 20m
# Blue/Green の Hooks 確認
ecspresso run --task-def ecs/ecs-task-def.jsonnet --count 1 --watch
8-3. AWS 本番運用 全14軸クロスリンク — Container 軸からのステップアップ
ecspresso × Terraform で ECS デプロイを習得したら、AWS 本番運用の全14軸へ視野を広げよう。Container 軸 (本記事) は、IAM・EKS・復旧・生成AI・セキュリティ・コスト最適化といった全軸と密接につながっている。本記事で登場した OIDC → IAM (第1軸)、ECS → EKS (第2軸)、Blue/Green → 復旧 (第3軸) など、各軸への自然な学習パスが存在する。
- 第1軸 IAM (4記事): Vol1 ポリシー設計入門 / Vol2 マルチアカウント IAM 設計 / Vol3 Permission Inventory 自動化 / Vol4 STS クロスアカウント
- 第2軸 EKS 本番運用 (3記事): Vol1 クラスター設計/IRSA/ALB Ingress / Vol2 Observability/Fluent Bit/Container Insights / Vol3 GitOps/ArgoCD
- 第3軸 復旧・運用 (4記事): Vol1 Backup×Cross-Region DR / Vol2 Chaos Engineering×FIS / Vol3 対応ランブック自動化 / Vol4 Multi-Region Active-Active
- 第4軸 生成AI (2記事): Vol1 Bedrock Agents 本番運用 / Vol2 Knowledge Bases×RAG
- 第5軸 セキュリティ (2記事): Vol1 セキュリティ運用入門 / Vol2 SOC 統合運用
- 第6軸 コスト最適化 (1記事): Vol1 Cost Explorer×Budgets×Compute Optimizer
- 第7軸 マルチアカウント運用 (1記事): Vol1 Organizations×Control Tower×Landing Zone
- 第8軸 Observability (1記事): Vol1 Application Signals×SLO×X-Ray
- 第9軸 Network/VPC 設計 (2記事): Vol1 Transit Gateway×VPC Lattice×PrivateLink / Vol2 Hybrid Connectivity (Direct Connect×VPN)
- 第10軸 DevOps/CI/CD (2記事): Vol1 CodePipeline×CodeBuild×CodeDeploy×GitHub Actions OIDC / Vol2 Container×CodeArtifact×SAM×Amplify
- 第11軸 Database 本番運用 Vol1: RDS×Aurora×DynamoDB 基礎運用
- 第12軸 Database 本番運用 Vol2: DMS×Aurora Global Database×DynamoDB Streams×Backup 戦略
- 第13軸 Serverless 本番運用 Vol1: Lambda×API GW×Step Functions
- 第14軸 Serverless 本番運用 Vol2: EventBridge×SQS×SNS×Kinesis
- 基盤: Terraform 基礎入門: Terraform 1.x×AWS Provider 6.x (S3×EC2×Remote State×Module化)
Container 軸から各軸への推奨学習パス:
Container → IAM (第1軸): 本記事の GHA OIDC / Task Role / Execution Role を理解したら、IAM シリーズで STS クロスアカウントや Permission Inventory 管理へ深掘りする。本記事の
AssumeRoleWithWebIdentityと第1軸 Vol4 の STS クロスアカウントは直接つながる知識だContainer → EKS (第2軸): ECS 運用に慣れたら EKS への移行検討。IRSA (EKS 版 Task Role) と ArgoCD (GitOps) が ECS との主な違いだ。本記事の ecspresso の概念 (diff/deploy/rollback) は EKS の kubectl apply と対応する
Container → Observability (第8軸): ecspresso deploy 後のコンテナ監視は Application Signals と X-Ray で完結させる。ECS タスクへの X-Ray サイドカー追加は TF で管理する
Container → DevOps/CI/CD (第10軸): 本記事の GHA OIDC パイプラインを CodePipeline と比較したい場合は第10軸 Vol1 が最適。Blue/Green の CodeDeploy 統合も詳解している
Container → コスト最適化 (第6軸): Fargate と EC2 起動タイプのコスト比較、Spot タスクの活用など、ecspresso で構築した ECS 環境のコスト最適化を第6軸で学べる
8-4. Container 本番運用シリーズ — 次のステップ
ecspresso × Terraform をマスターしたら、Container 本番運用シリーズの次のテーマへ進もう。
- EKS Cluster + Karpenter 本番運用 — Karpenter v1 NodePool/EC2NodeClass + Container Insights + Terraform 一気通貫。ECS から EKS へのステップアップに最適なエントリーポイントだ。Karpenter の動的スケーリングと ecspresso で培った Terraform 連携スキルがそのまま活かせる
- EKS IRSA 完全活用 — OIDC Provider + ServiceAccount + Terraform / EKS Pod Identity との使い分け。本記事の GHA OIDC AssumeRole と対になる EKS 権限設計を、完全実装形式で解説する
- ecspresso Service Connect 統合 — ECS Service Connect と ecspresso の組み合わせで内部サービス検出を実装。App Mesh に代わる次世代アーキテクチャとして、マイクロサービス間の安全な通信を実現する構成を詳解する