VITALIFY.ASIA logo

Blue-Green Deploymentでリリース時のダウンタイムを最小化する

Blue-Green Deploymentは、BlueとGreenの2環境を並行稼働させ、アイドル側に新バージョンをデプロイ・検証してからトラフィックを切り替えるリリース手法です。本記事では、Nginx・Compute Engine・Cloud Buildによる実装、即時ロールバック、DBマイグレーションやセッションの注意点、Rolling・Canaryとの違いまで解説します。

Dat Chu Tien
Blue-Green Deploymentでリリース時のダウンタイムを最小化する
目次

メンテナンスモード(メンテナンス画面)を使った従来のリリース方法では、リリースのたびにダウンタイムが発生し、リリース作業は業務時間外に追いやられます。さらに不具合が見つかった場合、ロールバックとはプロセス全体をもう一度実行することを意味します。

本記事では blue-green deployment を紹介します。2つの環境を並行して稼働させ、アイドル状態の環境に新バージョンをデプロイしてテストし、コマンド1つでトラフィックを切り替える手法です。本記事の内容は次のとおりです。

  • なぜメンテナンス画面を使う方法はもう適していないのか
  • Blue-green がそれぞれの問題をどのように解決するのか
  • Compute Engine 上の Nginx リバースプロキシと Cloud Build を使った実装方法
  • 導入時の注意点:データベースマイグレーション、バックグラウンドコンシューマー、セッション、コスト

1.メンテナンス画面

ソフトウェアエンジニアとして働き始めたばかりの頃、私はある新しい Web ツールを開発するプロジェクトに参加しました。機械学習モデルを使って需要の高い不動産を予測し、不動産仲介業者を支援するツールです。当時、新バージョンをリリースするたびに、チームは多くの課題を抱えた従来のプロセスを使っていました。

  1. パートナーやユーザーにリリーススケジュールを通知する。
  2. リリース担当のエンジニアがデータベースの isMaintenance = true フラグを更新し、アプリにメンテナンス画面を表示させる。
  3. エンジニアがリリースブランチに push し、Cloud Build がデプロイパイプラインを実行する。エンジニアはデプロイの進行を見守る。
  4. パイプラインが成功したら、メンテナンス中でもアクセスできるアカウントを使って機能を確認する。
  5. すべてのテスト項目が完了したら、エンジニアがフラグを isMaintenance = false に戻し、ユーザーが再び利用できるようにする。
メンテナンスモードを使ったリリースプロセス
ユーザーに表示されるメンテナンス画面

このプロセスはシンプルで、ほとんどの場合うまく機能していました。しかし、今後のすべてのリリースが常にこのような「ハッピーケース」になると保証できるエンジニアは、チームに一人もいませんでした。

2.従来のリリース方法の問題点

2.1 弱点

  • すべてのリリースでダウンタイムが発生する。 新機能であれ、ごく小さな変更であれ、メンテナンス画面がリリース中ずっとユーザーをブロックします。
  • リリースが業務時間外に追いやられる。 ダウンタイムの影響を抑えるため、更新はまとめられて決まった時間帯にリリースされます。その結果、1回のリリースが大きくなり、リスクも高まります。さらに、パートナーがさまざまなタイムゾーンで働いているため、調整もしにくくなります。
  • ロールバックはプロセス全体のやり直しになる。 新バージョンに不具合があっても、すぐに元に戻す手段はありません。チームはメンテナンス画面を表示したまま、リリースブランチのコードを revert し、Cloud Build パイプラインを再実行する必要があります。リリースに失敗するたびに、ユーザーの待ち時間はさらに長くなります。
リリースに失敗した場合

2.2 私たちに必要なもの

上記の問題から、改善したい点は次のとおりです。

  • ゼロダウンタイム。 チームがリリースしている間も、ユーザーがメンテナンス画面にブロックされないこと。
  • エンドユーザーが使う前に、本番環境でテストできること。
  • できるだけ速くロールバックできること。 パイプライン全体を再実行する必要がないこと。

3.Blue-Green Deployment の紹介

上記のシステム構成と達成すべき要件を考えると、Blue-green deployment は最適な解決策の一つです。

新バージョンをリリースする際、稼働中のバージョンを直接置き換えるのではなく、2つの独立した環境を維持します。

Blue は、エンドユーザーが現在利用しているバージョンです。
Green は新しいバージョンで、まだユーザーはいません。

両方の前にルーター(ロードバランサー、リバースプロキシなど)が置かれ、リクエストの行き先を決めます。このルーターこそが、トラフィックの向きを変える「スイッチ」です。

切り替え前:Blue が稼働中、Green はテスト中
切り替え後:Green が稼働中、Blue は待機
ロールバック:トラフィックを Blue に戻す

