新バージョンを安全にデプロイする方法の選び方
新機能のビルドが完了した。コードはすべてのチェックを通過し、テストはグリーン、アーティファクトも準備完了。さて、ここで本当の課題が待っている。ユーザーを怒らせずに、どうやってこの新バージョンをサーバーに投入するか?
最も単純な答えは、サーバーを停止し、ファイルを置き換えて、再起動することだ。アプリケーションの利用者がごく少数で、ダウンタイムが発生することを事前に認識しているなら、これで問題ない。しかし、実際のユーザーがいるサービスでは、この方法はすぐに破綻する。ユーザーはエラーページを目にし、リクエストは失敗し、信頼は失われる。
核心的な問題は、旧バージョンがトラフィックを処理している最中に、新コードを本番環境に投入しなければならない点にある。すべてを一度に切り替えることはできない。サービスを中断させずに、あるバージョンから別のバージョンへ移行するための戦略が必要だ。
この問題を解決する一般的な戦略として、ローリングアップデート、ブルー/グリーンデプロイ、カナリアデプロイの3つがある。それぞれ異なる方法で同じ問題を解決し、異なる状況に適している。
ローリングアップデート:サーバーを1台ずつ置き換える
ローリングアップデートは最も広く使われている戦略だ。考え方は単純で、すべてのサーバーを一度に置き換えるのではなく、1台ずつ、または少数のバッチで置き換えていく。
10台のサーバーでアプリケーションを運用しているとしよう。ローリングアップデートでは、2台のサーバーをオフラインにし、それらに新バージョンをインストールして、再びオンラインに戻す。この2台が新バージョンで稼働し始めたら、次の2台に移行する。このプロセスを10台すべてが新バージョンになるまで繰り返す。
最大の利点は、追加のサーバーが不要なことだ。既存のインフラをそのまま再利用する。重複した環境をプロビジョニングする必要がないため、コストは最小限で済む。
以下は、Kubernetes Deploymentマニフェストでのローリングアップデートの設定例だ。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
spec:
containers:
- name: app
image: my-app:2.0.0
この設定では、一度に作成される新しいPodは1つだけ(maxSurge: 1)で、新しいPodが準備完了になるまで古いPodは削除されない(maxUnavailable: 0)。アップデートはPod単位で進行し、サービスは常に利用可能な状態が保たれる。
しかし、注意点もある。アップデート中は、アプリケーションの2つのバージョンが同時に稼働することになる。一部のユーザーは旧バージョンに、別のユーザーは新バージョンにアクセスする。もしバージョン間でAPIの契約が変更されていると、リクエストが失敗する可能性がある。ユーザーが送信したリクエストが旧サーバーで処理されたものの、レスポンス形式が変わっている場合や、新しいサーバーが期待するフィールドを旧クライアントが送信しない場合などだ。
ローリングアップデートは、変更が後方互換性がある場合に適している。フィールドを削除するのではなく追加する場合、APIの形状を変えるのではなく拡張する場合、データベーススキーマの変更が追加的で破壊的でない場合などだ。小さく安全な変更には、ローリングアップデートが効率的でシンプルな選択肢となる。
ブルー/グリーンデプロイ:2つの完全な環境
ブルー/グリーンデプロイは異なるアプローチを取る。2つの同一の環境を維持する。それぞれをブルー、グリーンと呼ぶ。常にどちらか一方だけがライブトラフィックを処理し、もう一方はアイドル状態か、以前のバージョンを実行している。
仕組みはこうだ。現在ユーザーはブルー環境にアクセスしている。新しいバージョンをグリーン環境にデプロイする。グリーン環境が完全に準備でき、動作確認が取れたら、トラフィックを切り替える。すべてのユーザーがグリーン環境にアクセスするようになる。もし問題が発生した場合は、ブルーに戻せばよい。ロールバックは瞬時に行える。
この戦略は、バージョンが混在することが決してないため、より安全だ。ユーザーはある完全なバージョンから別の完全なバージョンへと移行する。半分のサーバーが旧コード、残り半分が新コードを実行する期間は存在しない。移行はクリーンだ。
トレードオフはコストだ。2倍のリソースが必要になる。同じキャパシティを持つ2つの完全な環境が同時に稼働する。小規模なアプリケーションでは許容できるかもしれないが、大規模なものではコストが大きくなる可能性がある。
ブルー/グリーンは、ダウンタイムが許容されず、ロールバックが迅速でなければならない重要なアプリケーションに最適だ。大規模な変更、データベースマイグレーション、システムの多くの部分に影響を与える何かをデプロイする場合、ブルー/グリーンはセーフティネットを提供する。
カナリアデプロイ:まずは小規模なグループでテストする
カナリアデプロイは、ローリングアップデートよりも慎重なアプローチだ。サーバーをバッチで置き換える代わりに、ごく小さな割合から始める。おそらくサーバーの5パーセントに新バージョンを適用し、残りは旧バージョンのままにする。
その小規模なグループをしばらく監視する。エラー率が正常で、応答時間に問題がなく、誰も問題を報告しなければ、割合を増やす。20パーセント、50パーセント、そして100パーセントへと段階的に進める。
考え方は、爆発範囲を限定することだ。新バージョンにバグがあった場合、影響を受けるのはごく一部のユーザーだけだ。問題が全面停止に発展する前に発見できる。
カナリアデプロイには、優れた監視体制が必要だ。正常な状態がどのようなものかを把握しておく必要がある。エラー率、レイテンシ、スループットをリアルタイムで表示するダッシュボードが必要だ。その可視性がなければ、手探りで進むことになる。カナリアが健全かどうかを判断できなくなる。
この戦略は、本番環境で変更を検証してから全面的に適用したい場合に適している。特に、パフォーマンスの変更、新しいアルゴリズム、実際のトラフィック下での振る舞いが不確かなものに有効だ。
適切な戦略の選び方
唯一の最善な戦略というものは存在しない。それぞれ異なる状況に適している。
以下のフローチャートは、状況に応じた戦略の選択を支援する。
ローリングアップデートは、ほとんどのチームのデフォルトの選択肢だ。リソース効率が良く、日常的な変更に適している。変更が後方互換性があり、即時ロールバックが必要ない場合に使用する。
ブルー/グリーンは最も安全な選択肢だ。重要なデプロイ、メジャーバージョン変更、または即座にロールバックする必要がある場合に使用する。追加のインフラコストを支払う覚悟が必要だ。
カナリアは最も慎重な選択肢だ。変更の影響を観察してから全面的に適用したい場合に使用する。優れた監視と、割合を増やすタイミングを判断するプロセスが必要だ。
多くのチームはローリングアップデートから始め、アプリケーションがより重要になり、ユーザーベースが成長するにつれて、ブルー/グリーンやカナリアに移行する。今日選んだ戦略が、将来も使い続けなければならないものではない。
選択前の実用的チェックリスト
- 変更は後方互換性があるか? はいの場合、ローリングアップデートで問題ない。
- 即時ロールバックが必要か? はいの場合、ブルー/グリーンの方が安全だ。
- リアルタイム監視はあるか? ない場合、カナリアはリスクが高い。
- 2倍のインフラを用意できるか? できない場合、ブルー/グリーンは避ける。
- 実際のトラフィック下でテストしたいか? はいの場合、カナリアがそれを可能にする。
最も重要なこと
戦略自体は難しい部分ではない。難しいのは、移行中のアプリケーションの振る舞いを理解することだ。2つのバージョンが同時に稼働する状況を処理できるか? APIの契約が混在する短期間を許容できるか? ユーザーが報告する前に問題を検出できるだけの可観測性があるか?
自身のリスク許容度とインフラ予算に合った戦略を選び、テストしよう。ステージング環境だけでなく、実際のトラフィックパターンを持つ本番環境でもテストする。ブルー/グリーンスイッチやカナリアロールアウトを初めて行うときは、トラフィックが少ない時間帯に実施する。メトリクスを監視し、正常な状態がどのようなものかを学ぶ。
デプロイとは、単にビットをある場所から別の場所に移動することではない。プロダクトを改善しながら、ユーザーを満足させ続けることだ。適切な戦略がそれを可能にし、間違った戦略はそれを苦痛にする。