CircleCI から GitHub Actions を実行して ECS (Fargete) にデプロイしてみる

CircleCI から GitHub Actions を実行して ECS (Fargete) にデプロイしてみる

Clock Icon2025.03.22

はじめに

デプロイパイプラインを構築する際、タイトルのように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

circle_env

必要な設定

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

circli_ci2

GHA workflows

gha_workflow

GitHub PR

pr

ECS タスク

ecs

さいごに

本記事では、CircleCI と GitHub Actions を組み合わせたECS (Fargate) へのデプロイパイプラインを構築してみました。

なお、ロールバックについては検討の上、準備しておく必要があると考えます。

完全自動のロールバックなのか、タグベースの手動ロールバックなのか、チームの開発事情に応じた適切な方法を選択して、明確な手順とフローを共有しておくことが重要だと考えます。

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.