1回のリリースは次のような流れになります。

  1. Blue がユーザーにサービスを提供している間に、v2 を Green にデプロイする。
  2. Green 専用の URL から直接テストする。
  3. ルーターを切り替え、すべてのトラフィックを Green に向ける。
  4. Blue は即時ロールバック用に稼働させたままにしておく。
  5. 新バージョンが原因で問題が起きた場合は、ルーターを切り替え直すだけです。安定していれば、Blue が次回リリースの受け皿になります。

上記の要件と結果を比較すると、次のようになります。

チームに必要なもの Blue-green での対応
ダウンタイムなし ユーザーがアクセスする前に、Green は完全に稼働している
ユーザーに届く前にテスト Green には直接テストできる専用のアドレスがある
数秒でロールバック Blue は稼働したままなので、トラフィックを Blue に戻すだけ

4.Blue-Green Deployment の実装方法

この考え方は、アプリの前にルーターがあるシステムであれば、どこにでも適用できます。冒頭で紹介したプロジェクトでは、アプリは Nginx リバースプロキシの背後にある Compute Engine の VM 上で動いていました。

  • Blue と Green は2台の VM(app-blue と app-green)で、それぞれポート 8080 でアプリを実行します。
  • ルーター はプロキシ用 VM 上の Nginx です。もともとアプリの前に置かれていたので、そのままチームのスイッチになります。
  • スイッチ は、どの VM がライブトラフィックを受け取るかを決める小さな Nginx 設定ファイルです。このファイルを変更して Nginx をリロードすること こそが トラフィックの切り替え操作です。
インフラ構成:Nginx プロキシと2台の VM
スイッチは2つの設定ファイルだけ

ステップ 1:2つの upstream とスイッチを作成する

プロキシ用 VM で、両方の環境と2つの入口を定義します。スイッチに従う公開用の入口と、常にアイドル側を指す内部テスト用の入口です。

# /etc/nginx/conf.d/app.conf
upstream app_blue  { server 10.148.0.10:8080; }  # app-blue
upstream app_green { server 10.148.0.11:8080; }  # app-green

# Public traffic: follows the switch
server {
    listen 80;
    server_name api.example.com;

    location / {
        include /etc/nginx/live-upstream.conf;
    }
}

# Internal test entrance: always points at the idle color
server {
    listen 8081;

    location / {
        include /etc/nginx/idle-upstream.conf;
    }
}

重要: ポート 8081 はチーム専用です。VPC ファイアウォールルールを使い、内部 IP(または CI)からのみアクセスできるようにしてください。

# /etc/nginx/live-upstream.conf
proxy_pass http://app_blue;
# /etc/nginx/idle-upstream.conf
proxy_pass http://app_green;

ステップ 2:スイッチをスクリプトにする

トラフィック切り替えのロジックを、プロキシ用 VM 上の小さなスクリプトにまとめます。

#!/usr/bin/env bash
# /usr/local/bin/switch-live.sh — usage: switch-live.sh blue|green
set -euo pipefail

TARGET="${1:?usage: switch-live.sh blue|green}"
case "$TARGET" in
  blue)  IDLE=green ;;
  green) IDLE=blue  ;;
  *) echo "target must be blue or green" >&2; exit 1 ;;
esac

echo "proxy_pass http://app_${TARGET};" | sudo tee /etc/nginx/live-upstream.conf >/dev/null
echo "proxy_pass http://app_${IDLE};"   | sudo tee /etc/nginx/idle-upstream.conf >/dev/null

# Validate first, then reload gracefully
sudo nginx -t && sudo nginx -s reload
echo "Live: ${TARGET} | Idle: ${IDLE}"
  • nginx -t で、変更を適用する前に設定をチェックします。失敗した場合はリロードが実行されず、古い設定のまま動き続けます。
  • nginx -s reload はグレースフルリロードです。新しいワーカープロセスが新しい設定を読み込み、古いワーカーは処理中のリクエストを最後まで処理します。切り替えの間にリクエストが失われることはありません。
グレースフルリロード:旧ワーカーが残りを処理し、新ワーカーが新しいリクエストを受け取る

ステップ 3:アイドル側の VM にデプロイする

ライブ側の VM がユーザーにサービスを提供している間に、Cloud Build がアイドル側の VM にデプロイします。

steps:
  - name: gcr.io/cloud-builders/docker
    args: ["build", "-t", "$_IMAGE", "."]
  - name: gcr.io/cloud-builders/docker
    args: ["push", "$_IMAGE"]
  - name: gcr.io/google.com/cloudsdktool/cloud-sdk
    entrypoint: bash
    args:
      - -c
      - |
        gcloud compute ssh app-${_TARGET} \
          --zone=asia-southeast1-b \
          --tunnel-through-iap \
          --command="docker pull $_IMAGE && (docker rm -f app || true) && docker run -d --name app --restart=always -p 8080:8080 $_IMAGE"

