CircleCIでblue-greenデプロイメントを設定する方法

このチュートリアルで学ぶ内容:

blue-greenデプロイメントは、同一の本番環境を2つ実行し、新しいバージョンをリリースする際にトラフィックを切り替える手法です。

1つの環境が「アクティブ」となり、すべてのトラフィックを処理します。もう一方はアイドル状態です。デプロイ時には、新しいバージョンをアイドル環境にプッシュし、検証した後でトラフィックを切り替えます。問題が発生した場合、トラフィックは数秒で元に戻せます。Podの再起動は発生せず、ロールバックのためのローリングアップデートも必要ありません。

CI/CDワークフロー(コードの変更が自動的にビルド・テスト・デプロイされる仕組み)では、blue-greenはデプロイステップとリリースステップを明確に分離します。CircleCIは各ステージを自動化します。イメージのビルド、アイドル環境へのデプロイ、検証テストの実行、承認ゲートでの待機、人間による確認後のトラフィック切り替えです。

このチュートリアルでは、Kubernetes上でblue-greenデプロイメントをCircleCIパイプライン内で自動化する方法を解説します。デュアル環境マニフェスト、切り替え前検証用のプレビューService、承認ゲート付きワークフロー、デプロイ追跡、即時ロールバック、およびblue-greenで最もよく発生する障害を防ぐデータベースマイグレーションパターンをカバーします。

前提条件

開始前に、チームは以下を用意する必要があります:

  • CircleCIアカウント(無料プランで利用可能)。
  • kubectlが設定済みのKubernetesクラスタ(このチュートリアルではGKEを使用しますが、EKS、AKS、minikubeやkindなどのローカルクラスタも利用可能です)。
  • サンプルイメージのビルド用にローカルにインストールされたDocker
  • コンテナレジストリ用のDocker Hubアカウント
  • GitHubからクローンしたサンプルリポジトリ: CIRCLECI-GWP/kubernetes-blue-green-deployment

Kubernetesでのblue-greenデプロイメントの仕組み

2つのKubernetes Deploymentが並行して存在します。1つはversion: blueラベル、もう1つはversion: greenラベルが付けられています。Serviceはセレクタに一致するDeploymentにトラフィックをルーティングします。一度にアクティブになるのは1つのみで、もう一方は次のリリースまでアイドル状態で待機します。

blueからgreenへの切り替えは、Serviceのセレクタをversion: blueからversion: greenにパッチすることで行います。これはメタデータの変更であり、Pod操作ではありません。トラフィックは即座に切り替わります。Podの作成・終了・再起動は発生しません。

Two Kubernetes architecture diagrams comparing blue-green deployment states. Before the switch, the Service selector targets `version: blue` and routes traffic to three blue pods running v1.0 while three green v2.0 pods sit idle. After the switch, the selector targets `version: green` and routes to the green pods while the blue pods stay on standby.

切り替え前に、チームはアイドル環境を検証できます。独立した「プレビュー」Serviceは常にgreen Deploymentを指しており、パイプラインが新しいバージョンに対してスモークテスト、統合テスト、負荷テストを実行するためのエンドポイントを提供します。green Podは本番インフラストラクチャ上で本番データベースにアクセスして実行されるため、ここでのテストはステージング環境では検出できない問題を発見できます。

両方のDeploymentは同じデータベースと外部サービスを共有します。これがblue-greenの障害の主な原因です。greenコードでは動作するがblueコードでは破綻するスキーマ変更は、ロールバックを不可能にします。ステップ5では、これを防ぐマイグレーションパターンを解説します。

トレードオフ: blue-greenはデプロイウィンドウ中に両方の環境が完全なレプリカセットで実行されるため、コンピュートリソースが2倍必要になります。このコストが問題となるチームには、ローリングデプロイメントが重複環境なしでPodを段階的に更新する選択肢となります。

ステップ1 — サンプルアプリケーションのビルドとコンテナ化

