複数ホストで動作する Web サーバーをプルリクエスト単位でプレビューする

リクエストのホスト名に応じて動作を変える Web サーバーは、開発効率を改善し一貫したユーザー体験を構築するのを容易にする一方で、プルリクエスト単位でプレビュー環境を作るには工夫が必要です。 この記事では、Google Cloud の Cloud Run でプレビュー環境を作る方法を紹介します。

なお、ここでいう「リクエストのホスト名に応じて動作を変える Web サーバー」とは、例えば foo.example.com と bar.example.com という異なるホストに対するリクエストを一つのサーバーで処理するような構成です。

背景

HQ はこれまで Web アプリケーションごとにサーバーを分ける構成を取ってきましたが、コンパウンド戦略の一環として今後多くの新規プロダクトをリリースすることを踏まえ、複数の Web アプリケーションを 1 つのサーバーでホストするモノリシックな構成に変更することにしました1

変更前と変更後

今回の構成変更により問題となるのがプレビュー環境です。

HQ では Vercel をもちいてプルリクエストごとにプレビュー環境を構築し活用してきました2。 プレビュー環境は、特に開発環境を手元で起動できない非エンジニアのメンバーとのコミュニケーションにおいて重要な役割を果たします。 そのため、新しい構成でもプレビュー環境を維持することが求められます。 しかし、Vercel では単一のホストしか使えないため、新しい構成でうまく動作させることができません3

既存サービスで実現できないのであれば、自前で実装するしかありませんね。

要件

今回プレビュー環境を作るアプリケーションは(説明のため) foo と bar という 2 つのアプリケーションを example.com でホストするものとします。 プルリクエストごとに構成されるプレビュー環境は、ホスト名が gh100-foo.example.com であればプルリクエスト GH-100 の foo アプリケーションを提供するようにします。 gh200-bar.example.com であれば GH-200 の bar アプリケーションが提供されます。

全体構成

以下に、今回構築したプレビュー環境の全体図を示します。4

ダイアグラム

右端の Cloud Run サービスの各リビジョン(gh100gh200)は foobar の 2 つのアプリケーションをホストしています。

外部グローバルアプリケーションロードバランサーを介して Cloud Run を公開する一般的な構成と比較して、重要なポイントは以下の通りです:

  • Certificate Manager によるワイルドカード証明書を利用する。
    • 任意の *.example.com に対して https でのアクセスを許可するために、ワイルドカード証明書が必要です。 Cloud Load Balancing の SSL Certificate はワイルドカード証明書をサポートしていません。 そのため、Certificate Manager を利用してワイルドカード証明書を作成する必要があります。
  • URL Map でホスト名ごとにリクエストを振り分ける。
    • URL Map はリクエストのホスト名に応じて、対応する Backend Service にリクエストを転送するためのものです。 今回の場合、*-foo.example.com*-bar.example.com というホスト名に応じて、それぞれの Backend Service にリクエストを転送するように設定します。 ワイルドカードを利用することで、動的に作成される URL をひとまとめにして扱うことができます。
    • URL Map でワイルドカードを使う場合、 * は必ずホスト名の先頭に配置する必要があります。加えて .- が続く必要があります。 つまり、*-foo.example.com という指定はできますが、foo-*.example.com*foo.example.com といった指定はできません。
  • プルリクエストごとに Cloud Run サービスにリビジョンを作成し、タグを付与する。その上で Serverless NEG の URL Mask により、リクエストのホスト名から動的にリビジョンを選択する。
    • URL Mask に部分文字として <tag> を指定すると、リクエスト URL の対応する箇所から取得した値をリビジョンのタグとして利用することができます。
      • 例えば URL Mask が <tag>-foo.example.com で、ホスト名が gh100-foo.example.com だった場合、リビジョンのタグは gh100 となり、自動的に gh100 のタグが付与されたリビジョンにリクエストが転送されます。
    • URL Mask はワイルドカードに対応していません。つまり <tag>-*.example.com というような指定はできません。 そのため、foobar という 2 つのアプリケーションをホストする場合、それぞれのアプリケーションごとに Backend Service および Serverless NEG を作成する必要があります。

https://gh100-foo.example.com/ へのリクエストの流れ

以下に、https://gh100-foo.example.com/ へリクエストした場合の流れを示します。

ホスト名が gh100-foo.example.com だった場合
  • 接続するとまず TLS ハンドシェイクが行われ、 Cloud Load Balancing がワイルドカード証明書を提供します。
  • リクエストのホスト名が gh100-foo.example.com であるため、URL Map により *-foo.example.com に対応する Backend Service にリクエストが転送されます。
  • Backend Service は URL Mask により <tag> として gh100 を取得し、 同名のタグが付与されたリビジョンにリクエストが転送されます。
  • Cloud Run サービス上で動作するアプリケーションコードが foo アプリケーションを提供します。

このように、ホスト名に応じて動的にリビジョンが選択されるため、プルリクエストごとにインフラを変更する必要はありません。 あとは、プルリクエストごとに Cloud Run サービスにリビジョンを作成し、タグを付与するだけでプレビュー環境を構築することができます。

おわりに

今回の記事では、Google Cloud の Cloud Run と Cloud Load Balancing を使用して、複数ホストで動作するモノリシック Web サーバーのプレビュー環境を構築する方法を紹介しました。

モノリシックな構成を取ることで、開発工数や運用コストを削減するだけでなく、一貫したユーザー体験の提供が容易になります。 今回作成したプレビュー環境を活用することで、開発者や非エンジニアのメンバーとのコミュニケーションを円滑にし、今後もスピーディーな開発を実現していきます。

Footnotes

  1. このアーキテクチャ変更をするに至った背景に関しては改めて別の記事で紹介します。

  2. 詳しくは Code Reviewをツールの力で高速化する を参照してください。

  3. 他にも Vercel のプレビュー環境にアクセスするには Vercel のアカウントが必要であり、 Vercel はユーザー数で課金されるため、プレビュー環境を利用する全てのメンバーにアカウントを発行するコストがバカにならないという問題もあります。

  4. Cloud Run はリビジョンにタグを付与することで独自の URL を持つことができるため、アプリケーションごとにタグを付与することで複数のホストを単一の Cloud Run サービスでホストすることも可能です。 しかし、この場合 Cloud Run が提供するドメインが eTLD+1 であるため5 cross-site となり、クッキーの共有ができないという問題があります。 HQ の場合、アプリケーション間でクッキーを共有する必要があるため、このアプローチは適していません。

  5. このことは publicsuffix.org/list のリストに *.run.app が含まれていることから確認できます。