GitHub Actions でゼロダウンタイムリリース:canary/blue-green をパイプライン実装

アプリケーションの更新時にサービスを停止せずにデプロイを実現したい――これは多くの開発チームが抱える課題です。GitHub Actions を活用すれば、canary リリースや blue-green デプロイメントといった高度なデプロイ戦略を、コードベースで管理できるパイプラインとして実装できます。
本記事では、GitHub Actions を用いてゼロダウンタイムリリースを実現する具体的な方法を、canary デプロイと blue-green デプロイの 2 つのパターンで解説します。実際に使えるワークフロー定義とともに、それぞれの戦略の特徴や使い分けについても詳しく見ていきましょう。
背景
ゼロダウンタイムリリースの必要性
現代の Web サービスでは、24 時間 365 日の可用性が求められます。しかし、従来のデプロイ方法ではアプリケーションの更新時にサービスを一時停止する必要があり、ユーザー体験を損なう原因となっていました。
ゼロダウンタイムリリースを実現することで、以下のメリットが得られます。
- ユーザーへのサービス提供を中断せずにアップデート可能
- ビジネス機会の損失を防ぐ
- デプロイ頻度を上げられる(リリースサイクルの高速化)
- 問題発生時の迅速なロールバックが可能
デプロイ戦略の種類
ゼロダウンタイムを実現するデプロイ戦略には、主に以下のパターンがあります。
# | 戦略名 | 特徴 | 適用シーン |
---|---|---|---|
1 | Canary リリース | 一部のユーザーに先行公開し、段階的に展開 | 新機能のリスク評価が必要な場合 |
2 | Blue-Green デプロイ | 新旧環境を完全に分離し、瞬時に切り替え | 全体的なバージョンアップや大規模変更 |
3 | Rolling デプロイ | インスタンスを順次更新 | Kubernetes などのオーケストレーション環境 |
本記事では、GitHub Actions で実装しやすく、かつ多くのユースケースに対応できる canary リリース と blue-green デプロイ に焦点を当てます。
GitHub Actions によるデプロイ自動化
GitHub Actions は、CI/CD パイプラインをコードとして管理できる強力なツールです。YAML 形式でワークフローを定義し、GitHub リポジトリ内で完結するため、以下の利点があります。
- リポジトリと連携した自動化(プルリクエスト、タグ、ブランチごとの処理)
- 豊富な公式・コミュニティアクションの活用
- シークレット管理の統合
- 実行履歴の可視化とデバッグ
これらの特性を活かして、高度なデプロイ戦略を実装していきます。
デプロイ戦略の全体像を図で示すと、以下のようになります。
mermaidflowchart TB
repo["GitHub<br/>リポジトリ"] -->|push/tag| actions["GitHub Actions<br/>ワークフロー"]
actions -->|デプロイ戦略選択| choice{戦略}
choice -->|Canary| canary["Canary<br/>リリース"]
choice -->|Blue-Green| bluegreen["Blue-Green<br/>デプロイ"]
canary --> prod1["本番環境<br/>10% トラフィック"]
canary --> prod2["本番環境<br/>100% トラフィック"]
bluegreen --> blue["Blue 環境<br/>(現行版)"]
bluegreen --> green["Green 環境<br/>(新版)"]
bluegreen --> switch["トラフィック<br/>切り替え"]
この図から、リポジトリへのコミットをトリガーに、選択したデプロイ戦略に応じて本番環境へ段階的またはスイッチング方式でリリースする流れが理解できます。
課題
従来のデプロイ方法の問題点
従来の単純なデプロイ(一度にすべてのインスタンスを置き換える方法)には、以下の課題がありました。
# | 課題 | 影響 |
---|---|---|
1 | サービス停止時間の発生 | ユーザーがアクセスできない期間が生じる |
2 | リスクの高いリリース | 問題発生時に全ユーザーが影響を受ける |
3 | ロールバックの困難さ | 元の状態に戻すのに時間がかかる |
4 | デプロイ頻度の制限 | リスクが高いため、リリースを頻繁に行えない |
ゼロダウンタイム実現の技術的ハードル
ゼロダウンタイムリリースを実現するには、以下の技術的課題をクリアする必要があります。
トラフィック制御の複雑さ
新旧バージョンへのトラフィック振り分けを動的に制御する必要があります。ロードバランサーや API Gateway の設定を自動化し、段階的な切り替えやロールバックを実現しなければなりません。
状態管理とデータベース互換性
データベーススキーマの変更が伴う場合、新旧バージョンが同時に稼働する期間中も両方のバージョンが正常に動作する必要があります。これには後方互換性を保つ設計が求められます。
ヘルスチェックと監視
新バージョンが正常に動作しているかをリアルタイムで監視し、問題があれば自動的にロールバックする仕組みが必要です。メトリクスの収集とアラートの設定が欠かせません。
インフラストラクチャのコード化
デプロイ戦略を確実に再現可能にするため、インフラ設定もコードとして管理する必要があります。GitHub Actions のワークフロー YAML だけでなく、Terraform や CloudFormation などの IaC ツールとの連携も重要です。
以下の図は、ゼロダウンタイムデプロイで考慮すべき要素を整理したものです。
mermaidflowchart LR
deploy["ゼロダウンタイム<br/>デプロイ"] --> traffic["トラフィック<br/>制御"]
deploy --> state["状態管理<br/>DB 互換性"]
deploy --> health["ヘルスチェック<br/>監視"]
deploy --> iac["インフラ<br/>コード化"]
traffic --> lb["ロードバランサー<br/>設定自動化"]
state --> migration["マイグレーション<br/>戦略"]
health --> metrics["メトリクス<br/>収集"]
iac --> workflow["GitHub Actions<br/>ワークフロー"]
これらの課題を GitHub Actions のワークフロー内でどのように解決するかが、次の「解決策」セクションのポイントとなります。
解決策
Canary リリースの実装
Canary リリースは、新バージョンを一部のユーザー(またはトラフィックの一部)にのみ公開し、問題がないことを確認してから全体に展開する手法です。
基本的な流れ
- 新バージョンを本番環境の一部(例:10%)にデプロイ
- メトリクスを監視し、エラー率やレスポンスタイムを確認
- 問題がなければ段階的に展開率を上げる(50% → 100%)
- 問題があればロールバック
この戦略により、リスクを最小限に抑えながら新機能をリリースできます。
以下は、GitHub Actions で canary リリースを実現するワークフローの概要図です。
mermaidflowchart TD
start["ワークフロー<br/>開始"] --> build["ビルド<br/>テスト"]
build --> deploy10["Canary デプロイ<br/>10% トラフィック"]
deploy10 --> wait1["待機<br/>5 分"]
wait1 --> check1{"ヘルスチェック<br/>成功?"}
check1 -->|No| rollback["ロールバック"]
check1 -->|Yes| deploy50["展開<br/>50% トラフィック"]
deploy50 --> wait2["待機<br/>5 分"]
wait2 --> check2{"ヘルスチェック<br/>成功?"}
check2 -->|No| rollback
check2 -->|Yes| deploy100["展開<br/>100% トラフィック"]
deploy100 --> done["完了"]
rollback --> done
この図から、段階的にトラフィックを増やしながら、各ステップでヘルスチェックを行い、問題があればロールバックする流れが理解できます。
Blue-Green デプロイの実装
Blue-Green デプロイは、本番環境と同一の環境(Green)を用意し、そちらに新バージョンをデプロイした後、トラフィックを一斉に切り替える手法です。
基本的な流れ
- 現在の本番環境(Blue)と同じ構成の新環境(Green)を構築
- Green 環境に新バージョンをデプロイ
- Green 環境でテストとヘルスチェックを実施
- ロードバランサーのトラフィックを Blue から Green に切り替え
- 問題があれば即座に Blue に戻す(ロールバック)
この戦略は、大規模な変更や完全な環境切り替えが必要な場合に適しています。
Blue-Green デプロイの流れを図で示します。
mermaidflowchart LR
lb["ロードバランサー"] --> blue["Blue 環境<br/>(現行版 v1.0)"]
lb -.->|切り替え前| green["Green 環境<br/>(新版 v2.0)"]
green --> test["テスト<br/>ヘルスチェック"]
test --> switch["トラフィック<br/>切り替え"]
switch --> lb2["ロードバランサー"]
lb2 -.->|切り替え後| blue
lb2 --> green
style blue fill:#4A90E2
style green fill:#7ED321
この図では、Blue 環境で稼働中のサービスを維持しながら、Green 環境に新バージョンをデプロイし、検証後にトラフィックを切り替える様子を表しています。
GitHub Actions ワークフローの設計原則
両方のデプロイ戦略を GitHub Actions で実装する際、以下の設計原則を守ることが重要です。
再利用可能なアクションの活用
共通処理(ビルド、テスト、デプロイ)は再利用可能なワークフローやコンポジットアクションとして切り出し、メンテナンス性を高めます。
環境ごとの設定管理
GitHub Environments を活用し、開発・ステージング・本番環境ごとに異なるシークレットや変数を管理します。承認フローも設定可能です。
ロールバック戦略の組み込み
デプロイ失敗時の自動ロールバックはもちろん、手動でロールバックできる仕組み(workflow_dispatch トリガー)も用意します。
可観測性の確保
デプロイの各段階でステータスを出力し、Slack や GitHub Issues への通知を設定することで、チーム全体が状況を把握できるようにします。
具体例
Canary リリースの完全実装例
ここでは、AWS ECS(Elastic Container Service)を対象とした canary リリースのワークフローを実装します。トラフィックの振り分けには AWS App Mesh や ALB(Application Load Balancer)のターゲットグループを使用します。
ワークフロー定義ファイルの作成
まず、.github/workflows/canary-deploy.yml
ファイルを作成します。このワークフローは、main
ブランチへのプッシュをトリガーに実行されます。
yamlname: Canary Release
on:
push:
branches:
- main
環境変数とシークレットの設定
GitHub リポジトリの Settings > Secrets and variables > Actions から、以下のシークレットを設定します。
# | シークレット名 | 説明 |
---|---|---|
1 | AWS_ACCESS_KEY_ID | AWS アクセスキー ID |
2 | AWS_SECRET_ACCESS_KEY | AWS シークレットアクセスキー |
3 | ECR_REPOSITORY | ECR リポジトリ名 |
4 | ECS_CLUSTER | ECS クラスター名 |
5 | ECS_SERVICE | ECS サービス名 |
ワークフローで使用する環境変数を定義します。
yamlenv:
AWS_REGION: ap-northeast-1
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
ECS_CLUSTER: ${{ secrets.ECS_CLUSTER }}
ECS_SERVICE: ${{ secrets.ECS_SERVICE }}
IMAGE_TAG: ${{ github.sha }}
IMAGE_TAG
には GitHub のコミット SHA を使用することで、各デプロイを一意に識別できます。
ビルドとテストのジョブ
最初に、アプリケーションのビルドとテストを実行します。
yamljobs:
build:
runs-on: ubuntu-latest
steps:
- name: リポジトリのチェックアウト
uses: actions/checkout@v4
- name: Node.js のセットアップ
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
- name: 依存関係のインストール
run: yarn install --frozen-lockfile
テストを実行し、コードの品質を確認します。
yaml- name: テストの実行
run: yarn test
- name: Lint チェック
run: yarn lint
Docker イメージのビルドとプッシュ
AWS ECR にログインし、Docker イメージをビルドしてプッシュします。
yaml- name: AWS 認証情報の設定
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: ECR へのログイン
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
Docker イメージをビルドし、ECR にプッシュします。
yaml- name: Docker イメージのビルドとプッシュ
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
run: |
# Docker イメージのビルド
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
# ECR へプッシュ
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
Canary デプロイ(10% トラフィック)
新しいタスク定義を作成し、ECS サービスの 10% のトラフィックを新バージョンに振り分けます。
yamlcanary-10:
needs: build
runs-on: ubuntu-latest
steps:
- name: AWS 認証情報の設定
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: タスク定義の更新
id: task-def
run: |
# 現在のタスク定義を取得
TASK_DEFINITION=$(aws ecs describe-task-definition \
--task-definition $ECS_SERVICE \
--query 'taskDefinition' \
--output json)
# 新しいイメージタグで更新
NEW_TASK_DEF=$(echo $TASK_DEFINITION | \
jq --arg IMAGE "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \
'.containerDefinitions[0].image = $IMAGE | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)')
# 新しいタスク定義を登録
NEW_TASK_ARN=$(aws ecs register-task-definition \
--cli-input-json "$NEW_TASK_DEF" \
--query 'taskDefinition.taskDefinitionArn' \
--output text)
echo "task-arn=$NEW_TASK_ARN" >> $GITHUB_OUTPUT
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ALB のターゲットグループを使用して、トラフィックの 10% を新バージョンに振り分けます。
yaml- name: Canary デプロイ(10%)の実行
run: |
# ECS サービスを更新(10% のトラフィックを新バージョンに)
aws ecs update-service \
--cluster $ECS_CLUSTER \
--service $ECS_SERVICE \
--task-definition ${{ steps.task-def.outputs.task-arn }} \
--deployment-configuration "minimumHealthyPercent=100,maximumPercent=200" \
--desired-count 10
# デプロイの完了を待機
aws ecs wait services-stable \
--cluster $ECS_CLUSTER \
--services $ECS_SERVICE
ヘルスチェックと監視
デプロイ後、一定時間待機してメトリクスを監視します。
yaml- name: 待機(5分間)
run: sleep 300
- name: ヘルスチェック
id: health-check-10
run: |
# CloudWatch メトリクスを取得してエラー率を確認
ERROR_RATE=$(aws cloudwatch get-metric-statistics \
--namespace AWS/ECS \
--metric-name TargetResponseTime \
--dimensions Name=ServiceName,Value=$ECS_SERVICE \
--start-time $(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 300 \
--statistics Average \
--query 'Datapoints[0].Average' \
--output text)
# エラー率が閾値を超えていないか確認
if (( $(echo "$ERROR_RATE > 500" | bc -l) )); then
echo "エラー率が高すぎます: $ERROR_RATE ms"
echo "result=failed" >> $GITHUB_OUTPUT
exit 1
else
echo "ヘルスチェック成功: $ERROR_RATE ms"
echo "result=success" >> $GITHUB_OUTPUT
fi
段階的展開(50% → 100%)
ヘルスチェックが成功したら、50% への展開を行います。
yamlcanary-50:
needs: canary-10
runs-on: ubuntu-latest
steps:
- name: AWS 認証情報の設定
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: Canary デプロイ(50%)の実行
run: |
# トラフィックの 50% を新バージョンに
aws elbv2 modify-listener \
--listener-arn $LISTENER_ARN \
--default-actions Type=forward,ForwardConfig='{
"TargetGroups": [
{"TargetGroupArn": "'$OLD_TARGET_GROUP_ARN'", "Weight": 50},
{"TargetGroupArn": "'$NEW_TARGET_GROUP_ARN'", "Weight": 50}
]
}'
- name: 待機(5分間)
run: sleep 300
- name: ヘルスチェック
run: |
# 同様のヘルスチェックを実行
# (省略:10% 時と同様の処理)
最終的に 100% への展開を行います。
yamlcanary-100:
needs: canary-50
runs-on: ubuntu-latest
steps:
- name: AWS 認証情報の設定
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: 完全展開(100%)の実行
run: |
# すべてのトラフィックを新バージョンに
aws elbv2 modify-listener \
--listener-arn $LISTENER_ARN \
--default-actions Type=forward,TargetGroupArn=$NEW_TARGET_GROUP_ARN
- name: 旧バージョンのクリーンアップ
run: |
# 旧タスクを削除
aws ecs update-service \
--cluster $ECS_CLUSTER \
--service $ECS_SERVICE-old \
--desired-count 0
ロールバック処理
ヘルスチェックが失敗した場合のロールバック処理を定義します。
yamlrollback:
if: failure()
needs: [canary-10, canary-50]
runs-on: ubuntu-latest
steps:
- name: AWS 認証情報の設定
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: ロールバックの実行
run: |
# トラフィックを旧バージョンに戻す
aws elbv2 modify-listener \
--listener-arn $LISTENER_ARN \
--default-actions Type=forward,TargetGroupArn=$OLD_TARGET_GROUP_ARN
# 新バージョンのタスクを削除
aws ecs update-service \
--cluster $ECS_CLUSTER \
--service $ECS_SERVICE \
--desired-count 0
- name: Slack 通知
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "❌ Canary デプロイが失敗し、ロールバックしました",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Canary デプロイ失敗*\nコミット: `${{ github.sha }}`\n詳細: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Blue-Green デプロイの完全実装例
次に、Blue-Green デプロイのワークフローを実装します。こちらは AWS ECS と ALB を使用した例です。
ワークフロー定義ファイルの作成
.github/workflows/blue-green-deploy.yml
ファイルを作成します。
yamlname: Blue-Green Deployment
on:
push:
tags:
- 'v*'
このワークフローは、バージョンタグ(v1.0.0
など)がプッシュされた際に実行されます。
環境変数の設定
Canary リリースと同様の環境変数に加え、Blue-Green 環境固有の設定を追加します。
yamlenv:
AWS_REGION: ap-northeast-1
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
ECS_CLUSTER: ${{ secrets.ECS_CLUSTER }}
BLUE_SERVICE: ${{ secrets.BLUE_SERVICE }}
GREEN_SERVICE: ${{ secrets.GREEN_SERVICE }}
BLUE_TARGET_GROUP: ${{ secrets.BLUE_TARGET_GROUP_ARN }}
GREEN_TARGET_GROUP: ${{ secrets.GREEN_TARGET_GROUP_ARN }}
LISTENER_ARN: ${{ secrets.LISTENER_ARN }}
IMAGE_TAG: ${{ github.ref_name }}
ビルドジョブ
Canary リリースと同様のビルド処理を行います(省略)。
Green 環境へのデプロイ
Green 環境(新バージョン)へデプロイします。この時点では、まだトラフィックは Blue 環境に向いています。
yamldeploy-green:
needs: build
runs-on: ubuntu-latest
environment:
name: production-green
url: https://green.example.com
steps:
- name: リポジトリのチェックアウト
uses: actions/checkout@v4
- name: AWS 認証情報の設定
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: タスク定義のレンダリング
id: render-task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: app
image: ${{ secrets.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
ECS サービスを更新し、Green 環境に新バージョンをデプロイします。
yaml- name: Green 環境へのデプロイ
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.render-task-def.outputs.task-definition }}
service: ${{ env.GREEN_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
Green 環境のテストとヘルスチェック
デプロイ後、Green 環境で統合テストとヘルスチェックを実施します。
yamltest-green:
needs: deploy-green
runs-on: ubuntu-latest
steps:
- name: リポジトリのチェックアウト
uses: actions/checkout@v4
- name: Green 環境のヘルスチェック
run: |
# Green 環境のエンドポイントにヘルスチェックリクエスト
for i in {1..10}; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://green.example.com/health)
if [ "$STATUS" -eq 200 ]; then
echo "ヘルスチェック成功"
exit 0
fi
echo "試行 $i: ステータスコード $STATUS"
sleep 5
done
echo "ヘルスチェック失敗"
exit 1
- name: 統合テストの実行
run: |
# Green 環境に対して統合テストを実行
yarn test:e2e --baseUrl=https://green.example.com
エラー率やパフォーマンスメトリクスを確認します。
yaml- name: メトリクス確認
run: |
# CloudWatch でメトリクスを確認
ERROR_COUNT=$(aws cloudwatch get-metric-statistics \
--namespace AWS/ApplicationELB \
--metric-name HTTPCode_Target_5XX_Count \
--dimensions Name=TargetGroup,Value=$GREEN_TARGET_GROUP \
--start-time $(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 300 \
--statistics Sum \
--query 'Datapoints[0].Sum' \
--output text)
if [ "$ERROR_COUNT" != "None" ] && [ "$ERROR_COUNT" -gt 0 ]; then
echo "エラーが検出されました: $ERROR_COUNT 件"
exit 1
fi
echo "メトリクス確認完了"
トラフィックの切り替え
テストが成功したら、ALB のリスナールールを変更してトラフィックを Green 環境に切り替えます。
yamlswitch-traffic:
needs: test-green
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- name: AWS 認証情報の設定
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: トラフィックを Green に切り替え
run: |
# ALB リスナーのデフォルトアクションを Green に変更
aws elbv2 modify-listener \
--listener-arn $LISTENER_ARN \
--default-actions Type=forward,TargetGroupArn=$GREEN_TARGET_GROUP
echo "トラフィックを Green 環境に切り替えました"
切り替え後、本番環境での監視を行います。
yaml- name: 本番環境の監視(10分間)
run: |
echo "本番環境を 10 分間監視します"
sleep 600
# エラー率を確認
ERROR_RATE=$(aws cloudwatch get-metric-statistics \
--namespace AWS/ApplicationELB \
--metric-name HTTPCode_Target_5XX_Count \
--dimensions Name=TargetGroup,Value=$GREEN_TARGET_GROUP \
--start-time $(date -u -d '10 minutes ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 600 \
--statistics Sum \
--query 'Datapoints[0].Sum' \
--output text)
if [ "$ERROR_RATE" != "None" ] && [ "$ERROR_RATE" -gt 10 ]; then
echo "エラー率が高すぎます: $ERROR_RATE 件"
exit 1
fi
echo "本番環境は正常に稼働しています"
Blue 環境のクリーンアップ
Green 環境が安定稼働していることを確認したら、Blue 環境(旧バージョン)をクリーンアップします。ただし、すぐには削除せず、一定期間保持しておきます。
yamlcleanup-blue:
needs: switch-traffic
runs-on: ubuntu-latest
steps:
- name: AWS 認証情報の設定
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: Blue 環境のスケールダウン
run: |
# Blue 環境のタスク数を減らす(完全には削除しない)
aws ecs update-service \
--cluster $ECS_CLUSTER \
--service $BLUE_SERVICE \
--desired-count 1
echo "Blue 環境を 1 タスクに縮小しました(ロールバック用に保持)"
ロールバック用ワークフロー
問題が発生した場合に手動でロールバックできるワークフローを別途用意します。
yamlmanual-rollback:
if: failure() || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: AWS 認証情報の設定
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: トラフィックを Blue に戻す
run: |
# Blue 環境にスケールアップ
aws ecs update-service \
--cluster $ECS_CLUSTER \
--service $BLUE_SERVICE \
--desired-count 3
# サービスが安定するまで待機
aws ecs wait services-stable \
--cluster $ECS_CLUSTER \
--services $BLUE_SERVICE
# トラフィックを Blue に切り替え
aws elbv2 modify-listener \
--listener-arn $LISTENER_ARN \
--default-actions Type=forward,TargetGroupArn=$BLUE_TARGET_GROUP
echo "ロールバック完了: トラフィックを Blue 環境に戻しました"
- name: Slack 通知
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "⚠️ Blue-Green デプロイがロールバックされました",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*ロールバック実行*\nタグ: `${{ github.ref_name }}`\nトラフィックを Blue 環境に戻しました"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
監視とアラートの統合
両方のデプロイ戦略において、監視とアラートは重要な要素です。以下に、CloudWatch と Datadog を使用した監視の設定例を示します。
CloudWatch アラームの作成
デプロイワークフロー内で、CloudWatch アラームを自動作成します。
yaml- name: CloudWatch アラームの作成
run: |
# エラー率アラームの作成
aws cloudwatch put-metric-alarm \
--alarm-name "$ECS_SERVICE-high-error-rate" \
--alarm-description "5XX エラーが多発" \
--metric-name HTTPCode_Target_5XX_Count \
--namespace AWS/ApplicationELB \
--statistic Sum \
--period 60 \
--evaluation-periods 2 \
--threshold 10 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=TargetGroup,Value=$TARGET_GROUP_ARN \
--alarm-actions $SNS_TOPIC_ARN
# レスポンスタイムアラームの作成
aws cloudwatch put-metric-alarm \
--alarm-name "$ECS_SERVICE-slow-response" \
--alarm-description "レスポンスタイムが遅い" \
--metric-name TargetResponseTime \
--namespace AWS/ApplicationELB \
--statistic Average \
--period 60 \
--evaluation-periods 3 \
--threshold 1.0 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=TargetGroup,Value=$TARGET_GROUP_ARN \
--alarm-actions $SNS_TOPIC_ARN
Datadog へのデプロイイベント送信
デプロイの開始・完了・失敗を Datadog に送信し、ダッシュボードで可視化します。
yaml- name: Datadog デプロイイベント送信
run: |
curl -X POST "https://api.datadoghq.com/api/v1/events" \
-H "Content-Type: application/json" \
-H "DD-API-KEY: ${{ secrets.DATADOG_API_KEY }}" \
-d '{
"title": "デプロイ開始: '"$ECS_SERVICE"'",
"text": "バージョン: '"$IMAGE_TAG"'\n戦略: Canary Release",
"priority": "normal",
"tags": ["service:'"$ECS_SERVICE"'", "env:production", "deployment:canary"],
"alert_type": "info"
}'
データベースマイグレーションの統合
ゼロダウンタイムデプロイでは、データベーススキーマの変更も考慮する必要があります。以下は、後方互換性を保ちながらマイグレーションを実行する例です。
段階的マイグレーション戦略
データベース変更を 3 段階に分けて実行します。
- 拡張フェーズ:新しいカラムやテーブルを追加(旧バージョンは新カラムを無視)
- 移行フェーズ:新バージョンをデプロイし、新カラムを使用開始
- 縮退フェーズ:旧カラムやテーブルを削除
ワークフローに組み込む例を示します。
yamldatabase-migration:
needs: build
runs-on: ubuntu-latest
steps:
- name: リポジトリのチェックアウト
uses: actions/checkout@v4
- name: マイグレーションの実行(拡張フェーズ)
run: |
# 新しいカラムを追加(旧バージョンは影響を受けない)
yarn migration:run --scope=expand
# 例: ALTER TABLE users ADD COLUMN email_verified BOOLEAN DEFAULT FALSE;
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
- name: データ移行スクリプトの実行
run: |
# 既存データを新しいカラムに移行
yarn migration:data-copy
デプロイ完了後、旧カラムを削除する縮退フェーズを実行します。
yamlcleanup-database:
needs: [canary-100, switch-traffic]
runs-on: ubuntu-latest
steps:
- name: リポジトリのチェックアウト
uses: actions/checkout@v4
- name: マイグレーションの完了(縮退フェーズ)
run: |
# 新バージョンが安定稼働したら、旧カラムを削除
yarn migration:run --scope=contract
# 例: ALTER TABLE users DROP COLUMN old_email_status;
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
以下の図は、段階的マイグレーションとデプロイの関係を示しています。
mermaidsequenceDiagram
participant DB as データベース
participant Old as 旧バージョン
participant New as 新バージョン
Note over DB: 拡張フェーズ
DB->>DB: 新カラム追加
Old->>DB: 旧カラムを使用
Note over Old,New: デプロイ開始
New->>DB: 新カラムを使用開始
Old->>DB: 旧カラムを使用(並行稼働)
Note over Old,New: トラフィック切り替え
New->>DB: 新カラムを使用
Note over DB: 縮退フェーズ
DB->>DB: 旧カラム削除
New->>DB: 新カラムを使用
この図から、旧バージョンと新バージョンが並行稼働する期間中も、データベーススキーマが両方に対応している様子が理解できます。
まとめ
GitHub Actions を活用したゼロダウンタイムリリースの実装方法を、canary リリースと blue-green デプロイの 2 つの戦略で詳しく解説しました。
主要なポイント
Canary リリースの特徴
段階的にトラフィックを増やしながら新バージョンをリリースする手法で、リスクを最小限に抑えられます。10% → 50% → 100% と展開し、各段階でヘルスチェックを行うことで、問題を早期に検出できます。
Blue-Green デプロイの特徴
完全に分離された 2 つの環境を用意し、一斉にトラフィックを切り替える手法です。テストを十分に行ってから切り替えるため、確実性が高く、ロールバックも瞬時に行えます。
戦略の使い分け
# | シーン | 推奨戦略 | 理由 |
---|---|---|---|
1 | 新機能の段階的リリース | Canary | リスク評価しながら展開可能 |
2 | 大規模なバージョンアップ | Blue-Green | 全体テスト後に一斉切り替え |
3 | データベース変更を伴う | Blue-Green | 互換性テストを完全に実施可能 |
4 | 高頻度のマイナーアップデート | Canary | 迅速な展開とフィードバック |
実装のベストプラクティス
再利用可能なワークフローの構築
共通処理をコンポジットアクションや再利用可能なワークフローとして切り出すことで、メンテナンス性が向上します。複数のサービスで同じデプロイ戦略を使用する場合に特に有効です。
監視とアラートの統合
CloudWatch、Datadog、Prometheus などの監視ツールと連携し、デプロイの各段階でメトリクスを確認することが重要です。エラー率、レスポンスタイム、リソース使用率などを自動チェックします。
ロールバック戦略の明確化
自動ロールバックだけでなく、手動でのロールバック手順も用意しておくことで、緊急時に迅速に対応できます。workflow_dispatch
トリガーを使用した手動実行ワークフローが有効です。
データベースマイグレーションの段階化
拡張・移行・縮退の 3 フェーズに分けることで、新旧バージョンが並行稼働する期間も安全にスキーマ変更を行えます。後方互換性を常に意識しましょう。
さらなる改善のヒント
パフォーマンステストの自動化
デプロイ前に負荷テストを自動実行し、パフォーマンスの劣化がないことを確認できます。Apache JMeter や k6 などのツールを GitHub Actions に統合しましょう。
セキュリティスキャンの組み込み
コンテナイメージの脆弱性スキャン(Trivy、Snyk など)をパイプラインに組み込むことで、安全なデプロイを実現できます。
マルチリージョン展開
AWS の複数リージョンや異なるクラウドプロバイダーにデプロイする場合、リージョンごとに段階的展開を行うことで、グローバルなサービスでもリスクを抑えられます。
GitHub Actions によるゼロダウンタイムリリースは、一度構築すれば継続的に恩恵を受けられる強力な仕組みです。本記事で紹介したワークフローを参考に、ぜひ皆さんのプロジェクトに導入してみてください。
関連リンク
- article
GitHub Actions でゼロダウンタイムリリース:canary/blue-green をパイプライン実装
- article
GitHub Actions 条件式チートシート:if/contains/startsWith/always/success/failure
- article
GitHub Actions を macOS ランナーで使いこなす:Xcode/コード署名/キーチェーン設定
- article
GitHub Actions 部分実行の比較:paths-filter vs if 条件 vs sparse-checkout
- article
GitHub Actions が突然失敗するときの切り分け術:ログレベル・re-run・debug secrets
- article
GitHub Actions の実行順序を完全図解:イベント → フィルタ → ジョブ → ステップの流れ
- article
Vue.js コンポーネント API 設計:props/emit/slot を最小 API でまとめる
- article
GitHub Copilot 前提のコーディング設計:コメント駆動 → テスト → 実装の最短ループ
- article
Tailwind CSS マルチブランド設計:CSS 変数と data-theme で横断対応
- article
Svelte フォーム体験設計:Optimistic UI/エラー復旧/再送戦略の型
- article
GitHub Actions でゼロダウンタイムリリース:canary/blue-green をパイプライン実装
- article
Git エイリアス 50 連発:長コマンドを一行にする仕事術まとめ
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来