カナリアデプロイとは?小さく試して安全にリリースする方法

要約

カナリアデプロイとは、新バージョンを本番の一部のトラフィック(1%から5%程度)だけに先に流し、残りのユーザーは旧バージョンのまま使うリリース方法です。エラー率とレイテンシを安定版と比べ、問題がなければ割合を増やし、問題があればカナリアへの重みを0に戻します。被害を小さくできますが、バージョンごとの指標とある程度のトラフィック量が必要です。

夜の開発者のデスク。トラフィックのグラフが並ぶダッシュボードと、黄色いカナリアの小さな置物

カナリアデプロイとは、新しいバージョンのアプリに、まず本番の実トラフィックのごく一部(1%から5%程度)だけを流すリリース方法です。残りのユーザーは古いバージョンを使い続けます。その小さな枠でエラー率とレイテンシを見て、問題がなければ割合を広げ、悪ければトラフィックを古いバージョンへ戻します。やることはこれだけです。以下では、それを実際に機能させるための要点を説明します。

作業台に置かれた古いランプと空の鳥かご。カナリアという名前の由来を表す写真

カナリアという名前は、どこから来ているのか?

炭鉱夫は、人間より先に有毒ガスに反応するカナリアを坑道に連れて行きました。カナリアがさえずりをやめたら、外へ逃げる合図です。カナリアデプロイでも同じように、少数のユーザーが新しいコードに最初に触れ、早期警報の役割を果たします。CNCFの用語集では、本番トラフィックを受ける環境と更新済みのコードを動かす環境の2つを用意し、1%のような小さな割合から徐々にトラフィックを移すものとして説明されています。

実際の問題はここにあります。カナリアのユーザーは本物のユーザーです。ステージング環境では再現できません。本番には、特殊なブラウザ、古いキャッシュ、40 MBのインポートファイルを送ってくる一人の顧客がいます。カナリアは、全員がバグに出会う前に、そうした本番の状況と先に出会うための方法です。

カナリアデプロイの仕組みと、トラフィック分割の設定例

仕組みの部品は4つあり、どれも特別なものではありません。

・2つのバージョンを同時に動かす。 古い安定版と新しいカナリア版です。両方が同時にリクエストを処理できる必要があります。 ・トラフィック分割装置。 ロードバランサー、Ingressコントローラー、サービスメッシュ、CDNのルールなど、指定した割合をカナリアへ送る仕組みです。 ・信頼できる指標。 エラー率、レイテンシ、そして決済成功率や会員登録数のようなビジネス指標を最低1つ。 ・進める・止めるの判断ルール。 デプロイの前に決めておくもので、深夜にグラフを眺めながら思いつくものではありません。

典型的な段階は、1%、5%、25%、50%、100%で、各段階で一時停止します。Google Cloud Deployも同じ形を説明しており、割合のフェーズを定義するとツールがトラフィックを配分します。例では25%、50%、75%のあとに安定版へ切り替えます。Cloud Deployのカナリアのドキュメントでは、各フェーズで検証ジョブと分析ステップを実行し、次へ進むかを判断できるとも書かれています。

完璧な構成ではありませんが、実現可能です。Kubernetesは必須ではありません。nginxの重み付きルーティング、ユーザーにCookieを付けて振り分けるCloudflareのワーカー、同じプロキシの後ろに置いた2台のFly.ioマシンでも十分に成立します。

最小限の例として、nginxのupstreamに重みを付けてみます。10回のリクエストのうち9回が安定版へ、1回がカナリアへ向かいます。

upstream app {
    server 10.0.0.10:3000 weight=9;   # 安定版
    server 10.0.0.11:3000 weight=1;   # カナリア版
}

この設定には欠点があります。分割はリクエスト単位なので、同じユーザーが連続したクリックでバージョンを行き来することがあります。新しいバージョンがAPIのレスポンス形式やDBのカラムを変えている場合、そのユーザーは困ります。CookieやユーザーIDによるスティッキーなルーティングで解決でき、基本の分割が動いたら最初に追加すべきものです。

2本の指標線が分かれて表示されるノートPCの画面。安定版とカナリア版を比較している様子

カナリアの間、何を監視すればいいか

カナリアを安定版と比較するときは、先週ではなく同じ時刻の値を比べます。先週はトラフィックが違っていたからです。問いは常にこれです。残りの95%を処理している安定版と比べて、カナリアは悪化していないか。

