安全なリリースのための3つのレバー:トラフィック、コホート、フィーチャーフラグ
新しいバージョンのアプリケーションが準備できました。ステージングでのテストはパスし、チームも自信を持っています。それでも、本番にデプロイするボタンを押す前には躊躇してしまうでしょう。その躊躇は健全です。すべてのデプロイにはリスクが伴い、そのリスクを減らす最も安全な方法は、変更を一度に全員に送らないことです。
考え方はシンプルです。段階的にリリースする。しかし、「段階的」の意味は、何を制御するかによって異なります。変更をすべてのユーザーに同時に送らないと決めた場合、あなたには3つの独立したレバーがあります。それぞれがリリースの異なる側面を制御します。それぞれをいつ使うか、そしてそれらをどのように組み合わせるかを理解することが、制御されたロールアウトとギャンブルを分けるものです。
トラフィック:新しいバージョンにかかる負荷の量を制御する
段階的にリリースする最も直接的な方法は、新しいバージョンに到達するリクエストの割合を制御することです。更新されたアプリケーションにすべてのトラフィックをルーティングする代わりに、その一部だけを送信します。全リクエストの5%が新しいバージョンに送られ、残りの95%は古いバージョンに送られ続けます。
このアプローチはトラフィック分割と呼ばれます。ロードバランサー、サービスメッシュ、APIゲートウェイなどのインフラストラクチャレベルで設定します。設定はシンプルです。パーセンテージを指定すると、インフラストラクチャがそれに応じてリクエストを分散します。ユーザーIDは関与しません。ターゲティングロジックもありません。単なるネットワークトラフィックの生の割合です。
トラフィック分割は、最初の検証段階に最適です。新しいバージョンが起動時にクラッシュしないか、実際の負荷でエラーをスローしないか、予想以上にメモリを消費しないかを確認したい場合です。サンプルはランダムなので、基本的な健全性に関する迅速なシグナルを得られます。エラー率が急上昇した場合、ほとんどのユーザーに影響が出る前にロールアウトを停止できます。
以下は、IstioのVirtualServiceを使用してトラフィックの5%を新しいバージョンに、95%を古いバージョンに分割する実際的な例です。
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: myapp
spec:
hosts:
- myapp.example.com
http:
- match:
- uri:
prefix: /
route:
- destination:
host: myapp
subset: v2
weight: 5
- destination:
host: myapp
subset: v1
weight: 95
制限は明らかです。誰が新しいバージョンを取得するかを制御できません。同じユーザーがあるリクエストでは新しいバージョンに、次のリクエストでは古いバージョンにヒットする可能性があります。この不整合はインフラストラクチャの検証には問題ありませんが、時間の経過に伴う動作を観察したり、特定のユーザーからフィードバックを収集したりする必要がある場合には問題になります。
コホート:誰が新しいバージョンを取得するかを制御する
特定のユーザーをターゲットにする必要がある場合、トラフィック分割だけでは不十分です。内部テスターに最初に新しいバージョンを見せたい。あるいはベータユーザー、特定の地域のユーザー、特定のサブスクリプションティアのアカウントなどです。ここでコホートが登場します。
コホートとは、ユーザーIDの範囲、地理的な場所、アカウントタイプ、ビジネスセグメント、またはシステムが認識する任意の属性など、基準によって定義されたユーザーのグループです。ランダムな割合のトラフィックをルーティングする代わりに、特定のコホートからのすべてのトラフィックを新しいバージョンにルーティングします。これはステージドロールアウトと呼ばれます。
トラフィック分割に対する利点はトレーサビリティです。内部ユーザーのコホートにリリースする場合、誰が新しいバージョンを見ているかを正確に把握できます。問題が報告された場合、その問題を特定の変更と関連付けることができます。コホートが小さく制御されていれば、爆発半径は抑えられます。エラー率だけでなく、実際の使用パターンを観察できます。
コホートは段階的な拡大も可能にします。内部ユーザーから始め、ベータユーザー、特定の地域の本番ユーザーの5%、25%へと拡大します。各段階で、既知のユーザーグループがシグナルを提供します。ロールアウトは、単一の二択の決定ではなく、意図的な拡大の連続になります。
トレードオフは複雑さです。属性に基づいてユーザーを識別しルーティングするためのインフラストラクチャが必要です。ロードバランサーやゲートウェイは、リクエストパスだけでなくユーザーIDを理解する必要があります。これには多くの場合、認証システム、ユーザーデータベース、セッション管理との統合が必要です。トラフィック分割よりもセットアップに手間がかかりますが、はるかに豊富な制御が可能になります。
フィーチャー:どの機能を有効にするかを制御する
トラフィックとコホートは、ユーザーが実行するアプリケーションのバージョンを制御します。しかし、新しいバージョンをどこにでもデプロイし、その中の特定の機能を選択的に有効にしたい場合もあります。新しいバージョンがすでにすべてのサーバーで実行されているが、特定の機能をまだ全員に見せるべきではない場合や、再デプロイせずにユーザーのサブセットに対して機能を有効にしたい場合です。
ここでフィーチャーフラグが登場します。フィーチャーフラグは、特定のユーザーに対して機能がアクティブかどうかを決定するコード内の条件付きスイッチです。フラグは新しいデプロイなしで実行時に切り替えることができます。ユーザーの10%、内部アカウント、偶数IDのユーザー、または定義した任意のロジックに基づいて機能を有効にできます。
フィーチャーフラグは、デプロイとアクティベーションを分離します。新しい機能を含むコードを、その機能が実際にオンになる数日前または数週間前にデプロイできます。これにより、デプロイ自体のリスクが軽減されます。コードを変更するのと同時に動作を変更しているわけではないからです。機能に問題が発生した場合は、フラグを無効にします。ロールバックは不要です。再デプロイも不要です。設定変更だけです。
フィーチャーフラグの力は粒度にあります。個々のユーザーをターゲットにしたり、A/Bテストを実行したり、段階的に露出を増やしたり、問題を引き起こしている機能を即座に停止したりできます。しかし、その力には責任が伴います。フィーチャーフラグはコードベースに条件付きロジックを追加します。フラグが多すぎたり、クリーンアップされないフラグは技術的負債を生み出します。機能が完全にロールアウトされた後もコードに残るすべてのフラグは、デッドウェイトになります。
3つのレバーの組み合わせ
これらの3つのコンポーネントは相互に排他的ではありません。実際には、成熟したプログレッシブデリバリー戦略は、これらすべてを一緒に使用します。
以下の図は、典型的なプログレッシブデリバリーのシーケンスにおいて、3つのレバーがどのように連携するかを示しています。
典型的なシーケンスは次のようになります。
- トラフィック分割を使用して、新しいバージョンをトラフィックのごく一部にデプロイします。アプリケーションがクラッシュしたりメモリリークしたりしないことを検証します。
- ステージドロールアウトを使用して、内部ユーザーのコホートに拡大します。フィードバックを収集し、動作パターンを観察します。
- 確信が持てたら、新しいバージョンをすべてのサーバーにデプロイしますが、新しい機能はフィーチャーフラグの背後に置きます。フラグをユーザーの10%に対して有効にします。
- メトリクスを監視しながら、フィーチャーフラグの割合を徐々に増やします。問題が発生した場合は、フラグを即座に無効にします。
各レバーは異なる種類の制御を提供します。トラフィックは新しいバージョンが広がる範囲を制御します。コホートは影響を受ける人を制御します。フィーチャーはアクティブな機能を制御します。3つすべてを理解すれば、リスク許容度と検証ニーズに合ったリリース戦略を設計できます。
実践的なチェックリスト
次のリリースの前に、これらの質問を自問してください。
- 最初に基本的なインフラストラクチャの健全性を検証する必要がありますか?トラフィック分割を使用します。
- 特定のユーザーからのフィードバックが必要ですか?コホートとステージドロールアウトを使用します。
- コードは今デプロイしたいが、機能は後で有効にしたいですか?フィーチャーフラグを使用します。
- 新しいバージョンや機能が安全かどうかを測定する方法はありますか?ない場合は、まだロールアウトを開始しないでください。
まとめ
段階的リリースは単一のテクニックではありません。異なる問題を解決する3つの独立した制御手段です。トラフィック分割はインフラストラクチャを検証します。コホートはユーザーエクスペリエンスを検証します。フィーチャーフラグはデプロイとアクティベーションを分離します。安全にリリースするチームは、どのレバーをいつ引くかを知っているチームです。