はじめに
宿泊UI開発チームでソフトウェアエンジニアをしている原です。昨年の10月に入社しました。
私の所属する宿泊プロダクト開発部では主に 一休.com と Yahoo!トラベル を開発しており、今回お話するのは、両サービスのトップページ、施設一覧ページ、施設詳細ページなどの主要な導線のフロントエンドを担う Nuxt.js で作られたアプリケーションのインフラとデプロイについてです。
今回はこのアプリケーションにカナリアリリースの手法を取り入れて、より安全にリリースできるようになった話をします。
カナリアリリースとは
カナリアリリースとは、複数の実行環境を用意しアプリケーションの新旧のバージョンを同時に稼働させ、一部のユーザーに絞って新環境を公開するリリース手法です。 カナリアリリースによって新バージョンに不具合があった場合でもユーザー全体に影響を及ぼすことなく、リスクを低減してリリースすることができます。
導入のきっかけ
一休では昨年から今年にかけて、宿泊プロダクトのNuxt.jsのバージョンを2系から3系にアップグレードしました。 詳しくは 一休.com、Yahoo!トラベルのNuxtをNuxt3にアップグレードしました をご覧ください。
上記のブログ記事の「リリース戦略」の項にあるように、メインブランチの内容が反映されているNuxt2バージョンの環境と、検証用ブランチのNuxt3バージョンの環境を立てて、Fastlyでユーザーの振り分けを行っていました。
無事にNuxt3へのバージョンアップが完了し、検証用環境がお役御免になったと思っていたところ、検索フォームの実装をまるごと置き換える大掛かりなリファクタリングが行われました。 同一アプリケーション内で条件分岐によるfeature flagは元来行われていましたが、それを差し込むのも難しいくらい大きな差分が発生するリファクタリングになりました。 そこで影響範囲が大きいリリースになるので失敗のリスクを最小限にしたいと考え、バージョンアップの検証用の環境をカナリア環境と銘打って引き続き使用することにしました。
カナリア環境の実現方法
以下が簡単な構成図です。
EKS
宿泊プロダクト内のシステムの多くは EKS で稼働していて、この Nuxt.js アプリケーションも EKS で動いています。 クラスタ内に通常バージョンが動作しているものとは別にカナリア環境用のデプロイメントを作成し、そこでカナリアバージョンのアプリケーションを動かします。
Fastly
通常環境とカナリア環境どちらにリクエストを向けるのか振り分けをFastlyで行っています。
以下コード例です(変数やCookieは仮のものです)。
sub vcl_recv { // リクエストの振り分け if (req.http.Cookie:new-environment-v1) { set req.http.new-environment-v1 = req.http.Cookie:new-environment-v1; } else { set req.http.new-environment-v1 = false; // カナリア環境に10%リクエストを振り分ける if (randombool(10, 100)) { set req.http.new-environment-v1 = true; } set req.http.new-environment-v1-new-cookie = "new-environment-v1=" req.http.new-environment-v1 "; max-age=31536000; path=/; secure; httponly"; } ... // req.backend はfastlyがリクエストを流すオリジンを指定する if (req.http.new-environment-v1 == true) { set req.backend = new-environment; } else { set req.backend = normal-environment; } } sub vcl_deliver { // Cookieの付与 if (req.http.new-environment-v1-new-cookie) { add resp.http.Set-Cookie = req.http.new-environment-v1-new-cookie; set resp.http.Cache-Control = "no-store"; unset req.http.new-environment-v1-new-cookie; } ... }
大まかな処理の流れを説明すると、
- リクエストを新旧どちらの環境に向けるのかを識別するCookieの有無を確認
- Cookieが付与されていなかったら、randombool関数 で一定の割合で新旧どちらに向けるかを決める
- 新旧どちらかのオリジンにリクエストを流す
- 新環境へリクエストした場合、次回リクエスト時も新環境へ向けられるようにCookieを付与
という流れになっています。 このアプリケーションは初回リクエスト以降はNuxtサーバーへのリクエストが不要なSPAになっているため、旧環境へ向いていたが動作中に新環境へリクエストしてしまい動作に不具合が生じるといったことも起こりません。 Nuxtのビルド成果物はS3にアップロードしており、アプリケーションが必要とする静的ファイルはカナリアリリースとは関係なく取得することができます。
運用方法
アプリケーションリリース方法
通常バージョンのアプリケーションはreleaseブランチへのマージを契機にCIで自動的にimageをビルド、pushをしてリリースされます。
カナリアバージョンも同様にcanary-release
ブランチへのマージを契機にカナリア環境へリリースされます。
スケールイン、スケールアウト
カナリアリリースを使用していない間はインフラコストの削減のため、podの最小レプリカ数を最小限にしています。 カナリアリリース開始時に流すリクエストの割合に応じてpodの最小レプリカ数を引き上げます。
リクエスト割合の調整
上述のvclのrandomboolの割合を変更します。
この際に割合が正しく反映されるようにnew-environment-v1
といった変数やCookieのsuffixのバージョンもインクリメントさせる必要があります。
以下カナリア環境へのリクエスト割合を10%から0%に変更する際のコード差分の例です。
sub vcl_recv { - if (req.http.Cookie:new-environment-v1) { - set req.http.new-environment-v1 = req.http.Cookie:new-environment-v1; + if (req.http.Cookie:new-environment-v2) { + set req.http.new-environment-v2 = req.http.Cookie:new-environment-v2; } else { set req.http.new-environment-v1 = false; - if (randombool(10, 100)) { - set req.http.new-environment-v1 = true; + if (randombool(0, 100)) { + set req.http.new-environment-v2 = true; } - set req.http.new-environment-v1-new-cookie = "new-environment-v1=" req.http.new-environment-v1 "; max-age=31536000; path=/; secure; httponly"; + set req.http.new-environment-v2-new-cookie = "new-environment-v1=" req.http.new-environment-v2 "; max-age=31536000; path=/; secure; httponly"; } ... - if (req.http.new-environment-v1 == true) { + if (req.http.new-environment-v2 == true) { set req.backend = new-environment; } else { set req.backend = normal-environment; }
このように割合変更(カナリア環境へのリクエストを取りやめることも含む)をする度に、コードの修正が必要になります。
カナリアリリース導入の効果
Nuxtをはじめとしたライブラリのバージョンアップや、コード差分が大きく影響範囲の大きなリリースに対するリスクを大幅に軽減できるようになりました。 またこのNuxtアプリケーションは、宿泊事業部内の複数チームが開発しているのですが、カナリアリリースの概要やリリース手順書といったドキュメントの作成をしたり、リリースの都度レクチャーすることで、どのチームもカナリアリリースによって安全にデプロイできるようになりました。
課題
上述の通りカナリア環境へのリクエスト割合を変更するたびにVCLの変数のsuffixを変更する必要があり、そこそこ面倒な作業になっています。 また、カナリアリリース実施中に通常環境のリリースがあった際にその内容をカナリア環境に取り込む必要があります。そのため常に通常バージョンのリリース動向をチェックする必要があり、これもそれなりの負担があります。 これらを自動化などでコストを軽減できないかと模索中です。
最後に
一休ではプロダクトの機能開発をしながらボトムアップで開発基盤の改善もしたい!というエンジニアを大募集中です。 カジュアル面談も実施しているので、お気軽にご応募ください。