ここで _TARGET はアイドル側の色です(今回のリリースでは green)。

ステップ 4:Green をテストする

Green を指している内部テスト用の URL にアクセスします。

curl -i http://<proxy-internal-ip>:8081/health

スモークテスト、手動での簡単な確認、あるいは自動テストスイートを実行します。これこそ、メンテナンス画面を使う方法には一度もなかったステップです。ユーザーの目に触れる前に、本物の VM 上で、本物のプロキシの背後で新バージョンを試せるのです。

ステップ 5:トラフィックを切り替える

sudo /usr/local/bin/switch-live.sh green

これ以降、新しいリクエストはすべて app-green に送られ、app-blue がアイドル側になります。そして次回のリリースからは、2つの環境の役割が入れ替わります。

ステップ 6:必要に応じてロールバックする

sudo /usr/local/bin/switch-live.sh blue

Blue は常に稼働しているので、すぐにトラフィックを受け取れます。

新しいリリースフロー

Blue-green deployment で何が解決されるのか

従来の方法の問題点を一つずつ振り返ってみましょう。

従来の問題(メンテナンス画面) Blue-green の場合
Cloud Build が動いている間ずっとユーザーがブロックされる Blue がサービスを続けている裏で、Green をビルド・デプロイできる
リリースが深夜や週末に追いやられる 切り替えは一瞬なので、業務時間内にリリースできる
ロールバック=パイプライン全体の再実行、メンテナンス画面は表示されたまま 数秒でトラフィックを Blue に戻せる
DB の isMaintenance フラグ フラグは不要。トラフィックの行き先はルーターが決める
本物のユーザーが最初のテスターになる 切り替え前に、専用 URL で Green をスモークテストできる
導入前と導入後:ユーザーがブロックされる時間

5.導入時の注意点

5.1 データベースマイグレーション

Blue と Green は通常、同じデータベースを共有しており、一定期間は 両方 のバージョンが同じデータベース上で動きます。カラムを削除したり名前を変更したりするマイグレーションは Blue を壊し、ロールバックの道も断ってしまいます。解決策は expand/contract パターンです。

  1. Expand: v1 と v2 の両方が動くよう、後方互換性を保ったまま新しいカラムやテーブルを追加する。
  2. v2 をリリースし、安定性を確認する。
  3. Contract: 稼働中のどのバージョンも必要としなくなってから、後のリリースで古いカラムを削除する。
expand/contract パターンによるマイグレーション

5.2 バックグラウンドコンシューマー

Nginx が制御するのは HTTP トラフィックだけです。各 VM 上のアプリが Pub/Sub の pull サブスクライバーや cron ジョブ、その他のワーカーも実行している場合、アイドル側の VM もそれらの処理を続けます。場合によってはテストされていないコードで処理し、2台の VM が同じサブスクリプションから pull することになります。ライブ側の VM でのみワーカーを有効にする(たとえば切り替えスクリプトの一部として有効化する)か、両方のバージョンが同じメッセージを安全に処理できるようにしてください。

問題:アイドル側の VM もメッセージを pull している
対処:ライブ側の VM だけがメッセージを pull する

5.3 セッションと状態

ユーザーのセッションがアプリのメモリに保存されている場合、環境を切り替えるとユーザーはログアウトされてしまいます。どちらのバージョンでも任意のユーザーにサービスを提供できるよう、状態は共有の場所(データベース、Redis、Firestore)に保存しましょう。

5.4 長時間接続(persistent connection)

グレースフルリロードは開いている接続を切断しません。WebSocket や gRPC ストリームは、閉じられるまで古い Nginx ワーカー上に残り、Blue と通信し続けます。これらが徐々に減っていくことを考慮し、いつまでも残らないよう worker_shutdown_timeout の設定を検討してください。また、切り替え直後に Blue を停止しないようにしましょう。

5.5 コスト

2つの環境を並行して稼働させるため、コストは2倍になる可能性があります。リリースの合間にアイドル側の VM を停止し、デプロイ時だけ起動することもできますが、その場合は即時ロールバックができなくなります。私の経験では、トラフィックを切り替えた後もしばらくアイドル側の VM を稼働させておき、リリースで最もリスクの高い期間に即時ロールバックできる状態を保つのがおすすめです。新バージョンが安定したら、アイドル側の VM を停止してコストを節約し、次回のデプロイ前に再び起動すればよいでしょう。

