目次
「リリースは深夜にメンテナンス画面を出して行う」という運用を続けていると、リリース頻度は下がり、担当者の負担は増え、深夜作業ゆえのミスも起きやすくなります。一方で「ダウンタイムなしでデプロイしたい」と調べると、ブルーグリーン・カナリア・ローリングと手法が並び、どれを選べばよいのか、自分たちの規模で現実的なのかがわかりにくいのも事実です。
この記事では、3つの手法をそれぞれ「何が起きているか」「コストはどれだけ増えるか」「失敗したときにどう戻すか」の観点で比較し、多くのチームがつまずくDBマイグレーションとの両立、ロードバランサー切替とヘルスチェックの関係、そして少人数チームでの現実的な選択を解説します。CI/CDパイプライン自体の作り方はCI/CDパイプラインの基礎で扱っていますので、本記事は「パイプラインの最後のステップである本番への反映」をどう設計するかに絞ります。
ゼロダウンタイムデプロイの3つの手法
なぜ単純な「止めて入れ替える」でダウンタイムが出るのか
1台のサーバーで「旧プロセスを止める→新コードを配置→新プロセスを起動」とすると、停止から起動完了までの数秒〜数十秒はリクエストを受けられません。起動に時間のかかるJavaやRailsのアプリではさらに長くなります。ゼロダウンタイムの手法はすべて、「旧と新を同時に存在させ、トラフィックの向き先を切り替える」ことでこの空白をなくします。つまり前提として、ロードバランサーやリバースプロキシのように向き先を切り替えられる層が必要です。
ブルーグリーン・カナリア・ローリングの比較
| ブルーグリーン | カナリア | ローリング | |
|---|---|---|---|
| 仕組み | 本番と同じ環境をもう1組(グリーン)用意し、検証後にLBの向き先を一括で切り替える | 新バージョンを一部(1〜10%程度)のトラフィックにだけ流し、指標を見ながら比率を上げる | 複数台のうち1台ずつ(または一定割合ずつ)新バージョンに入れ替える |
| 追加コスト | 切替前後の一時期、2倍のリソース | 少量(新バージョン用の数台) | ほぼなし(余剰1台分程度) |
| ロールバック | 向き先を戻すだけ。数秒で完了 | 比率を0%に戻す。影響範囲が最小 | 再度ローリングで戻す。時間がかかる |
| 新旧混在期間 | なし(切替は一瞬) | あり(意図的に長く) | あり(入れ替え中ずっと) |
| 必要なもの | 環境を丸ごと2組作れる自動化 | 重み付きルーティングと監視指標 | ヘルスチェックと複数台構成 |
| 向いている場面 | 「戻せること」を最優先したい | ユーザー影響を最小にしながら本番で検証したい | コストを増やさず標準的に運用したい |
ローリングはKubernetesのDeploymentやAWS ECS、Cloud Runなど多くのプラットフォームのデフォルトで、何も考えなくてもそれなりに動きます。ブルーグリーンとカナリアは「戻しやすさ」と「安全な検証」という明確なメリットがある代わりに、前者はコスト、後者は監視指標と重み付きルーティングの仕組みを要求します。
DBマイグレーションとの両立|後方互換が全ての前提
「新旧が同時に動く瞬間」にスキーマは1つしかない
3手法のどれを選んでも、旧バージョンと新バージョンのコードが同じデータベースを同時に使う時間帯が必ず存在します(ブルーグリーンでも、ロールバックに備える限りDBは共有です)。したがって、スキーマ変更は新旧どちらのコードでも動く形でなければなりません。「カラム名を変更」「NOT NULLカラムをデフォルトなしで追加」「カラムを削除」といった変更を1回のリリースで行うと、旧バージョンのSQLがエラーになります。
Expand / Contractパターン
互換性を保つには、変更を「広げる(Expand)」段階と「縮める(Contract)」段階に分け、複数回のリリースに分割します。たとえば users.name を full_name に改名したい場合は次のようになります。
- リリース1(Expand):
full_nameカラムをNULL許容で追加。コードはnameとfull_nameの両方に書き込み、読み取りはnameのまま。 - バックフィル: 既存行の
nameをfull_nameにコピーするバッチを、負荷を見ながら実行。 - リリース2: 読み取りを
full_nameに切り替え。書き込みは引き続き両方。 - リリース3(Contract):
nameへの書き込みをやめ、十分な期間が過ぎてからnameカラムを削除。
手間は増えますが、どの段階でも直前のバージョンにロールバックできます。「マイグレーションを先に流してからコードをデプロイする」のか「後に流す」のかも重要で、Expand系(追加)はコードより先、Contract系(削除)はコードより後、と覚えておくと判断を誤りません。マイグレーションの運用全般はデータベースマイグレーション戦略で詳しく解説しています。
ロードバランサー切替とヘルスチェック
切替は「新が健全になってから、旧を静かに外す」
どの手法でも、向き先の切替は次の順序で行われる必要があります。
- 新バージョンを起動し、ロードバランサーのターゲットに登録する。
- ヘルスチェックが連続して成功するまで待つ(起動直後は失敗するので、成功回数のしきい値が意味を持つ)。
- 旧バージョンをターゲットから登録解除する。このとき既存の接続は完了まで待つ(AWS ALBのDeregistration delay、Nginxの
down指定など)。 - 旧バージョンのプロセスにSIGTERMを送り、処理中リクエストを終えてから停止させる。
4はアプリ側の実装が必要で、グレースフルシャットダウンの記事で詳しく説明しています。ヘルスチェックのエンドポイントが「プロセスが生きている」だけを返していると、DB接続が確立する前に振り分けが始まりエラーになるため、依存先への接続完了を含めたReadiness判定を返すようにしてください。
Nginx 1台でできる最小ブルーグリーン
サーバー1台でも、アプリを2つのポートで起動し、Nginxのupstreamを書き換えて reload すればブルーグリーンが成立します。Nginxの reload は既存接続を切らずに新設定のワーカーへ移行するため、この切替自体がゼロダウンタイムです。
# /etc/nginx/conf.d/upstream.conf(デプロイスクリプトが書き換える)
upstream app {
server 127.0.0.1:3001; # blue(現行)
# server 127.0.0.1:3002; # green(次期)
}
# deploy.sh の切替部分
NEW_PORT=3002
# 1. 新バージョンを別ポートで起動し、ヘルスチェックが通るまで待つ
for i in $(seq 1 30); do
curl -fs "http://127.0.0.1:${NEW_PORT}/healthz" && break
sleep 1
done
# 2. upstreamを書き換えて構文チェック後にreload
sed -i "s/127.0.0.1:[0-9]*;/127.0.0.1:${NEW_PORT};/" /etc/nginx/conf.d/upstream.conf
nginx -t && nginx -s reload
# 3. 旧プロセスにSIGTERMを送り、ドレインを待つ
systemctl stop app@3001
Kubernetesのローリングアップデート設定
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 一時的に何台増やしてよいか(デフォルト25%)
maxUnavailable: 0 # 稼働台数を減らさない(デフォルト25%)
template:
spec:
containers:
- name: app
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
maxUnavailable: 0 にすると、必ず新Podが健全になってから旧Podを落とすので、台数不足による性能低下を避けられます。ロールバックは kubectl rollout undo deployment/app で前のReplicaSetに戻せます。Kubernetesの基本はKubernetes入門を参照してください。
トラブル事例:ローリング中に半分のリクエストが500になった
症状
3台構成でローリングアップデートを行った直後、ユーザー登録機能で断続的に500エラーが発生しました。全台の入れ替えが終わると収まりましたが、約3分間、リクエストの3分の1〜3分の2が失敗していました。
原因
新バージョンのマイグレーションで users テーブルに NOT NULL かつデフォルト値なしの plan_id カラムを追加していました。マイグレーションはデプロイ開始時に一括で実行されたため、まだ旧バージョンで動いている2台の INSERT INTO users (email, name) が「plan_idにNULLは許可されない」というエラーで失敗したのです。ローリング中に新旧が混在することを想定しないスキーマ変更が原因でした。
対処
- 緊急対応として
ALTER TABLE users ALTER COLUMN plan_id SET DEFAULT 1を実行し、旧バージョンのINSERTを通るようにした。 - 再発防止として、マイグレーションのレビュー観点に「直前のバージョンのコードでもエラーにならないか」を追加。
- NOT NULL追加は「NULL許容で追加→バックフィル→NOT NULL制約を付ける」の3段階に分ける運用に変更。
ローリングやカナリアは「混在期間が長い」からこそ、後方互換の徹底が不可欠です。ブルーグリーンなら混在は一瞬ですが、ロールバックした瞬間に同じ問題が起きるので、結局は同じルールに従うことになります。
小規模チームでの現実的な選択
構成別の推奨
手法の優劣より、「自分たちの構成で追加の仕組みなしにできるもの」から始めるのが現実的です。
- VPS 1台+Nginx: 上記のポート切替式ブルーグリーン。デプロイスクリプトに30行足すだけで実現でき、ロールバックはポートを戻してreloadするだけです。
- AWS ECS / Cloud Run / App Service: プラットフォームのローリング更新(ECSは最小健全率、Cloud Runはリビジョンのトラフィック分割)をそのまま使う。Cloud Runはリビジョン単位でトラフィック比率を指定できるので、そのままカナリアにもなります。
- Kubernetes: Deploymentのローリングを基本にし、カナリアが必要になった段階でArgo RolloutsやIngressの重み付けを検討する。
カナリアは「指標を見て判断する人か自動化」が必要なので、監視ダッシュボードとエラー率アラートが整っていないチームが導入すると、単に「一部ユーザーだけ壊れている時間が長い」状態になります。まずはローリングかブルーグリーンで「戻せる」状態を作り、監視が育ってからカナリアへ進むのが順当です。GitHub Actionsからのデプロイ自動化はGitHub Actions自動デプロイ入門を参照してください。
まとめ
ゼロダウンタイムデプロイは、手法の選択そのものより「新旧が同時に存在する前提で設計されているか」で成否が決まります。
- ブルーグリーンは戻しやすさ、カナリアは影響の最小化、ローリングは低コスト。まずは構成上すぐできるものから。
- スキーマ変更はExpand/Contractで分割し、直前のバージョンでも動く状態を常に保つ。
- 切替は「新が健全→旧を外す→旧をドレイン」の順。ヘルスチェックは依存先の準備完了まで含める。
- カナリアは監視が育ってから。
次のリリースで「このマイグレーションは旧コードでも動くか」を1つ確認するだけでも、事故の確率は大きく下がります。デプロイ基盤の整備やリリース運用の改善は、Harmonic Societyのシステム開発・インフラ支援でもお手伝いしています。
Harmonic Society
この記事の内容、自社の業務でも活かせそうですか?
ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。
関連記事
Related / 9 articles
Notes & Insights
- プログラミング
DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用
DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。
- プログラミング
WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド
WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。
- プログラミング
セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説
CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。
- プログラミング
クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計
クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。
- プログラミング
秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除
APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。
- プログラミング
開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御
開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。
- プログラミング
ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方
NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。
- プログラミング
ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方
ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。
- プログラミング
localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決
localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。
Harmonic Society
「読んで終わり」にせず、自社の業務で試してみませんか?
AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。
無料・30分・オンラインOK|1営業日以内に返信します