Fastly と Cloud Load Balancing によるダークカナリアリリース

ジャンプTOON バックエンドチームの國師 (@ronnnnn_jp) です。

2024 年 5 月にサービスを開始した「ジャンプTOON」は、モバイルアプリケーションと Web ブラウザアプリケーションを提供しています。

今日では、Web ブラウザアプリケーションを安全に本番環境へ展開するためのさまざまな手法が存在します。その中でも「ダークカナリアリリース」は、一般ユーザに影響を与えることなく、本番と同等の環境で動作確認を行うための有効な手法です。
本記事では、Fastly と Google Cloud の Cloud Load Balancing を活用して、Web ブラウザアプリケーションのダークカナリア環境を構築した実装について解説します。

目次

  1. ダークカナリアリリースとは
  2. インフラ構成
  3. 要件
  4. アプローチ
  5. おわりに


——

ダークカナリアリリースとは

ダークカナリアリリースとは、一般ユーザに公開する前に、限定的な環境のユーザのみが新バージョンを本番環境と同じ条件で検証できる仕組みです。

通常のカナリアリリースでは、一部のユーザトラフィックを新バージョンに向けますが、ダークカナリアの場合は特定の条件(社内 IP アドレスや特定のヘッダー情報など)を満たすリクエストのみを新バージョンに振り分けます。これにより、本番環境と同じバックエンドサービスを使いながら、一般ユーザには影響を与えずに新しいバージョンの Web ブラウザアプリケーションをテストできます。

このリリース手法は Web ブラウザアプリケーションだけではなく、バックエンドアプリケーションにも活用できます。

インフラ構成

われわれの Web ブラウザアプリケーションは、次のインフラ構成で稼働しています。

インフラ構成

CDN (Contents Delivery Network) として Fastly、ロードバランサとして Google Cloud の Cloud Load Balancing、Web アプリケーションサーバとして Google Cloud の Cloud Run Service を利用しています。

要件

先のインフラ構成に対してダークカナリアリリースを実現するアプローチはいくつかあるため、要件を整理して選択肢を絞りました。

💡
以降、環境の説明を簡略化するために次の表現で説明します。

- 本番環境: エンドユーザがアクセスできる環境
- ダークカナリア環境: 限定ユーザがアクセスできる環境
  1. ダークカナリア環境はサービス運営者のみが利用できること
    1. 非エンジニア (ビジネス職やテスター) にとってもアクセスが容易である
  2. ダークカナリア環境は単一で本番環境と同じドメインとパスを利用できること
    1. Pull Request、ブランチ単位でダークカナリア環境を作るといったことはしない
  3. Web アプリケーションサーバが依存するバックエンドサービス (API サーバなど) は本番環境を利用すること
  4. Fastly と Web ブラウザのキャッシュを本番とダークカナリア環境で分離できること
  5. システムモニタリングやユーザ行動分析で環境を区別できること

アプローチ

先の要件に対してどのようなアプローチを候補とし、何を選択したか紹介します。

アクセス制限とルーティング

われわれのチームには開発環境等にアクセスするための VPN が用意されており、この IP アドレスを社内ユーザと一般ユーザの判別に利用することにしました。

それぞれのユーザをダークカナリア環境と本番環境にルーティングするためのアプローチとして以下の選択肢がありました。

  1. Fastly で特定の HTTP ヘッダー (例: X-DarkCanary) を付与
  2. Web ブラウザのリクエストで特定の HTTP ヘッダーを付与 (ブラウザ拡張機能等を想定)
  3. VPN 環境でしか閲覧できない Web ページを作成し、そこで Cookie に特定の情報を付与しリクエスト

今回は職種に囚われない利便性の高さとモバイルアプリ内の Web ビューを考慮して、1 のアプローチを選択しました。

Fastly での HTTP ヘッダー付与

Fastly VCL を用いることで、Fastly がリクエストを受け取ってからレスポンスを返すまでにカスタムロジックを挿入することができます。



Using VCL | Fastly Documentation

Using VCL | Fastly Documentation

社内 VPN からのアクセスであれば特定のヘッダーを付与するロジックを VCL で記述しています。

sub vcl_recv {
  #FASTLY recv

  # ...
%{ if environment == "prd" }
  call add_dc_header;
%{ endif }
  # ...
}

