TT Lab
はじめる
学ぶ 学習パス コース

Istioサービスメッシュ

カナリア — 重みより大事なのは中止条件だ

TT Labで続きを見る

一言でいうと

カナリアは「新バージョンにトラフィックを少し流す」ことではありません。どの指標がどうなったら止めて元に戻すかを先に決めてから、少しだけ流すことです。

なぜ必要なのか

ローリングアップデートだけでも、Podは少しずつ入れ替わります。ただし、ローリングアップデートが見ているのはPodの健全性だけです。プロセスが生きていてreadinessプローブが通れば、そのまま進み続けます。新バージョンが200をきちんと返しながら応答の中身だけが間違っていたり、p99のレイテンシが3倍になったり、特定のリクエストでだけ500を返したりしても、ローリングアップデートは何も気づかずに最後まで進みます。

メッシュのトラフィック分割は、ここにレバーを1つ加えます。Podの数とトラフィックの比率を切り離すのです。v2のPodをあらかじめすべて起動しておいても、トラフィックは0%しか流さないようにできますし、問題が見えたらPodには触れず、重みだけを0に戻せます。元に戻すのにデプロイが要らないことが、ローリングアップデートとの決定的な違いです。

どう動くのか

重みによる分割は、EnvoyのWeightedClusterに変換されます。リクエストごとに乱数を引き、重みの区間に従ってクラスターを選びます。そのため、正確な90:10ではなく、確率的に90:10へ近づきます。リクエストが100件しかなければ、12件がv2に行くこともあります。この性質のため、トラフィックが少ないサービスでは、カナリアの段階ごとに十分なサンプルがたまるまで待たないと、判断に意味がありません。

重みの合計は必ず100でなければなりません。合計が違うと検証の段階で拒否されます。このルールのおかげで、「v1を90に下げたのにv2を上げ忘れる」という事故が構造的に防がれます。

分割方式は、次の3つを使い分ける必要があります。

方式 対象 使うとき
重み ランダムな一部のユーザー 一般的な段階的デプロイ
ヘッダーマッチング 指定した人だけ 社内テスターへの先行公開、QA
ミラーリング 誰にも流さない(コピーだけ) ユーザーへの影響0で、負荷とリグレッションを検証

ミラーリング(シャドーイング)は、特に誤解が多い機能です。コピーされたリクエストの応答は完全に捨てられます。ミラー先が停止していても、ユーザーへの応答には影響しません。その代わり、ミラーされたリクエストも、DBへの書き込みや外部APIの呼び出しのような副作用は本当に起こします。書き込み経路をミラーリングするには、新バージョンがシャドーモードで動くように、アプリケーション側での準備が必要です。ミラーされたリクエストのHostヘッダーには-shadowというサフィックスが付くので、ログで区別できますし、区別すべきです。

重みによる分割には、もう1つ落とし穴があります。リクエストごとに乱数を引くので、同じユーザーが画面を切り替えるたびにv1とv2を行き来します。セッションの状態やUIがバージョンごとに違えば、これはそのままバグに見えます。解決策は、DestinationRuleの一貫ハッシュによるロードバランシングです。特定のヘッダー(例: x-user-id)をハッシュして、同じキーを常に同じエンドポイントへ送ります。ハッシュリング構造なので、エンドポイントが増減しても再マッピングは最小限で済みます。

最後に、カナリア計画は文書ではなく、実行できる定義でなければなりません。段階ごとに、(1)重み、(2)次の段階へ進む判断基準、(3)観察時間が必要で、全体にはロールバックの方法が必要です。FlaggerやArgo Rolloutsは、この定義をCRDで受け取って自動で進めます。自動化の本当の価値は速度ではありません。悪い指標を見てもロールバックをためらってしまう、人間的なバイアスをなくすことです。

現場での姿

第一に、ラベルの不一致で始めることすらできないカナリアです。subsetが指すラベルを持つPodが1つもなければ、そのsubsetへ流れたトラフィックはすべて503(UH)になります。重みを10に上げた瞬間に、リクエストの10%が失敗します。デプロイパイプラインに、subsetのラベルとPodのラベルが一致しているかを検証するステップを入れるチームが多いのは、このためです。

第二に、ゲートのないカナリアです。10 → 30 → 50 → 100と、人が目で見ながら上げる方式は、出発点としては悪くありません。しかし夜間のデプロイが難しく、判断も揺らぎます。エラー率とレイテンシのしきい値を段階の定義に書き込んでおけば、その判断は人の手を離れます。

第三に、戻る先がないカナリアです。v1をすでに消してしまっていれば、重みを0に戻しても行き先がありません。カナリアが終わって100%になるまで、以前のバージョンは生かしておく必要があり、その後始末の時点も計画に入れておかなければなりません。

次のラボですること

100/0から始めて90/10に移し、合計が100ではないマニフェストがどのように拒否されるかを確認します。ヘッダーで社内テスターだけを新バージョンに送るルート、応答を捨てるミラーリング、同じユーザーを1つのバージョンに固定する一貫ハッシュを、順に加えます。最後に、段階・判断基準・ロールバックを盛り込んだカナリア計画のファイルを作り、実際のメッシュを50/50の中間段階まで進めておきます。

注意点が1つあります。このラボの前半のステップは、クラスターではなく保存されたファイルを採点します。vs-baseline.yaml、vs-90-10.yaml、vs-header.yaml、vs-mirror.yamlは、各ステップのスナップショットとして別々に残っている必要があります。1つのファイルを書き換え続けると、前のステップの根拠が消えてしまいます。実務でも、カナリアの段階ごとのマニフェストをそれぞれコミットしておくのが定石です。どの段階で何が変わったのかが、そのままインシデント調査の資料になるからです。