・エラー率。 HTTP 5xxと未処理の例外を、バージョンのラベルごとに分けて見ます。 ・レイテンシ。 平均ではなくp95とp99を見ます。平均では、回帰が潜んでいる遅い裾野が隠れてしまいます。 ・リソースの飽和。 CPU、メモリ、DBコネクションです。メモリリークは右肩上がりの傾きとして現れるので、時間をかけて観察します。 ・ビジネス指標を1つ。 カナリアで決済成功率だけが落ち、エラーは横ばいなら、静かなバグを出荷しています。

小規模なチームでよく見かける失敗は、タイマーで段階を進めてしまうことです。1%で5分、そのあと25%。グラフが何を示していても進めてしまいます。トラフィックが少ないと、1%の5分間はわずか数リクエストにしかなりません。それでは何も分かりません。分単位ではなく、最低リクエスト数を基準にしてください。

トラフィックが十分になったら、監視を助けるツールも検討する価値があります。

カナリアとブルーグリーン、どちらを選ぶべきか?

ブルーグリーンは、フル環境を2つ用意して、全トラフィックを一度に切り替えます。切り戻しは元に戻すだけで、速くてシンプルです。ただし切り替えの瞬間は全ユーザーが新バージョンに載り、切り替え中はインフラが2倍必要になります。

カナリアは一部だけを公開するので、影響範囲が小さくなります。代償は複雑さです。トラフィックの重み付け、バージョンごとの指標、判断ルールが必要になります。切り戻しはカナリアの重みを0にすることで行います。速い方法ですが、接続のドレインが必要な場合は厳密には即時ではありません。

カナリアを選ぶ理由は3つ、選ばない理由は1つと考えると整理しやすくなります。本番のバグのコストが高いとき、小さな割合でも意味のある量のトラフィックがあるとき、バージョンごとに指標を比べられるとき、の3つです。逆に、1日に数十人しか来ないサービスでは、カナリアの割合にはほぼ何も入らず、結局は推測になります。その規模なら、ブルーグリーン、あるいは素早く戻せるスクリプト付きの通常デプロイが正直な答えです。

ローリングアップデートは中間にあります。インスタンスを1台ずつ入れ替えるので穏やかですが、制御された実験ではありません。どのユーザーに新バージョンを見せるかを選べず、コホートを比べることもできないからです。

フィーチャーフラグはどこに入るのか?

カナリアデプロイはコードのバージョン間でトラフィックを分けます。フィーチャーフラグは1つのバージョンの中で振る舞いを分けます。両者はよく混同され、その混乱は高くつきます。

新しいコードを全員にデプロイし、フラグをオフの状態で隠しておきます。そのあとフラグを1%のユーザー、次に10%、最後に全員へ有効にします。これは機能レベルのカナリアリリースと呼ばれることがよくあります。利点は、切り戻しがフラグを切り替えるだけで済み、再デプロイが不要なことです。欠点は、使われていないコード経路が全員の本番に残ること、そして削除されないフラグが技術的負債になることです。

個人開発者にとっては、フラグのほうが安上がりな第一歩であることが多いです。設定テーブルのboolean値と、ユーザーIDのハッシュがあれば、価値の8割は得られます。それ以上が必要になったら、ターゲティング、監査ログ、段階的な公開を扱うマネージドサービスが役立ちます。

少しだけ回されたダイヤル。新しいバージョンへ少量のトラフィックを移すイメージ

実プロジェクトでカナリアが失敗する場面

ここでは、実際に効いてくる失敗を挙げます。試してみて分かったことですが、どれも最初は見落とされがちです。

DBマイグレーション。 2つのバージョンが同じDBに同時にアクセスします。カナリアがカラムを削除または改名すると、安定版が壊れます。対策はexpand and contractパターンです。まず新しいカラムを追加し、両方の形を扱えるコードを出し、古いカラムは後のリリースで削除します。これを省くと、カナリアが安定版を道連れにします。

ステートフルなセッション。 セッションが1台のインスタンスのメモリにあると、バージョンの間を行き来したユーザーはログアウトされてしまいます。カナリアを試す前に、セッション状態を共有ストアへ移してください。

キャッシュの汚染。 新バージョンが別の形式でキャッシュを書くと、古いバージョンが読むデータが壊れることがあります。キャッシュキーにバージョンを含めてください。