sub add_dc_header {
  if (req.http.X-DarkCanary) {
    # クライアントから明示的に X-DarkCanary が設定された場合はダークカナリア環境に向かないようにする
    unset req.http.X-DarkCanary;
  }
  if (is_in_dc_allowlist()) {
    set req.http.X-DarkCanary = "1";
  }
}

sub is_in_dc_allowlist BOOL {
  if ( client.ip ~ allowlist_for_dc ) {
    return true;
  } else{
    return false;
  }
}

%{ } の記法は、if の条件が一致している時のみこのロジックが VCL に挿入されるというものです。
全環境向けの VCL を Terraform の Module として管理している場合、この記法を用いることで他の環境に影響を与えず、特定環境のみに処理を追加することができます。
また、万が一ダークカナリア向けの HTTP ヘッダー情報が漏れてしまっても、ダークカナリア環境に接続できないよう HTTP ヘッダーを取り除く処理も入れています。

ダークカナリア Cloud Run Service へのルーティング

本番向けの Web アプリケーションは Cloud Run Service 上で稼働していることを説明しましたが、ダークカナリア向けの Cloud Run Service を新たに用意しました。

  • リビジョンのタグ付けや管理が容易になる
  • 誤って本番に反映されるリスクを低減できる
  • モニタリングの分離が容易になる

という点から別サービスとして用意することを決めました。

Fastly からのリクエストを適切な Cloud Run Service へルーティングするために、Cloud Load Balancing の URL マップを利用しました。
Fastly VCL によって付与されたヘッダー情報を基に、トラフィックを振り分けています。

resource "google_compute_url_map" "userweb" {
  name            = "${var.codename}-${var.environment}-userweb"
  default_service = google_compute_backend_service.userweb.id

  host_rule {
    hosts        = ["*"]
    path_matcher = "allpaths"
  }

  path_matcher {
    name            = "allpaths"
    default_service = google_compute_backend_service.userweb.id

    route_rules {
      priority = 1
      service  = var.google_compute_backend_service_dc_id

      match_rules {
        prefix_match = "/"
        ignore_case  = true
        header_matches {
          header_name = "X-DarkCanary"
          exact_match = "1"
        }
      }
    }
  }
}



従来のアプリケーション ロードバランサにヘッダーとクエリ パラメータ ベースのルーティングを設定する  |  Load Balancing  |  Google Cloud

従来のアプリケーション ロードバランサにヘッダーとクエリ パラメータ ベースのルーティングを設定する  |  Load Balancing  |  Google Cloud

キャッシュの分離

今回のケースでは、Web ブラウザと Fastly において、本番環境とダークカナリア環境のキャッシュが混在しないよう工夫する必要がありました。

しかし、今回のアプローチの性質上、Web ブラウザからのリクエストにはダークカナリア情報が含まれていないため、Web ブラウザでのキャッシュ分離は断念しました。
先で挙げた 2, 3 番目のアプローチであれば、Vary ヘッダーにより、Fastly と Web ブラウザ共にキャッシュを分離できます。
今回は上記の理由から Vary ヘッダーの利用が難しいため、明示的に Fastly VCL でキャッシュキーロジックを変更し、キャッシュの分離を行なっています。

sub vcl_hash {
  set req.hash += req.url;
  set req.hash += req.http.host;
%{ if environment == "prd" }
  set req.hash += req.http.X-DarkCanary;
%{ endif }

  #FASTLY hash
  return(hash);
}

シーケンス

最後にトラフィックの流れの全体像を掲載しておきます。

sequenceDiagram;

actor browser as Web ブラウザ
participant fastly as Fastly
participant lb as Cloud Load Balancing
participant run as Cloud Run (ダークカナリア)

browser ->> fastly: 
fastly ->> fastly: VPN からのリクエスト
に固有ヘッダーを付与 (vcl_recv) fastly ->> fastly: ハッシュキー変更による
キャッシュの分離 (vcl_hash) fastly ->> lb: X-DarkCanary: 1 lb ->> lb: X-DarkCanary を基に
URL マップでバックエンドを分岐 lb ->> run: X-DarkCanary: 1 run ->> lb: lb ->> fastly: fastly ->> browser: ;

おわりに

Fastly と Cloud Load Balancing を組み合わせた、Web ブラウザアプリケーションのダークカナリアリリースについて紹介しました。
ダークカナリアリリースは、安全で効率的なリリースのための手法として、サービス品質の向上に貢献しています。