📖

AWS App Runner + GitHub Actions で環境別CI/CDパイプラインを構築した話

に公開

手動デプロイで運用していたAPIサービスに、ブランチ戦略を導入してCI/CDパイプラインを構築しました。本記事では、その手順と学びを共有します。


背景・課題

Before

  • デプロイは担当者が手動で実施
  • どのコードがどの環境にデプロイされているか不明確
  • 本番・開発環境が同じDockerイメージタグ(:latest)を参照
  • ブランチ戦略なし、mainブランチに直接push

After

  • ブランチにマージするだけで自動デプロイ
  • 環境ごとに異なるイメージタグで完全分離
  • develop → staging → main の明確なフロー

構成

ブランチ戦略

ブランチ ECRタグ 環境 用途
main :prod 本番 本番リリース
staging :stg ステージング リリース前検証
develop :dev 開発 開発中の検証

アーキテクチャ

┌─────────────────────────────────────────────────────────────┐
│  GitHub Repository                                          │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐                      │
│  │ develop │  │ staging │  │  main   │                      │
│  └────┬────┘  └────┬────┘  └────┬────┘                      │
└───────┼────────────┼────────────┼───────────────────────────┘
        │            │            │
        ▼            ▼            ▼
┌─────────────────────────────────────────────────────────────┐
│  GitHub Actions                                             │
│  - Docker build                                             │
│  - ECR push (環境別タグ)                                      │
└───────┼────────────┼────────────┼───────────────────────────┘
        │            │            │
        ▼            ▼            ▼
┌─────────────────────────────────────────────────────────────┐
│  Amazon ECR                                                 │
│  ┌────────┐  ┌────────┐  ┌────────┐                         │
│  │  :dev  │  │  :stg  │  │ :prod  │                         │
│  └────┬───┘  └────┬───┘  └────┬───┘                         │
└───────┼───────────┼───────────┼─────────────────────────────┘
        │           │           │
        ▼           ▼           ▼
┌─────────────────────────────────────────────────────────────┐
│  AWS App Runner (自動デプロイ有効)                             │
│  ┌────────┐  ┌────────┐  ┌────────┐                         │
│  │  dev   │  │  stg   │  │  prod  │                         │
│  └────────┘  └────────┘  └────────┘                         │
└─────────────────────────────────────────────────────────────┘

実装手順

1. ブランチ作成

mainブランチから staging / develop を作成。

# mainブランチの最新コミットSHAを取得
gh api repos/{owner}/{repo}/git/refs/heads/main --jq '.object.sha'

# ブランチ作成
gh api repos/{owner}/{repo}/git/refs \
  -f ref="refs/heads/staging" \
  -f sha="{main_sha}"

gh api repos/{owner}/{repo}/git/refs \
  -f ref="refs/heads/develop" \
  -f sha="{main_sha}"

2. GitHub Actions用IAMユーザー作成

最小権限の原則に従い、ECR pushのみ許可するIAMユーザーを作成。

# ユーザー作成
aws iam create-user --user-name github-actions-ecr-push

# ポリシー作成
cat > ecr-push-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ECRAuth",
      "Effect": "Allow",
      "Action": "ecr:GetAuthorizationToken",
      "Resource": "*"
    },
    {
      "Sid": "ECRPush",
      "Effect": "Allow",
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "ecr:PutImage",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload"
      ],
      "Resource": "arn:aws:ecr:ap-northeast-1:{account_id}:repository/{repo_name}"
    }
  ]
}
EOF

aws iam create-policy \
  --policy-name GitHubActionsECRPushPolicy \
  --policy-document file://ecr-push-policy.json

# ポリシーをアタッチ
aws iam attach-user-policy \
  --user-name github-actions-ecr-push \
  --policy-arn arn:aws:iam::{account_id}:policy/GitHubActionsECRPushPolicy

# アクセスキー作成
aws iam create-access-key --user-name github-actions-ecr-push

3. GitHub Secrets 設定

リポジトリの Settings → Secrets and variables → Actions で追加。

