はじめに
みなさんこんにちは、エンジニアのおすずです。
以前投稿したこちらの記事では、GitLab CI/CD から AWS CLI を直接呼び出してタスク定義やサービスを更新する方法をご紹介しました。
本記事では、実案件で取り組んだECSデプロイの自動化について整理します。
今回のプロジェクトでは、インフラのリソースを CloudFormationでIaC(Infrastructure as Code)化して管理していました。
しかし、CI/CD パイプラインを構築するにあたり、次のような疑問が出てきました。
「IaCをGitでバージョン管理したいけれど、アプリ更新のたびにサービスやタスク定義も更新する必要がある。インフラ構成自体は変わっていないのに、毎回IaCをデプロイし直すのだろうか・・・」
運用を調べる中で、社内の別プロジェクトでは「インフラ構成はCFnで管理し、ECSサービスやタスク定義はアプリケーションリポジトリ側で扱う」という構成が取られていました。
この構成により、アプリの更新をトリガーにして新しいタスク定義を作成し、サービスを更新できるようになっていたのです。
その実現を支えていたのが、今回紹介する ecspresso というツールです。
ecspresso とは
ecspresso(エスプレッソ)は ECS デプロイをよりシンプルかつ効率的に行うためのCLI ツールです。
Terraform のように宣言的にタスク定義やサービス定義を管理でき、ECS運用に特化した軽量ツールとして多くの現場で利用されています。
👉 https://github.com/kayac/ecspresso
特徴
- IaCとデプロイの分離管理
インフラ構成(ネットワークやセキュリティグループなど)は CloudFormation に任せ、アプリケーション側ではデプロイに関わる ECS タスク・サービスだけを扱う。 - リポジトリ単位の責務分離
アプリごとにタスク定義・サービス定義を保持できるため、開発チーム単位で独立したデプロイ管理が可能。 - CI/CD との親和性の高さ
CLI ベースで差分検出・デプロイ・ロールバックができるため、自動化パイプラインとの統合が容易。
コマンド例:
# 差分確認
ecspresso diff -f ecs/ecspresso.yml
# デプロイ
ecspresso deploy -f ecs/ecspresso.yml
# ロールバック
ecspresso rollback -f ecs/ecspresso.yml
構成
今回の構成イメージは以下のようになります。
app-repo/
ecs/
ecspresso.yml # ecspresso 設定ファイル
task-definition.json # タスク定義
service-definition.json # サービス定義
IaC-repp/
templates/ # CFnテンプレート
タスクやサービス定義を IaC(CFn)側では定義せず、アプリケーションのリポジトリに置くことで、アプリケーションコードとタスク/サービス定義を同じ場所で管理 できるようになります。
実際のところ、今回のアプリケーションに関して言えば、インフラは一度構築してしまえばその後の変更頻度はアプリケーションに比べて圧倒的に低いものです。にもかかわらず、タスクやサービスを更新するたびに IaC を再デプロイするのは現実的な運用とは言えませんでした。
かといって、手動でデプロイを実行し、IaC のタスク/サービス定義だけを書き換えて IaC の再デプロイはしないという運用もナンセンスです。
そこで活躍するのが ecspresso です。
このツールを利用することで、タスクやサービス定義の更新を アプリケーション側の gitlab-ci.yml に組み込み、アプリのデプロイとあわせて自動化することができます。仕組みにより、IaC とアプリケーションのデプロイ頻度の差を吸収し、運用をシンプルに整えることができました。

設定ファイルのサンプル
- ecspresso.yml
region: "{{ must_env `RESION` }}"
cluster: "{{ must_env `CLUSTER` }}"
service: "{{ must_env `SERVICE_NAME` }}"
service_definition: service-definition.json
task_definition: task-definition.json
- task-definition.json
{
"serviceName": "{{ must_env `SERVICE_NAME` }}",
"desiredCount": 1,
"launchType": "FARGATE",
"deploymentConfiguration": {
"maximumPercent": 200,
"minimumHealthyPercent": 100
},
"loadBalancers": [
{
"targetGroupArn": "{{ must_env `TARGET_GROUP_ARN` }}",
"containerName": "{{ must_env `CONTAINER_NAME` }}",
"containerPort": 8080
}
],
"networkConfiguration": {
"awsvpcConfiguration": {
"subnets": "{{ must_env `SUBNET` }}",
"securityGroups": "{{ must_env `SECURITY_GROUP` }}",
"assignPublicIp": "DISABLED"
}
}
}
- service-definition.json
{
"family": "{{ must_env `SERVICE_NAME` }}",
"executionRoleArn": {{ must_env `EXECUTION_ROLE_ARN` }}",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "{{ must_env `CONTAINER_NAME` }}",
"image": "{{ must_env `REPOSITORY_URL` }}:{{ must_env `TAG_NAME` }}",
"essential": true,
"memoryReservation": 256,
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/{{ must_env `SERVICE_NAME` }}",
"awslogs-region": "{{ must_env `RESION` }}",
}
}
}
]
}
- gitlab-ci.ymlでのjob定義(関係ないjobは省略しています)
stages:
- build
- deploy
# コンテナビルド
build:
stage: build
script:
- docker build -t $REPOSITORY_URL:$CI_COMMIT_SHORT_SHA .
- docker push $REPOSITORY_URL:$CI_COMMIT_SHORT_SHA
# タスク定義登録
register:
stage: deploy
script:
- ecspresso register --config=ecs/ecspresso.yml
# デプロイ
deploy:
stage: deploy
script:
- ecspresso deploy --config=ecs/ecspresso.yml --latest-task-definition
needs:
- register
このようにステージを分けることで、ビルド → タスク登録 → デプロイ の流れを自動化できます。
デプロイ状況は GitLab の Pipeline UI からも確認でき、失敗時には Slack 通知やロールバック処理を組み合わせることも可能です。
実際に導入してみての感想
AWS CLI で直接操作していた頃と比べると、ecspresso を使うことで圧倒的にシンプルかつ再現性の高いパイプラインを構築できました。
手動操作の手間が減り、タスク定義の更新忘れや ヒューマンエラーのリスクも軽減されました。
まとめ
- CloudFormation による IaC 管理と、ecspresso による ECS デプロイを分離することで運用がシンプルに。
- GitLab CI/CD との連携により、ビルドからリリースまでの一連の流れを自動化できる。
- 小規模から中規模のチーム開発でも導入しやすく、ECS 運用の負担を大きく軽減できる。
デプロイ周りはまだまだ改善の余地が大きく、便利なツールや設計パターンも日々進化しています。今後も新しい選択肢を試しながら、より効率的で快適な開発体験につなげていきたいと思います。