はじめに
私は普段バックエンドエンジニアとして活動しています。Terraformとecspressoを活用しているプロジェクトが多いのですが、両者の役割分担は、一見すると分かりにくいです。この記事では、Terraformとecspressoを使ったサンプルプロジェクトを作りながら、両者のライフサイクルの違いについて説明いたします。
この記事を読むと分かること
- Terraformとecspressoの役割の違い
- なぜECSの変更管理にecspressoが向いているのか
- ecspressoおよびGitHub Actionsのサンプルコード
想定読者
- バックエンドエンジニアで、インフラもある程度触る人
- Terraformとecspressoの違いを知りたい人
ecspressoが解決したい課題
TerraformのみでECSの変更管理をする難しさ
「ecspressoはECSの管理に特化したツールです。」
私はこれを聞いたとき「TerraformでもECSの管理できるじゃん、なぜecspressoが必要?」と疑問が湧きました。はじめに、AWSリソースのライフサイクルを整理し、ecspressoの守備範囲を確認します。
まずTerraformはAWS全体の構成管理を行います。ほとんどのAWSリソースは一度設定すると変更頻度は低いという特徴があります。一方で、ECS上で起動させるイメージはアプリエンジニアが開発して頻繁にデプロイします。CI/CDパイプラインが整備されているエリートなチームであれば、1日に1回の高頻度でアプリをデプロイすることもあるでしょう。
このときECSの更新をTerraformで行うと次のような問題が生じます。
- ECSのタスク定義のみ変更したいのに、AWS全体の差分チェックが発生する。場合によってはECS以外のリソースも同時に変更される
- CI/CDなど他の手段でECSの設定を書き換えると、Terraformに差分アリと判断され、
terraform apply時に、ECSの状態が以前の状態に切り戻される可能性がある
特に、商用サービスでは事故防止のため、高頻度で変更したいECS周りの管理と、更新頻度が低い他のAWSリソースの管理を分けておきたいかと思います。
ECSの変更はTerraformで管理しない
そこでインフラの中でライフサイクルが短いECS周りのリソースはTerraformで管理せず、別の管理ツールで管理します。この役割を担うのがecspressoです。
- 表: AWSリソース管理のすみ分け
| 項目 | Terraform | ecspresso | 変更頻度 |
|---|---|---|---|
| ECSタスク定義 | ○ | 高 | |
| ECSサービス | ○ | 高 | |
| ECSクラスター | ○ | 低 | |
| その他AWSリソース | ○ | 低 |
アプリチームとインフラチームの境界を明確にする
開発プロセスにおいて、ECS以外のインフラ管理はインフラ管理用のGitリポジトリで管理し、アプリケーションのデプロイ、ECSのリソース管理はアプリ管理用のGitリポジトリで管理します。
これによって、アプリチームとインフラチームの責任分界点が明確になり、アプリ側はインフラチームに変更依頼を出さずに自分たちの管理でアプリのデプロイを管理できます。また、アプリデプロイ時のインフラチームとの調整コスト(コミュニケーションコスト)の削減も狙えます。
- インフラリポジトリ: AWS全般のリソース管理 (terraform)
- アプリリポジトリ: アプリケーションコード + ecspressoの管理
Terraform管理からecspresso管理への移行
Terraform管理下のコードを削除
下記のコードは、ECS管理用のtfファイルの例です。もともとECS全体をTerraformで管理していましたが、services配下および container_definitions はコメントアウトされています。terraform.tfstateの管理外になったため、コンテナを再デプロイしても tfstateの diff として現れません。
modules/ecs/main.tf
module "ecs" {
source = "terraform-aws-modules/ecs/aws"
# NOTE: Terraformでは、ECSクラスタのみ管理する
cluster_name = "${var.product_name}-${var.env}"
cluster_configuration = {
}
default_capacity_provider_strategy = {
FARGATE_SPOT = {
weight = 1
}
}
# NOTE: ecspressoでECSサービスを管理するため、TerraformからはECSサービスの定義を削除
services = {
# fastapi-service = {
# cpu = 256
# memory = 512
# desired_count = 1
# container_definitions = {
# fastapi = {
# image = data.aws_ecr_image.this.image_uri
# essential = true
# readonlyRootFilesystem = false
# port_mappings = [
# # NOTE: hostPort must be the same as containerPort when using awsvpc network mode
# {
# name = "http"
# containerPort = 8000
# hostPort = 8000
# protocol = "tcp"
# }
# ]
# ... (以下略)
# }
}
}
コメントアウトした箇所をecspressoの定義ファイルに移動
先ほどのmodules/ecs/main.tfでコメントされた部分は、ecspressoの ecs-service-def.jsonnet, ecs-task-def.jsonnet で管理するようになります。それぞれECS Service, ECS Taskdefを制御します。ecspresso deployコマンドを実行すればAWSリソースのうち、ECSサービス, タスクのみ更新されるようになります。
ecs-service-def.jsonnet
{
"deploymentConfiguration": {
"deploymentCircuitBreaker": {
"enable": true,
"rollback": false
},
"maximumPercent": 200,
"minimumHealthyPercent": 50
},
"deploymentController": {
"type": "ECS"
},
"desiredCount": 1,
"enableECSManagedTags": true,
"enableExecuteCommand": true,
"healthCheckGracePeriodSeconds": 30,
"launchType": "FARGATE",
"networkConfiguration": {
"awsvpcConfiguration": {
"assignPublicIp": "ENABLED",
"securityGroups": [
"{{ tfstate `module.ecs.aws_security_group.ecs_sg.id` }}"
],
"subnets": [
"{{ tfstate `module.vpc.module.vpc.aws_subnet.public[0].id` }}",
"{{ tfstate `module.vpc.module.vpc.aws_subnet.public[1].id` }}"
]
}
},
"pendingCount": 0,
"platformFamily": "Linux",
"platformVersion": "LATEST",
"propagateTags": "NONE",
"runningCount": 0,
"schedulingStrategy": "REPLICA",
}
ecs-task-def.jsonnet
{
"containerDefinitions": [
{
"cpu": 0,
"essential": true,
"image": "{{ tfstate `module.ecs.data.aws_ecr_repository.this.repository_url`}}:latest",
"interactive": false,
"linuxParameters": {
"initProcessEnabled": true
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "{{tfstate `module.ecs.module.ecs.module.cluster.aws_cloudwatch_log_group.this[0].name`}}",
"awslogs-region": "{{ tfstate `module.ecs.module.ecs.module.cluster.aws_cloudwatch_log_group.this[0].region` }}",
"awslogs-stream-prefix": "ecs"
}
},
"name": "fastapi",
"portMappings": [
{
"containerPort": 8000,
"hostPort": 8000,
"protocol": "tcp"
}
],
"privileged": false,
"pseudoTerminal": false,
"readonlyRootFilesystem": false,
"startTimeout": 30,
"stopTimeout": 120,
"healthCheck": {
"command": [
"CMD-SHELL",
"curl -f http://localhost:8000/ || exit 1"
],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 10
}
}
],
"cpu": "256",
"executionRoleArn": "{{ tfstate `module.ecs.aws_iam_role.ecs_task_execution_role.arn` }}",
"family": "{{ tfstate `module.ecs.module.ecs.module.cluster.aws_ecs_cluster.this[0].tags.ServiceName` }}",
"ipcMode": "",
"memory": "512",
"networkMode": "awsvpc",
"pidMode": "",
"requiresCompatibilities": [
"FARGATE"
],
"runtimePlatform": {
"cpuArchitecture": "X86_64",
"operatingSystemFamily": "LINUX"
},
"taskRoleArn": "{{tfstate `module.ecs.aws_iam_role.ecs_task_role.arn`}}"
}
詳しい使い方を知りたい方は、ecspresso作者の執筆している下記の書籍がおすすめです。(¥500)
CI/CDにecspressoを組み込む
アプリリポジトリにおいて、mainブランチにコードがマージされたときに自動でGitHub Actionsが実行されるように設定します。GitHub Actionsのワークフローは下記のような流れで構成します。
- GHAでDockerイメージをビルド
イメージタグにはlatestタグと、コミットハッシュを両方付与する - DockerイメージをECRにプッシュ
- ecspressoでデプロイ
※--force-new-deploymentを使うことで最新イメージを確実に反映
.github/workflows/ecs-deploy.yml
name: ECS Deploy
on:
push:
branches:
- main
workflow_dispatch: # GitHub UIからの手動実行用
jobs:
build_and_push:
name: Docker Build and Push Docker Image to ECR
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_AllowGitHubActions }}
aws-region: ap-northeast-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: n700a/fastapi-mini
IMAGE_TAG: latest # ${{ GitHub.sha }}
run: |
docker build -f backend/Dockerfile -t $ECR_REPOSITORY:$IMAGE_TAG backend
docker tag $ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
deploy:
name: Deploy to ECS
needs: build_and_push
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_AllowGitHubActions }}
aws-region: ap-northeast-1
- name: Deploy to ECS service
uses: kayac/ecspresso@v2
with:
version: v2.6.0
args: deploy --config ./infra/dev/ecspresso/ecspresso.yml
このワークフローを設定しておくと、アプリチームはECSのデプロイ作業から解放され、アプリソースコードの作成に集中することができます。
まとめ
本記事では、AWSのリソースのうちアプリ領域とインフラ領域において、デプロイの頻度の違いがあることを述べ、全てをTerraformで管理することの課題を示しました。ECSの安全かつ高頻度なデプロイを実現するために、ecspressoとTerraformのすみわけを示しました。また、二者を併用することでAWSリソースのうちアプリ管理の領域とインフラ管理の領域を明確にできそうです。
この記事ではecspressoの具体的な環境構築方法については記載していません。ご興味のある方はぜひ一度手を動かしてみて下さい。
この記事が、ecspressoの意義や概念について知るきっかけになると幸いです。
参考記事



コメント