5.6 Blue-green が適さないケース

  • データベーススキーマの変更が頻繁で、後方互換性を保つのが難しいシステム。
  • 2つの環境を維持する予算がない場合。
  • メモリ上の状態や長時間接続に大きく依存しているアプリケーション。
  • すべてのユーザーに公開する前に、本物のトラフィックで新バージョンを検証する必要がある場合。この場合は canary の方が適しています。

実際の事例:

  • Netflix は、自社で開発しオープンソース化した継続的デリバリー基盤 Spinnaker で blue-green を使っています。Netflix はこの戦略を「red/black」と呼んでいます。Netflix TechBlog によると、チームは「using strategies like Blue-Green (or Red-Black as we call it at Netflix)」で新しいサーバーグループをデプロイできます。
  • Waze(Google 傘下)は、Spinnaker のパイプラインで blue/green 戦略を使ってリリースをステージング環境に展開しています。

6.類似の手法:Rolling デプロイと Canary デプロイ

メンテナンス画面なしでリリースする方法は、blue-green だけではありません。よく一緒に語られる手法として、rolling deployment と canary deployment があります。

Blue-green、Rolling、Canary の比較

6.1 Rolling デプロイ

Blue-green deployment のようにすべてのトラフィックを一度に切り替えるのではなく、rolling deployment はアプリの v1 インスタンスを v2 インスタンスに少しずつ置き換え、最終的にすべてを v2 にします。

  • メリット: ダウンタイムがなく、インフラコストを2倍にする必要もない。
  • デメリット: ロールアウト中は v1 と v2 が同時にユーザーにサービスを提供するため、API とデータに後方互換性が必要。ロールバックも v2 を v1 に戻す逆方向のロールアウトになり、blue-green のように即時ではない。

事例: Rolling update は Kubernetes の Deployment におけるデフォルトの戦略です。Google Cloud でも、Compute Engine のマネージドインスタンスグループが rolling update を標準でサポートしています。

6.2 Canary デプロイ

Canary では、トラフィックのごく一部(たとえば 1〜5%)だけを新バージョンに流し、指標(エラー率、レイテンシなど)を監視してから、段階的に 100% まで増やします。異常があっても、影響を受けるのは一部のユーザーだけです。

  • メリット: 本物のトラフィックをもとに、最小限のリスクで不具合を検出できる。
  • デメリット: トラフィックを増やしても安全かを判断するための監視システムが必要で、リリースにかかる時間も長くなる。

事例:

  1. Netflix と Google は、自動カナリア分析ツール Kayenta を共同で開発し、オープンソース化しました。Netflix によると、自動カナリア分析は本番デプロイプロセスの「欠かせない一部」です。
  2. Meta(Facebook)は段階的にリリースしています。まず社内の従業員に、次に本番環境の 2% に、そして 100% へと広げていきます。

6.3 プロジェクトに合った手法

どのリリース手法にも、システムの規模やアーキテクチャに応じた長所とトレードオフがあります。

簡単な比較

Blue-green Rolling Canary
ダウンタイム なし なし なし
エンドユーザーに届く前のテスト あり(アイドル環境) なし 一部(少数のユーザー)
ロールバックの速さ 即時 普通 速い(少量のトラフィックを戻すだけ)
複数バージョンの同時稼働 なし あり あり
追加インフラ 2つ目の環境 少ない 少ない
複雑さ 中 低 高(監視が必要)
  • Blue-Green が向いているケース: 即時ロールバックが必要で、公開前に新バージョン全体を本番環境でテスト/スモークテストしたい場合。また、アプリが長時間接続やメモリ上の状態をあまり持たない場合。
  • Rolling Deployment が向いているケース: アプリがすでにコンテナ/Kubernetes やクラウドのインスタンスグループ上で動いており、インフラコストを抑えたく、ロールバックに少し時間がかかっても許容できる場合。
  • Canary Deployment が向いているケース: ユーザー数が非常に多く、少数のユーザーの実トラフィックでテストしてリスクを最小限に抑える必要があり、指標を自動で評価できる十分な監視/アラートの仕組みがすでにある場合。

7.まとめ

リリーススケジュールとメンテナンス画面を使った従来のリリース方法が悪いわけではありません。シンプルで、きちんと機能します。しかしその代償は、製品にお金を払っているユーザー自身の体験です。

Blue-green deployment はそれを改善できます。新バージョンは Green に並行してデプロイされ、本番環境で検証され、コマンド1つでトラフィックが切り替わります。問題があれば、もう1つのコマンドでトラフィックは Blue に戻り、ユーザーはリリースがあったことにさえ気づきません。

参考資料

1,000社以上の事業成長を支えた圧倒的な『スピードと柔軟性』で、御社のアイデアを最短で形にします。まずは無料で壁打ちしませんか?

ブログに戻る
ぼくはデューパー、なんでもきいてね!