サンプルアプリケーションは依存関係のないNode.js HTTPサーバーです。3つのエンドポイントを提供します:

  • GET /は、実行中のバージョン、デプロイスロット(blueまたはgreen)、Podのホスト名、タイムスタンプを表示するスタイル付きHTMLステータスページを返します。
  • GET /apiは同じ情報をJSONで返します。
  • GET /healthはreadinessおよびlivenessプローブ用に{"status":"ok"}を返します。

バージョンはAPP_VERSION環境変数から取得し、スロットはDEPLOY_SLOTから取得します。両方ともKubernetesマニフェストがデプロイ時に設定します。GET /apiをblueとgreen環境で実行すると異なるslot値が返るため、切り替えが視認できます。

完全なソースはサンプルリポジトリにあります。Dockerfileはnode:20-alpineを使用し、非rootユーザーで実行します:

FROM node:20-alpine
WORKDIR /app
COPY package.json .
COPY index.js .
EXPOSE 3000
USER node
CMD ["node", "index.js"]

プッシュ前にローカルでビルドとテストを行います:

docker build -t blue-green-tutorial-app:local ./app
docker run -p 3000:3000 -e DEPLOY_SLOT=blue blue-green-tutorial-app:local

# 別のターミナルで:
curl localhost:3000/api
# {"version":"1.0.0","slot":"blue","hostname":"...","timestamp":"..."}

curl localhost:3000/health
# {"status":"ok"}

ステップ2 — Kubernetesマニフェストの作成

Kubernetes上のblue-greenには4つのマニフェストが必要です。2つのDeployment(環境ごとに1つ)と2つのService(本番用とプレビュー用)です。4つすべてでenvsubstプレースホルダーを使用し、CircleCIパイプラインがデプロイ時に値を埋め込みます。envsubstはファイル内の${VARIABLE}参照を環境変数の値で置換するため、パイプラインは適用前にイメージタグとDocker Hubユーザー名を注入できます。

Blue Deployment

k8s/deployment-blue.yml内のblue Deploymentは現在の本番バージョンを実行します。version: blueラベルにより、green Podと区別されます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: blue-green-tutorial-app-blue
  namespace: default
  annotations:
    circleci.com/project-id: ${CIRCLE_PROJECT_ID}
    circleci.com/operation-timeout: 10m
  labels:
    app: blue-green-tutorial-app
    version: blue
    circleci.com/component-name: blue-green-tutorial-app
    circleci.com/version: ${IMAGE_TAG}
spec:
  replicas: 3
  selector:
    matchLabels:
      app: blue-green-tutorial-app
      version: blue
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: blue-green-tutorial-app
        version: blue
        circleci.com/component-name: blue-green-tutorial-app
        circleci.com/version: ${IMAGE_TAG}
    spec:
      containers:
        - name: blue-green-tutorial-app
          image: ${DOCKERHUB_USERNAME}/blue-green-tutorial-app:${IMAGE_TAG}
          ports:
            - containerPort: 3000
          env:
            - name: APP_VERSION
              value: "${IMAGE_TAG}"
            - name: DEPLOY_SLOT
              value: "blue"
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 5
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 15
            periodSeconds: 10

