Amazon ECRを利用したCIの高速化

こんにちは!開発本部 SINIS for X 開発チームの西野(@fingerEase24)です。

今回はAmazon ECRを利用したCIの高速化を実施したので、その手順についてお話しします。

なお、CIにはGitHub Actionsを利用しており、インフラ環境についてはTerraformによるIaC管理を行っています。

想定読者

  • Dockerを利用したコンテナ化を行っている
  • CIの実行時間を短縮したいと考えている

実施内容

以下の流れで作業を行いました。

  1. CIに利用するイメージ用のECRリポジトリを作成する
  2. OpenID Connectを利用したAWS認証の設定
  3. CIで最新のイメージをECRにpushするワークフローを追加
  4. CIの各ワークフローでECRからイメージをpullして利用

それぞれ、コード例とともに解説します。

CIに利用するイメージ用のECRリポジトリを作成する

CIでイメージをpush, pullするためのECRリポジトリを作成します。

CIでDockerイメージを利用するGitHubリポジトリごとに作成すると良いでしょう。

以下はバックエンド用の作成例です。

resource "aws_ecr_repository" "example_backend_ci" {
  name = "example-backend-ci"

  image_scanning_configuration {
    scan_on_push = true
  }

  encryption_configuration {
    encryption_type = "KMS"
  }
}

OpenID Connectを利用したAWS認証の設定

GitHub ActionsでサポートされているOpenID Connect(OIDC)を利用したAWS認証を行い、作成したECRリポジトリにアクセスできるようにします。

詳細については以下の公式ドキュメントをご参照ください。

docs.github.com

まず、IDプロバイダを作成します。

data "http" "github_actions_openid_configuration" {
  url = "https://token.actions.githubusercontent.com/.well-known/openid-configuration"
}

data "tls_certificate" "github_actions" {
  url = jsondecode(data.http.github_actions_openid_configuration.response_body).jwks_uri
}

resource "aws_iam_openid_connect_provider" "github_actions" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = data.tls_certificate.github_actions.certificates[*].sha1_fingerprint
}

次に、IAMポリシーとロールを作成します。

data "aws_iam_policy_document" "example_backend_assume_role_policy" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRoleWithWebIdentity"]

    principals {
      type        = "Federated"
      identifiers = ["arn:aws:iam::${your-account-id}:oidc-provider/token.actions.githubusercontent.com"]
    }

    condition {
      test     = "StringEquals"
      variable = "token.actions.githubusercontent.com:aud"
      values   = ["sts.amazonaws.com"]
    }

    condition {
      test     = "StringLike"
      variable = "token.actions.githubusercontent.com:sub"
      values   = ["repo:${your-repository-url}:*"] # 対象リポジトリの全てのワークフローで認証を許可
    }
  }
}

resource "aws_iam_role" "example_backend" {
  name               = "oidc-example-backend-role"
  assume_role_policy = data.aws_iam_policy_document.example_backend_assume_role_policy.json
}

resource "aws_iam_role_policy_attachment" "example_backend_ecr_power_user" {
  role       = aws_iam_role.example_backend.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser"
}

IAMポリシーとロールは、対象のGitHubリポジトリの数だけ作成しておきます。

CIで最新のイメージをECRにpushするワークフローを追加

これでGitHub Actionsから作成したECRリポジトリにアクセスできるようになったため、まずはDockerfileに更新があった場合にイメージをビルドしてpushするCIワークフローを追加します。

name: Push Docker image

on:
  push:
    # mainブランチにマージされた変更差分にDockerfileが含まれていた場合に実行
    branches: [main]
    paths:
      - "src/Dockerfile"
  workflow_dispatch: # 手動でも実行可能にする

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build-and-push:
    runs-on: ubuntu-24.04
    timeout-minutes: 10
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: AWS authentication
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: "ap-northeast-1"
          role-to-assume: "arn:aws:iam::${your-account-id}:role/oidc-example-backend-role"

      - name: Login to ECR
        uses: aws-actions/amazon-ecr-login@v2
        id: login-ecr

      - name: Build and push Docker image to ECR
        working-directory: ./src
        env:
          REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          REPOSITORY: "example-backend-ci"
        run: |
          docker build . -t $REGISTRY/$REPOSITORY:latest
          docker push $REGISTRY/$REPOSITORY:latest

初回作成時には手動でこのワークフローを実行し、対象のECRリポジトリにイメージが作成されていることを確認します。

CIの各ワークフローでECRからイメージをpullして利用

実際にDockerイメージを利用するワークフローで、ECRからイメージを取得してくるようにします。

これにより、該当ワークフローの実行ごとにDockerイメージをビルドする必要がなくなり、CI実行時間が短縮されます。

以下は、対象ワークフローにおけるジョブの設定例です。

jobs:
  api:
    runs-on: ubuntu-24.04
    timeout-minutes: 10
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: AWS authentication
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: "ap-northeast-1"
          role-to-assume: "arn:aws:iam::${your-account-id}:role/oidc-example-backend-role"

      - name: Login to ECR
        uses: aws-actions/amazon-ecr-login@v2
        id: login-ecr

      - name: Pull Docker image from ECR
        env:
          REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          REPOSITORY: "example-backend-ci"
        run: |
          docker pull $REGISTRY/$REPOSITORY:latest
          docker tag $REGISTRY/$REPOSITORY:latest example_backend # compose.ymlで指定しているイメージタグを設定

      # 以下後続の処理

成果

実際にバックエンドリポジトリで実行しているCIの実行時間を前後で比較します。

Before: 約3分

After: 2分弱

1回あたりおおよそ1分強の実行時間短縮につながりました。

運用ルールと注意点

Dockerイメージに加えた変更は、mainブランチにマージされて初めて適用されるため、基本的にDockerイメージに変更を加える際はそれ単体でPull Requestを作成するようにしています。

これは、できる限りPull Requestの粒度を小さくするという点においても良く作用します。

ただし、場合によってはDockerfileの変更差分だけで分離できないこともあるため、その場合は対象の作業ブランチ上でワークフローの手動実行を行うことによって対応します。

この際に既存のPull Requestがある場合は、それらのCIで利用するイメージにも影響が及ぶため注意が必要です。

まとめ

今回の記事では、Amazon ECRを利用したCIの高速化について解説しました。

GitHub Actionsのキャッシュを利用することでも同様の高速化が見込めますが、継続的デプロイのプロセスでECRを利用している場合、CIのワークフローでも同様にすることでCI/CD間で一貫性が出て見通しが良くなると思います。

CI実行時のフローや運用ルールについては、プロダクトやチーム規模の拡大に伴い、必要に応じて随時見直していければと思います。


テテマーチでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! herp.careers

エンジニアチームガイドはこちら! tetemarche01.notion.site