バックグラウンドジョブ。 カナリアはキューのメッセージも消費します。バグのあるワーカーは全速力でジョブを処理してしまいます。トラフィックの割合はキューの消費を制限しないからです。カナリアには専用のキューか、コンシューマーの重みを与えてください。

誰も見ていないアラート。 カナリアが、誰も開かないダッシュボードで静かに失敗することがあります。しきい値を超えたら自動で止める仕組みは、人間が見張るよりも確実です。台北やバンコクの深夜2時に、起きているのがデプロイした本人だけという状況では特にそうです。

個人開発でカナリアを始めるには

思っているより小さく始めてください。あなたは一人の開発者であって、プラットフォームチームではありません。

  1. すべての指標にバージョンのラベルを付ける。 gitのSHAをヘッダーとログに入れます。これがないと、カナリアと安定版を比べることすらできません。

  2. 停止ルールを1つ決める。 たとえば、カナリアのエラー率が安定版の2倍の状態が5分続いたら切り戻す、というルールです。1つだけ、書き留めておきます。

  3. 重み付きルーティングを追加する。 nginxの重み、Cloudflareのルール、またはホスティングのトラフィック分割機能です。主要なマネージドプラットフォームの多くは、割合指定のルーティングをすでに提供しています。

  4. まずは地味なものを出す。 価格ページの全面書き直しではなく、文言の修正から始めます。失うものが何もないときに、段階的な公開と切り戻しを練習します。

  5. 切り戻しの時間を測る。 実際に一度やってみてください。10分もまごつくなら、直すべきは段階的な公開の仕組みではなく、切り戻しの手順です。

まだ誰が払ってくれるのかを探している段階なら、カナリアはおそらく時期尚早です。普通にリリースし、切り戻しスクリプトを用意しておき、停止がお金や信頼を失わせるようになってからカナリアに戻ってきてください。すでにその段階にあるなら、重み付きルーティングと停止ルールを1つ用意するのは週末の作業で、悪いリリースが100%ではなく1%の段階で止まるたびに元が取れます。

何でもカナリアで出すべきか?

いいえ。設定変更、文言の修正、フッターの誤字の修正には、段階的な公開は必要ありません。カナリアを使うのは、間違えるとお金や信頼を失う変更です。決済、認証、データモデル、ホットパスに触れるものなどです。すべてのコミットに1時間かけて段階的に公開するパイプラインは、誰かが急いでいる最初の日に迂回されます。迂回されるプロセスは、プロセスがないより悪いのです。

では、次のリリースで最もリスクの高い変更は何でしょうか。それを1%のユーザーに先に届けるには、何が必要でしょうか。

よくある質問

カナリアデプロイとは簡単に言うと何ですか?
新バージョンを本番の少数のユーザーだけに先に使わせ、残りは古いバージョンのまま動かすリリース方法です。指標が健全なら割合を増やし、悪化したら古いバージョンへ戻します。
カナリアの開始割合は何%にすべきですか?
1%から5%程度から始めるチームが多いです。適切な値はトラフィック量によります。割合の枠でエラー率やレイテンシを比べられるだけのリクエストが必要で、トラフィックが少ない場合は固定の分数ではなく最低リクエスト数を待ちましょう。
カナリアデプロイとブルーグリーンデプロイの違いは何ですか?
ブルーグリーンは2つのフル環境を用意し、全トラフィックを一度に切り替えます。カナリアはトラフィックを徐々に移すので、問題のあるリリースに触れるユーザーが少なくなります。ただし、トラフィックの重み付けとバージョンごとの監視が必要です。
カナリアデプロイはフィーチャーフラグと同じですか?
同じではありません。カナリアは2つのデプロイ済みバージョン間でトラフィックを分けます。フィーチャーフラグは1つのバージョン内で機能のオンとオフを切り替えます。フラグの裏にデプロイし、一部のユーザーにだけ有効にする形で組み合わせると効果的です。
カナリアデプロイにはKubernetesが必要ですか?
いいえ。nginxの重み付きupstream、CDNやエッジのルール、マネージドホストのトラフィック分割でも実現できます。Kubernetesのツールを使うと段階的な拡大や自動分析が楽になりますが、必須ではありません。
カナリアリリースでデータベースのマイグレーションはどう扱えばいいですか?
両方のバージョンが1つのDBを共有するため、expand and contractパターンを使います。まず新しいカラムを追加し、両方の形を扱えるコードを出し、古いカラムは後のリリースで削除します。