Name Value
AWS_ACCESS_KEY_ID IAMユーザーのアクセスキー
AWS_SECRET_ACCESS_KEY IAMユーザーのシークレットキー

4. GitHub Actions ワークフロー作成

ブランチごとに異なるタグをpushするワークフローを作成。

name: Deploy to ECR

on:
  push:
    branches:
      - main
      - staging
      - develop

env:
  AWS_REGION: ap-northeast-1
  ECR_REPOSITORY: my-api

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set image tag based on branch
        id: tag
        run: |
          case "${{ github.ref_name }}" in
            main)    echo "image_tag=prod" >> $GITHUB_OUTPUT ;;
            staging) echo "image_tag=stg" >> $GITHUB_OUTPUT ;;
            develop) echo "image_tag=dev" >> $GITHUB_OUTPUT ;;
          esac

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

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

      - name: Build and push Docker image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ steps.tag.outputs.image_tag }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

ポイント: 各ブランチにこのワークフローファイルを配置する必要があります。

5. App Runner 設定変更

各環境が異なるタグを参照するように変更し、自動デプロイを有効化。

aws apprunner update-service \
  --service-arn {service_arn} \
  --source-configuration '{
    "ImageRepository": {
      "ImageIdentifier": "{account_id}.dkr.ecr.ap-northeast-1.amazonaws.com/{repo}:dev",
      "ImageRepositoryType": "ECR",
      "ImageConfiguration": {
        "Port": "8080"
      }
    },
    "AutoDeploymentsEnabled": true,
    "AuthenticationConfiguration": {
      "AccessRoleArn": "arn:aws:iam::{account_id}:role/service-role/AppRunnerECRAccessRole"
    }
  }'

ハマったポイント

1. App Runnerのソースタイプは変更不可

ECRで作成したApp RunnerサービスをGitHub直接連携に変更することはできません。

解決策: ECR経由のまま、GitHub Actions でECRにpush → App Runnerの自動デプロイで連携。

2. 設定変更とイメージpushのタイミング問題

App Runnerのタグ設定変更と、GitHub ActionsによるECR pushが同時に走ると、イメージが存在しない状態で参照してロールバックが発生。

解決策: GitHub Actionsが完了してからApp Runnerの設定変更を行う、または手動で再デプロイをトリガー。

3. :latest タグの危険性

全環境が :latest を参照していると、developの変更が本番に影響する可能性がある。

解決策: 環境別タグ(:prod, :stg, :dev)で完全分離。


結果

開発フローの改善

Before: 手動でECRにpush → 手動でApp Runnerデプロイ
After:  ブランチにマージ → 自動でデプロイ完了

環境の分離

環境 タグ 自動デプロイ
開発 :dev
ステージング :stg
本番 :prod

チーム開発の基盤

  • PRベースの開発フロー確立
  • develop → staging → main の段階的リリース
  • 「どのコードがどの環境にあるか」が明確に

ECS Fargateの場合

App Runnerではなく ECS Fargate を使う場合も、基本的な流れは同じです。

違いは以下の点:

  1. IAM権限の追加: ECSデプロイ権限が必要
  2. ワークフローの変更: aws ecs update-service --force-new-deployment でデプロイ
- name: Deploy to ECS
  run: |
    aws ecs update-service \
      --cluster my-cluster \
      --service ${{ steps.env.outputs.service }} \
      --force-new-deployment

- name: Wait for deployment
  run: |
    aws ecs wait services-stable \
      --cluster my-cluster \
      --services ${{ steps.env.outputs.service }}

まとめ

手動デプロイからCI/CDパイプラインへの移行は、以下の効果がありました:

  1. デプロイの自動化 - 手作業の削減、ヒューマンエラーの防止
  2. 環境の分離 - 開発の変更が本番に影響しない安心感
  3. トレーサビリティ - どのコードがどの環境にあるか明確
  4. チーム開発の基盤 - PRベースの開発フロー確立

AWS App Runner + GitHub Actions の組み合わせは、比較的シンプルにCI/CDを実現できるのでおすすめです。

Discussion