
CircleCI から GitHub Actions を実行して ECS (Fargete) にデプロイしてみる
はじめに
デプロイパイプラインを構築する際、タイトルのようにCIツールを敢えて複数組み合わせる機会は少ないとは思います。
しかし、実際のプロジェクトでは既存の CircleCI パイプラインが存在する、CircleCIの特定機能に現状依存している、段階的な移行が必要などといった理由から、このような状況に直面することがあると思います。
このような背景から、本稿では CircleCIとGitHub Actionsとを使ったデプロイを検証しました。
やりたいこと
- GitHubリポジトリA でアプリ更新、CircleCI でDockerイメージをビルドしてECRにプッシュ
- GitHubリポジトリT の GitHub Actions を使ってECSのタスク定義を更新
- GitHubリポジトリT の GitHub Actions でTerraformを使用してECSにデプロイ
なお、本稿では、PRの前にデプロイしてしまっていますが、環境に応じて plan のみにするなどの調整をすると良いと思います。
イメージ
環境
- node v22.14.0
- npm 10.9.2
- Terraform v1.11.2
前提
GitHubの設定
- アプリケーションリポジトリ(as Aリポジトリ)
- Terraformリポジトリ (as Tリポジトリ)
- GitHub Actions のOIDC設定
- 環境変数の設定
AWSの設定
- ECRリポジトリの作成
- ECSクラスターの作成
CircleCIの設定
- プロジェクト・パイプラインの設定
- CircleCI のOIDC設定
- context/環境変数の設定
- AWS_ACCOUNT_ID
- AWS_REGION
- AWS_ROLE_ARN
- ECR_REPOSITORY_NAME
- GITHUB_TOKEN (Fine-grained personal access token)
- GITHUB_URL
必要な設定
Aリポジトリ
アプリコードは、本稿の目的外なので省略します。(シンプルな Node Express です。)
.circleci/config.yml
version: 2.1
orbs:
node: circleci/node@7.1.0
aws-cli: circleci/aws-cli@5.2.0
jobs:
test-node:
executor: node/default
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
name: Run tests
command: echo "No test specified in package.json"
build-and-push-image:
machine:
image: ubuntu-2204:current
steps:
- checkout
- aws-cli/setup:
role_arn: ${AWS_ROLE_ARN}
region: ${AWS_REGION}
- run:
name: Set image tag
command: |
echo "export IMAGE_TAG=${CIRCLE_SHA1}-$(date +%Y%m%d-%H%M%S)" >> $BASH_ENV
source $BASH_ENV
- run:
name: Login to Amazon ECR
command: aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
- run:
name: Build and push Docker image
command: |
docker build -t ${ECR_REPOSITORY_NAME}:${IMAGE_TAG} .
docker tag ${ECR_REPOSITORY_NAME}:${IMAGE_TAG} ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY_NAME}:${IMAGE_TAG}
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY_NAME}:${IMAGE_TAG}
- run:
name: "Execute GitHub Actions"
command: |
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
${GITHUB_URL} \
-d "{
\"event_type\": \"update-ecs-image\",
\"client_payload\": {
\"image_tag\": \"${IMAGE_TAG}\",
\"sha\": \"${CIRCLE_SHA1}\"
}
}"
workflows:
build-test-deploy:
jobs:
- test-node
- build-and-push-image:
requires:
- test-node
context: aws
ここでの肝は、 create a repository dispatch event
のAPIを叩くことだけです。
下記例に沿って実行します。
curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer <YOUR-TOKEN>" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/OWNER/REPO/dispatches \
-d '{"event_type":"on-demand-test","client_payload":{"unit":false,"integration":true}}'
Tリポジトリ
ECSを構築するTFのコードは、本稿の目的外なので関連箇所以外は省略します。
locals.tf
locals {
container_name = "app"
container_port = 3000
image_tag = "hogehoge"
ecr_repository_name = "sample-express-app"
}
task_definition.json
[
{
"name": "${container_name}",
"image": "${account_id}.dkr.ecr.${region}.amazonaws.com/${ecr_repository_name}:${image_tag}",
"portMappings": [
{
"containerPort": ${container_port},
"protocol": "tcp"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/app",
"awslogs-region": "${region}",
"awslogs-stream-prefix": "ecs"
}
}
}
]
.github/workflows/update-ecs.yml
name: "Update ECS Task Definition"
on:
repository_dispatch:
types: [update-ecs-image]
env:
IMAGE_TAG: ${{ github.event.client_payload.image_tag }}
SHA: ${{ github.event.client_payload.sha }}
BRANCH_NAME: feature/update_ecs_image_${{ github.event.client_payload.sha }}
jobs:
update-ecs:
name: "Dispatch"
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
- name: Update Image Tag in locals.tf
run: |
# locals.tfのパスを指定
LOCALS_FILE="${GITHUB_WORKSPACE}/locals.tf"
# image_tagの値を更新
sed -i "s/image_tag *= *\".*\"/image_tag = \"$IMAGE_TAG\"/" "$LOCALS_FILE"
echo "=== After Update ==="
cat "$LOCALS_FILE"
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: "ap-northeast-1"
role-to-assume: "arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-for-ecs-deploy-role"
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: terraform plan -out=tfplan
# 環境に応じてplanのみにするなど検討
- name: Terraform Apply
run: terraform apply tfplan
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
title: "Update ECS Task"
commit-message: Update ECS Task Definition Image Tag
body: |
Updates the ECS task definition with new image tag:
- Tag: ${{ env.IMAGE_TAG }}
- Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
branch: ${{ env.BRANCH_NAME }}
base: main
labels: |
automated pr
ecs-update
Webhook でリクエストされたクライアントペイロードを受け取って GitHub Actions のworkflow を実行します。
下記例に沿って記述します。
on:
repository_dispatch:
types: [test_result]
jobs:
run_if_failure:
if: ${{ !github.event.client_payload.passed }}
runs-on: ubuntu-latest
steps:
- env:
MESSAGE: ${{ github.event.client_payload.message }}
run: echo $MESSAGE
本稿では、PRを出すのに、Create Pull Request Actions を利用しました。
実行してみる
CircleCI Pipeline
GHA workflows
GitHub PR
ECS タスク
さいごに
本記事では、CircleCI と GitHub Actions を組み合わせたECS (Fargate) へのデプロイパイプラインを構築してみました。
なお、ロールバックについては検討の上、準備しておく必要があると考えます。
完全自動のロールバックなのか、タグベースの手動ロールバックなのか、チームの開発事情に応じた適切な方法を選択して、明確な手順とフローを共有しておくことが重要だと考えます。