注目すべき設定:

  • version: blueラベル。これはselector.matchLabels、Podテンプレート、本番Serviceのセレクタに登場します。これによりblue Podが独立したセットとして分離され、Serviceが特定してターゲットにできます。
  • maxUnavailable: 0maxSurge: 1。パイプラインがgreen Deploymentに新しいイメージを適用する際、置き換えが準備できるまでPodはオフラインにならず、一度に余分に実行されるPodは1つだけです。
  • circleci.com/*ラベルとアノテーション。CircleCIのデプロイ追跡は実行中のPodからこれらを読み取ります。これらはKubernetesが不変と扱うspec.selector.matchLabelsには含めないでください。完全なリファレンスはコンポーネント設定ドキュメントを参照してください。

k8s/deployment-green.yml内のgreen Deploymentは、すべてのblue参照をgreenに置き換えた以外は同一です(メタデータ名、versionラベル、DEPLOY_SLOT値)。

本番Service

k8s/service.yml内の本番Serviceは、アクティブな環境にトラフィックをルーティングします。初期状態ではversion: blueを選択します:

apiVersion: v1
kind: Service
metadata:
  name: blue-green-tutorial-app
  namespace: default
spec:
  selector:
    app: blue-green-tutorial-app
    version: blue
  ports:
    - port: 80
      targetPort: 3000
  type: LoadBalancer

トラフィックの切り替えは、このServiceのセレクタをversion: blueからversion: greenにパッチすることで行います。この1つの変更により、すべての本番トラフィックがgreen Podにリダイレクトされます。

プレビューService

k8s/service-preview.yml内のプレビューServiceは常にgreen環境を指します。新しいバージョンを本番切り替え前に検証するためのエンドポイントをパイプラインに提供します:

apiVersion: v1
kind: Service
metadata:
  name: blue-green-tutorial-app-preview
  namespace: default
spec:
  selector:
    app: blue-green-tutorial-app
    version: green
  ports:
    - port: 80
      targetPort: 3000
  type: LoadBalancer

クラスタの作成

このチュートリアルではクラスタにGKEを使用しますが、任意のKubernetesクラスタで動作します。マニフェストとパイプラインはクラウド非依存です。EKS、AKS、DigitalOcean、minikubeやkindなどのローカルクラスタでも同じワークフローが実行できます。

GKEの場合、1つのgcloudコマンドでクラスタを作成します:

gcloud container clusters create blue-green-tutorial \
  --zone=us-central1-a \
  --num-nodes=3 \
  --machine-type=e2-medium

次に新しいクラスタに接続するようkubectlを設定します:

gcloud container clusters get-credentials blue-green-tutorial --zone=us-central1-a

get-credentialsはクラスタのエンドポイントと認証情報を~/.kube/configに書き込み、アクティブなコンテキストとして設定します。以降、すべてのkubectlコマンドはこの新しいクラスタをターゲットにします。別途接続手順は必要ありません。

Google CloudのGKEクイックスタートには、プロジェクト設定、認証、課金を含む完全なセットアップ手順が記載されています。他のプロバイダーの場合は、それぞれの同等ドキュメントに従ってください。パターンは同じです。クラスタをプロビジョニングし、kubectlをそれに向けます。

進む前に接続を確認します:

kubectl get nodes

ノードが返ってきたら、このチュートリアルの残りはクラウド非依存です。以降のすべてのコマンドは、クラスタの場所に関係なく同じように動作します。

初期セットアップ

最初のパイプライン実行前に、両方のDeploymentと両方のServiceを手動で適用します:

export IMAGE_TAG=initial
export DOCKERHUB_USERNAME=<your-dockerhub-username>
export CIRCLE_PROJECT_ID=<your-circleci-project-id>

envsubst < k8s/deployment-blue.yml | kubectl apply -f -
envsubst < k8s/deployment-green.yml | kubectl apply -f -
kubectl apply -f k8s/service.yml
kubectl apply -f k8s/service-preview.yml

両方の環境が同じイメージで開始します。以降、green環境への更新はすべてパイプラインが処理します。

ステップ3 — 承認ゲート付きCircleCIパイプラインの作成

リポジトリの接続

CircleCIで左サイドバーのProjectsをクリックし、リポジトリを見つけてSet Up Projectをクリックします。Fastestオプションを選択して既存の.circleci/config.ymlを使用します。詳細な手順はCircleCIのGetting startedを参照してください。

コンテキストの作成

パイプラインにはリポジトリに保存すべきでないシークレットが必要です。CircleCIのcontextsは、ジョブ実行時に利用可能な環境変数を保存します。blue-green-tutorialという名前のコンテキストを作成します:

  1. CircleCIでOrganization Settings > Contextsに移動します。
  2. Create Contextをクリックし、blue-green-tutorialと命名します。
  3. 3つの環境変数を追加します:
変数
DOCKERHUB_USERNAME Docker Hubユーザー名
DOCKERHUB_PASSWORD Docker Hubパスワードまたはアクセストークン
KUBECONFIG_DATA Base64エンコードされたクラスタのkubeconfig

GKE用のKUBECONFIG_DATAの生成方法:

gcloud container clusters get-credentials <cluster-name> --zone <zone> --project <project-id>
cat ~/.kube/config | base64 | tr -d '[:space:]'

EKS、AKS、その他のプロバイダーの場合は、プロバイダーのドキュメントに従ってkubectlアクセスを設定し、得られたkubeconfigを同じ方法でエンコードしてください。

パイプライン設定

パイプラインは線形ワークフロー内の5つのジョブで構成されます。ここがblue-greenとローリングの違いです。1ステップでデプロイとリリースを行うのではなく、検証ゲートで分離します。

version: 2.1

commands:
  setup-kubectl:
    description: Install kubectl and configure cluster access
    steps:
      - run:
          name: Install kubectl
          command: |
            KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt)
            curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
            chmod +x kubectl
            sudo mv kubectl /usr/local/bin/kubectl
      - run:
          name: Configure kubeconfig
          command: |
            mkdir -p ~/.kube
            echo "$KUBECONFIG_DATA" | tr -d '[:space:]' | base64 --decode > ~/.kube/config
            chmod 600 ~/.kube/config

jobs:
  build-and-push:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: true
      - run:
          name: Build Docker image
          command: |
            docker build -t $DOCKERHUB_USERNAME/blue-green-tutorial-app:$CIRCLE_SHA1 ./app
      - run:
          name: Push to Docker Hub
          command: |
            echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
            docker push $DOCKERHUB_USERNAME/blue-green-tutorial-app:$CIRCLE_SHA1

  deploy-to-green:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - setup-kubectl
      - run:
          name: Install envsubst
          command: sudo apt-get update -qq && sudo apt-get install -y gettext-base
      - run:
          name: Deploy to green environment
          command: |
            export IMAGE_TAG=$CIRCLE_SHA1
            envsubst < k8s/deployment-green.yml | kubectl apply -f -
            kubectl apply -f k8s/service.yml
            kubectl apply -f k8s/service-preview.yml
      - run:
          name: Wait for green rollout
          command: kubectl rollout status deployment/blue-green-tutorial-app-green --timeout=5m

  validate-green:
    docker:
      - image: cimg/base:stable
    steps:
      - setup-kubectl
      - run:
          name: Get preview service endpoint
          command: |
            for i in $(seq 1 30); do
              PREVIEW_IP=$(kubectl get svc blue-green-tutorial-app-preview \
                -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
              if [ -n "$PREVIEW_IP" ]; then
                echo "Preview IP: $PREVIEW_IP"
                echo "export PREVIEW_IP=$PREVIEW_IP" >> "$BASH_ENV"
                break
              fi
              sleep 10
            done
            if [ -z "$PREVIEW_IP" ]; then
              echo "Preview service did not receive an external IP"
              exit 1
            fi
      - run:
          name: Smoke test - health check
          command: |
            curl -sf http://$PREVIEW_IP/health | grep -q '"status":"ok"'
      - run:
          name: Smoke test - version check
          command: |
            curl -sf http://$PREVIEW_IP/api | grep -q "$CIRCLE_SHA1"
      - run:
          name: Smoke test - slot check
          command: |
            curl -sf http://$PREVIEW_IP/api | grep -q '"slot":"green"'

  switch-traffic:
    docker:
      - image: cimg/base:stable
    steps:
      - setup-kubectl
      - run:
          name: Plan deployment
          command: |
            circleci run release plan "${CIRCLE_JOB}" \
              --environment-name="production" \
              --component-name="blue-green-tutorial-app" \
              --target-version="$CIRCLE_SHA1"
      - run:
          name: Switch production traffic to green
          command: |
            kubectl patch svc blue-green-tutorial-app \
              -p '{"spec":{"selector":{"version":"green"}}}'
      - run:
          name: Update deployment status to running
          command: circleci run release update "${CIRCLE_JOB}" --status=RUNNING
      - run:
          name: Log deploy marker
          command: |
            circleci run release log \
              --component-name=blue-green-tutorial-app \
              --environment-name=production \
              --target-version=$CIRCLE_SHA1
      - run:
          name: Update deployment status to success
          command: circleci run release update "${CIRCLE_JOB}" --status=SUCCESS
          when: on_success
      - run:
          name: Update deployment status to failed
          command: circleci run release update "${CIRCLE_JOB}" --status=FAILED
          when: on_fail

workflows:
  build-deploy:
    jobs:
      - build-and-push:
          context: blue-green-tutorial
      - deploy-to-green:
          requires:
            - build-and-push
          context: blue-green-tutorial
          filters:
            branches:
              only: main
      - validate-green:
          requires:
            - deploy-to-green
          context: blue-green-tutorial
      - hold-for-approval:
          type: approval
          requires:
            - validate-green
      - switch-traffic:
          requires:
            - hold-for-approval
          context: blue-green-tutorial

各ジョブの解説:

build-and-pushはDockerイメージをビルドし、GitコミットSHA($CIRCLE_SHA1)でタグ付けします。すべてのイメージは、それを生成した正確なコミットにトレースできます。

deploy-to-greenは新しいイメージタグでgreen Deploymentマニフェストと両方のServiceマニフェストを適用します。その後、greenのロールアウト完了を待機します。この時点で新しいコードはgreen環境で実行されていますが、本番トラフィックはまだblueに向かっています。

validate-greenはプレビューServiceに対してスモークテストを実行します。ヘルスエンドポイントが応答すること、APIが期待されるバージョンを返すこと、レスポンスがgreenスロットから来ていることを確認します。いずれかのテストが失敗するとジョブは失敗し、承認ゲートは表示されません。本番はblueのままです。

hold-for-approvalはCircleCIの手動承認ジョブです。パイプラインはここで一時停止します。エンジニアは検証結果を確認し、必要に応じてプレビューエンドポイントに対して追加の手動チェックを実行し、CircleCI UIで「Approve」をクリックして続行します。これはコードのデプロイとユーザーへのリリースの間の人間によるチェックポイントです。

CircleCI pipeline view showing the build-deploy workflow paused at hold-for-approval. The job list shows build-and-push, deploy-to-green, and validate-green completed, with hold-for-approval awaiting input. A side panel offers Approve job and Cancel job buttons.

switch-trafficは本番Serviceのセレクタをversion: blueからversion: greenにパッチします。トラフィックは即座に切り替わります。これがリリースの瞬間です。

このジョブはまた、デプロイマーカーを書き込みます。release planは作業開始前に計画されたデプロイを登録します。切り替え後、release update --status=RUNNINGはアクティブであることをマークし、release logはイベントを記録します。最後のrelease updateSUCCESSまたはFAILEDでライフサイクルを閉じます。これらのマーカーはCircleCIのDeploysダッシュボードにフィードされ、各リリースはパイプラインとコミットにリンクされたタイムラインエントリとして表示されます。

デプロイチェーンはmainでのみ実行されます。プルリクエストビルドはbuild-and-pushで停止し、デプロイはトリガーされません。

CircleCI workflow view of a successfully completed Retrigger pipeline run. All five jobs in the build-deploy workflow (build-and-push, deploy-to-green, validate-green, hold-for-approval, and switch-traffic) show green checkmarks, with overall status Success.

ステップ4 — 切り替え前にgreen環境を検証する

validate-greenジョブはプレビューServiceエンドポイントに対して3つのチェックを実行します:

  1. ヘルスチェック: curl -sf http://$PREVIEW_IP/health{"status":"ok"}を返します。
  2. バージョンチェック: /apiレスポンスに期待されるコミットSHAが含まれます。
  3. スロットチェック: /apiレスポンスが"slot":"green"であることを確認します。

これらは最低限です。実際の本番セットアップでは、重要なAPIエンドポイントをカバーするスモークテストスイート、プレビューエンドポイントに対する軽量負荷テスト、アプリケーションが必要とするデータベース接続やマイグレーションチェックを追加します。プレビューServiceは安定したターゲットを提供するため、検証ジョブはHTTPリクエストを実行する任意の処理を実行できます。

テストスイートが何であれ、本番インフラストラクチャに対して実行されます。コネクションプール制限、実際のデータエッジケース、本番限定の設定値による障害は、トラフィックが切り替わる前にここで表面化します。

検証が失敗した場合、パイプラインは停止します。承認ゲートは表示されません。本番トラフィックはblueのままです。チームはgreen環境を調査し、kubectl logs -l version=greenでログを確認し、次のパイプライン実行前に問題を修正できます。

パイプラインが実行され、トラフィックがgreenに切り替わると、アプリケーションはデプロイを確認するステータスページを提供します:

Browser view of the deployed sample application showing a "Deployment successful" status card. The card displays version 901ebbb, slot green, the green pod name, and a deployment timestamp. The page is served from the GKE load balancer IP.

ステップ5 — データベースマイグレーションを安全に処理する

expand-migrate-contractパターンは、blue-greenパイプラインを通じてスキーマ変更をどちらのバージョンのアプリも破綻させることなくリリースできるようにします。3つのフェーズで動作します:

Expandフェーズ。既存の列やテーブルと並行して新しい列やテーブルを追加します。何も削除しません。このスキーマ変更をアプリケーション変更より前にデプロイします。現在のblueコードは依存していたものが削除されていないため動作し続けます。新しいgreenコードは新しい構造が存在するため動作します。

Migrateフェーズ。古い構造から新しい構造へデータをバックフィルします。これはバックグラウンドジョブとして、またはスキーマexpand適用後のパイプラインステップとして実行できます。

Contractフェーズ。すべてのトラフィックがgreenバージョンで実行され、blue環境がロールバックに不要であることが確認された後、古い列を削除します。これは別個のデプロイとして、数日または数週間後に実行されます。

例として、user_name列をusernameにリネームする場合の流れは次のようになります:

フェーズ スキーマ状態 Blueコード Greenコード
Before user_nameが存在 user_nameを読み取り
Expand 両方の列が存在、トリガーが同期 user_nameを読み取り usernameを読み取り
Switch 両方の列が存在 まだ実行中、user_nameを読み取り アクティブ、usernameを読み取り
Contract usernameのみが存在 スケールダウン usernameを読み取り

全体を通じての制約はN-1互換性です。新しいアプリバージョンは古いスキーマと新しいスキーマの両方で動作する必要があります。古いアプリバージョンも新しいスキーマで動作する必要があります。いずれかの互換性が破綻すると、ロールバックは失敗します。

スキーママイグレーションはdeploy-to-greenジョブより前にパイプライン内の別ジョブとして実行し、新しいコードが到達する前にデータベースを準備します。

ステップ6 — 即時ロールバックの設定

ロールバックは本番Serviceのセレクタをversion: blueに戻すパッチを適用します。トラフィックは即座に以前のバージョンに戻ります。Podの再起動、新しいReplicaSet、逆方向のローリングアップデートは発生しません。blue Deploymentは以前のイメージでまだ実行されているため、切り替えは数秒で効果を発揮するネットワークレベルの変更です。

.circleci/rollback.yml内のロールバックパイプラインは、CircleCI Deploysダッシュボードからこれを自動化します:

version: 2.1

commands:
  setup-kubectl:
    description: Install kubectl and configure cluster access
    steps:
      - run:
          name: Install kubectl
          command: |
            KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt)
            curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
            chmod +x kubectl && sudo mv kubectl /usr/local/bin/kubectl
      - run:
          name: Configure kubeconfig
          command: |
            mkdir -p ~/.kube
            echo "$KUBECONFIG_DATA" | tr -d '[:space:]' | base64 --decode > ~/.kube/config
            chmod 600 ~/.kube/config

jobs:
  rollback:
    docker:
      - image: cimg/base:stable
    environment:
      COMPONENT_NAME: << pipeline.deploy.component_name >>
      ENVIRONMENT_NAME: << pipeline.deploy.environment_name >>
      TARGET_VERSION: << pipeline.deploy.target_version >>
    steps:
      - setup-kubectl
      - run:
          name: Plan rollback release
          command: |
            circleci run release plan "${CIRCLE_JOB}" \
              --component-name=${COMPONENT_NAME} \
              --environment-name=${ENVIRONMENT_NAME} \
              --target-version=${TARGET_VERSION} \
              --rollback
      - run:
          name: Switch production traffic back to blue
          command: |
            kubectl patch svc blue-green-tutorial-app \
              -p '{"spec":{"selector":{"version":"blue"}}}'
      - run:
          name: Verify rollback
          command: |
            PROD_IP=$(kubectl get svc blue-green-tutorial-app \
              -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
            curl -sf http://$PROD_IP/api | grep -q '"slot":"blue"'
      - run:
          name: Update rollback status to SUCCESS
          command: circleci run release update "${CIRCLE_JOB}" --status=SUCCESS
          when: on_success
      - run:
          name: Update rollback status to FAILED
          command: circleci run release update "${CIRCLE_JOB}" --status=FAILED
          when: on_fail

  cancel-rollback:
    docker:
      - image: cimg/base:stable
    steps:
      - run:
          name: Update rollback status to CANCELED
          command: circleci run release update "${CIRCLE_JOB}" --status=CANCELED

workflows:
  rollback:
    jobs:
      - rollback:
          context: blue-green-tutorial
      - cancel-rollback:
          context: blue-green-tutorial
          requires:
            - rollback:
              - canceled

留意点:

  • pipeline.deploy.*パラメータ。CircleCIがロールバックをトリガーした際に自動的に注入されます。
  • release plan --rollback。ダッシュボードで通常のデプロイと区別します。
  • cancel-rollbackジョブ。明示的なキャンセル時のみ起動し、古い「進行中」エントリを防止します。

ロールバックパイプラインの接続

ステップ3のデプロイマーカーをmainにコミットした状態で、プロジェクトのOverviewページからRollback > Create rollback pipelineをクリックします。ウィザードは4つのステップを処理します:

  1. GitHub Appのインストール。この機能はGitHub限定です。必要に応じてウィザードがAppをインストールします。
  2. パイプライン定義。プロジェクトのリポジトリを選択します。
  3. デプロイマーカーswitch-trafficジョブに既に含まれているため、I’ll make the config changes myselfを選択し、次にI’ve updated my configを選択します。
  4. Rollback設定.circleci/rollback.ymlを使用するためI already have a rollback configを選択し、Setup rollback pipelineをクリックします。

これでロールバックはプロジェクトOverviewページからトリガー可能になります。パイプラインの切り替えについては、rollback pipeline documentationProject Settings > Deploysを参照してください。

CircleCI "Run rollback" modal. The "Rolling back" section identifies the current blue-green-tutorial-app version, while "To the following deployment" shows the previous deployment SHA and its commit message. The dialog includes an optional rollback reason field and Cancel and Rollback buttons.

ステップ7 — リリースの監視と確認

トラフィック切り替え後、切り替え前後のウィンドウでエラー率とレイテンシを比較します。切り替え直後にエラーが急増した場合、通常はgreenコードが検証テストでカバーされていなかった本番条件に遭遇したことを意味します。

データベースクエリパターンを監視します。greenコードからの新しいクエリが予期しない負荷を生む可能性があります。コネクションプール使用量とスロークエリログが最初に確認すべき場所です。

各切り替えはCircleCIのDeploysダッシュボードにタイムスタンプが記録されるため、リリースイベントはPrometheus、Datadog、Grafanaのメトリック変化と整合します。

CircleCI Deploys dashboard for the blue-green-tutorial-app component. A timeline shows four production deployments with a mix of Logged and Success statuses. Below, two deployment rows display the same component, version SHA, environment, switch-traffic trigger, and publication times. One row shows Success and the other shows Logged.

切り替え後、定義されたソーク期間(30分、1時間、チームのリスク許容度による)の間、blue環境を稼働させ続けます。リリースが安定しておりロールバックが不要であると確信できた後にのみスケールダウンします。blue Deploymentがまだ実行されている間は、ロールバックは数秒で完了します。スケールダウンした後は、以前のイメージを再デプロイする必要があります。

両方の環境の現在の状態を確認するには:

kubectl get pods -l app=blue-green-tutorial-app -L version

これによりすべてのPodとそのversionラベルが表示され、どの環境が何を実行しているかが明確になります。

CircleCIがblue-greenデプロイメントを可能にする仕組み

このパイプラインにおけるCircleCIの役割は単にステップを実行することではありません。承認ジョブがblue-greenデプロイをゲート付きリリースに変えるものです。コードはmainへのプッシュごとに本番インフラストラクチャに到達しますが、トラフィックが切り替わるのはエンジニアがCircleCI UIでApproveをクリックしたときだけです。デプロイとリリースの分離が、blue-greenのロールバックを高速にする理由です。以前のバージョンはまだ実行されているため、Serviceセレクタの反転で元に戻せます。デプロイマーカーは各リリースをDeploysダッシュボードで追跡し、ロールバックパイプラインはその同じダッシュボードから1クリックで巻き戻しを可能にします。

blue-greenは多くのデプロイメント戦略の1つです。他の戦略は速度、リスク、インフラストラクチャコストの異なるトレードオフを提供します。CircleCIのプリミティブ(承認ゲート、デプロイマーカー、ロールバックパイプライン)はすべてに適用可能です。

blue-greenを使うべき場面と使わない方が良い場面

blue-greenが適している場合:

  • 即時ロールバックが必須要件である場合(金融サービス、eコマースチェックアウト、ヘルスケア)。
  • チームがデプロイウィンドウ中に2倍のコンピュートコストを負担できる場合。
  • 本番環境での切り替え前検証が重要である場合。
  • リリース頻度が低く、ステークが高い場合。

blue-greenが不適切な場合:

  • インフラストラクチャ予算が厳しい場合(canaryはリソースのほんの一部を使用)。
  • 1日に複数回デプロイする場合(2環境のオーバーヘッドは高頻度では割に合わない)。
  • パーセンテージベースのトラフィックシフトとメトリック駆動のプロモーションが必要な場合(これはcanaryの領域)。

戦略の比較については、Kubernetes上のローリングデプロイメントを参照してください。

まとめ

このチュートリアルでは、Kubernetes上で完全なblue-greenパイプラインを構築しました。並行環境へのデプロイ、本番インフラストラクチャに対する検証、人間による承認の待機、トラフィックの切り替え、問題発生時のダッシュボードからのロールバックです。パイプライン全体はリポジトリ内のYAMLとして存在し、CircleCIの無料プランで実行でき、GKE、EKS、AKS、または任意の自己管理Kubernetesクラスタに対して動作します。

構築を開始するには、無料のCircleCIアカウントにサインアップし、パイプライン設定を自身のプロジェクトに適応させてください。