Cloudflareは、2020年のBirthday Week中にgrPC® のサポートを開始しました  。当社は、皆様がベータ版への大きな関心をお寄せくださったことに、身の引き締まる思いです。そして、grPCを適用し、試してくださったすべての方々に感謝申し上げます!この記事では、サポートの実装方法に関する技術的な点について詳しくご説明します。

gRPCとは何か

grPCは、HTTP/2 上で動作するオープンソースのRPCフレームワークです。RPC(Remote Procedure Call:遠隔手続き呼び出し)は、あるマシンに、ライブラリ内のローカル関数を呼び出させるのではなく、別のマシンに何かをするように指示する方法です。RPCは分散コンピューティングの歴史の中で、長い間、さまざまな分野に焦点を当てて、さまざまな実装を行ってきています。grPCには以下のような特徴があります。

  • トランスポートに、最新のHTTP/2プロトコル(現在、広く利用可能)を必要とする。
  • 完全なクライアント/サーバーリファレンス実装、デモ版、テストスイートがオープンソースとして利用可能 。
  • メッセージフォーマットに関する指定はないが、シリアル化メカニズムでは、プロトコルバッファが望ましい。
  • クライアントとサーバの両方がデータをストリーミングできるため、新しいデータを引き出したり、新しい接続を作成したりする必要がない。

プロトコルに関しては、 grPC は HTTP/2 フレームを広範囲に使用しています。リクエストとレスポンスは、通常の HTTP/2 リクエストと非常によく似ています。

しかし、珍しいのは、GRPCによるHTTPトレーラーの使用法です。一般的には広く使われていませんが、HTTPトレーラーは1999年から存在していて、オリジナルのHTTP/1.1 RFC2616で定義されています。HTTP メッセージヘッダーは HTTP メッセージ本文の前に来るように定義されていますが、HTTP トレーラーは、メッセージ本文の後に追加できる HTTP ヘッダーをまとめたものです。しかし、トレーラーの使用例が多くないため、多くのサーバーとクライアントの実装では完全にサポートされていません。HTTP/1.1は、HTTPトレーラーを送信するために、チャンク転送エンコーディングをその本文に使用する必要がありますが、HTTP/2の場合、トレーラーは本文のDATAフレームの後のヘッダーフレームにあります。

HTTPトレーラーが有用になるいくつかのケースがあります。たとえば、リクエストの状態を示すためには、HTTPレスポンスコードを使用しますが、レスポンスコードはHTTPレスポンスの最初の行であるため、レスポンスコードを早期に決定する必要があります。トレーラーを使用すると、本文の後にメタデータを送信することができます。たとえば、Webサーバーが大きなデータ(固定サイズではない)のストリームを送信し、最終的に送信したデータの SHA256 チェックサムを送信して、クライアントが内容を確認できるようにするとします。通常、これはHTTPステータスコードまたはレスポンスの先頭に送信されるべきレスポンスヘッダーでは不可能です。HTTPトレーラーヘッダーを使用して、すべてのデータを送信した後、別のヘッダー(例えば、ダイジェスト )を送信することができるのです。

gRPCは、HTTP トレーラーを使用する目的が2つあります。最初の目的は、コンテンツの送信後に、最終ステータス (grpc-status) をトレーラーヘッダーとして送信することです。2番目の目的は、ストリーミングユースケースをサポートすることです。これらのユースケースは、通常のHTTPリクエストよりもずっと長く続きます。HTTPトレーラーは、リクエストまたはレスポンスのポスト処理結果を送信するために使用されます。たとえば、ストリーミングデータ処理中にエラーが発生した場合は、トレーラーを使用してエラーコードを送信できます。これは、メッセージ本文の前に来るヘッダーでは不可能です。

HTTP/2 フレームの gRPC リクエストとレスポンスの簡単な例を次に示します。

Cloudflare エッジに gRPC サポートを追加する

gRPCはHTTP/2を使用しており、CloudflareはすでにHTTP/2をサポートしているため、gRPCをネイティブにサポートするのは簡単だと思われるかもしれません。しかし、私たちにはいくつかの問題がありました。

  • HTTPリクエスト/レスポンストレーラーヘッダーは、エッジプロキシによって完全にサポートされていません。CloudflareはNGINXを使用してユーザーからのトラフィックを受け入れますが、トレーラーのサポートは制限されています。さらに複雑なことに、Cloudflareを経由するリクエストとレスポンスは、一連の他のプロキシを通過することになります。
  • HTTP/2 からオリジンへ:エッジプロキシは HTTP/1.1 を使用して、オリジンから (動的か静的かに関わらず) オブジェクトをフェッチします。 gRPC トラフィックをプロキシするには、顧客のgRPCオリジンへのHTTP/2をサポートする必要があります。
  • gRPC ストリーミングは双方向のリクエスト/レスポンスのフローを許可する必要があります。gRPC には2 種類のプロトコルフローがあります。1 つは単一項目からなるもので、シンプルなリクエストとレスポンスです。もう1つはストリーミングで、各方向のノンストップデータフローを可能にします。ストリーミングを完全にサポートするには、もう一方の側でレスポンスヘッダーを受信した後、HTTP メッセージ本文を送信する必要があります。たとえば、クライアントストリーミングは 、レスポンスヘッダーを受信した後、リクエスト本文を送信し続けます。

これらの理由から、ネットワーク経由でプロキシされた場合、gRPC リクエストは中断します。これらの制限を克服するために、さまざまな解決策を考えました。たとえば、NGINXは、HTTP/2 gRPCオリジンをサポートするためにgRPCアップストリームモジュール を持っていますが、それは別のモジュールであり、HTTP/2ダウンストリームが必要ですが、当社のサービスには使用できません。なぜなら、場合によっては複数のHTTPプロキシを通してリクエストがカスケードするためです。パイプラインのあらゆる場所で HTTP/2 を使用することは、現実的ではありません。 内部の負荷分散アーキテクチャの特性上、 およびすべての内部トラフィックがHTTP/2 を使用していることを確認するのに多大な労力を要するためです。

HTTP/1.1に変換しますか?

最終的に、当社はより良い方法を発見しました:gRPCメッセージを当社ネットワーク内でトレーラーなしでHTTP/1.1メッセージに変換し、その後、元のリクエストを送信する前にHTTP/2に変換するのです。これなら、HTTPトレーラーをサポートしていないCloudflare内のほとんどのHTTPプロキシで動作し、最小限の変更だけで済みます。

当社が独自フォーマットを発明するかわりに、gRPCコミュニティはすでにHTTP/1.1互換バージョンを考え出しています。これがgRPC-web です。gRPC-Webは、元のHTTP/2ベースのgRPCの仕様を変更したものです。本来の目的は、HTTP/2フレームに直接アクセスできないWebブラウザで使用することでした。gRPC-Web では、HTTP トレーラーが本文に移動されるので、プロキシ内の HTTP トレーラーサポートについて心配する必要はありません。また、ストリーミングサポートが付属しています。生成されたHTTP/1.1メッセージは、WAFやボット管理などの当社のセキュリティ製品によって検査され、Cloudflareが他のHTTPトラフィックに提供するのと同じレベルのセキュリティを提供することができます。

CloudflareのエッジプロキシでHTTP/2 grPC メッセージが受信されると、メッセージは HTTP/1.1 GRPC Web 形式に「変換」されます。gRPC メッセージが変換されると、通常のHTTPリクエストと同じ方法で WAF、キャッシュ、Argo サービスなどのサービスを適用し、当社のパイプラインを通過します。

gRPC WebメッセージがCloudflareネットワークを離れる直前に、再びHTTP/2 gRPCに「変換」する必要があります。当社システムによって変換されたリクエストは、当社システムが誤ってお客様から発信されたgRPC-Webトラフィックを変換しないように、マークされています。

HTTP/2 オリジンのサポート

エンジニアリング上の課題の1つは、HTTP/2 を使用してオリジンへの接続をサポートすることでした。このプロジェクト以前は、CloudflareにはHTTP/2経由でオリジンに接続する機能がありませんでした。

そのため、HTTP/2 オリジンサポートのサポートを社内で構築することにしました。HTTP/2 経由でオリジンに接続できるスタンドアロンのオリジンプロキシを構築しました。この新しいプラットフォームに加えて、gRPCの変換ロジックを実装しました。この新しいプラットフォームを利用する最初の機能は、gRPC サポートです。オリジンサーバーへの HTTP/2 接続の広範なサポートが今後展開予定です。

gRPC ストリーミングサポート

既述したように、gRPCにはリクエスト本文またはレスポンス本文をストリームで送信できるストリーミングモードがあります。gRPCリクエストがある限り、gRPCメッセージブロックはいつでも送信できます。ストリームの終わりには、ストリームの終了を示すヘッダーフレームがあります。gRPC-webに変換されると、Cloudflareからチャンクエンコーディングを使用して本文が送信され、ストリームの終わりを示すgRPCメッセージを受け取るまで、接続を開いたままで、両サイドから送信される本文を受け取り続けます。このために、当社のプロキシが双方向転送をサポートする必要があります。

たとえば、クライアントストリーミングは、サーバーがレスポンスコードとそのヘッダーを使ってすでに応答していても、クライアントがリクエスト本文を送信できる興味深いモードです。

相互運用性試験

Cloudflareのすべての新機能は、リリース前に適切なテストが必要です。初期開発時には、gRPC Web フィルター機能 とgRPCの公式例でEnvoy プロキシを使用しました。EnvoyとgRPCテストオリジンを使用してテスト環境を用意し、エッジプロキシが gRPC リクエストで正常に動作することを確認しました。gRPC テストクライアントからのリクエストは、エッジプロキシに送信された後、gRPC-Webに変換され、Envoyプロキシに転送されます。その後、Envoyは gRPC リクエストへと変換し直され、gRPC テストオリジンに送信されます。この方法で基本的な動作を検証することができました。

基本機能の準備ができたら、両方の末端にある変換機能が適切に機能することを確認する必要がありました。そのために、より詳細な相互運用性試験を構築しました。

テストスイートのために、既存のgrPC 相互運用性試験ケース を参照し、最初の試験は、エッジプロキシと新しいオリジンプロキシ間でローカルに実行しました。

2番目の試験では、異なるgRPC実装を使用しました。たとえば、一部のサーバーは、即時エラーが発生したときに、最終ステータス(grpc-status)をトレーラーのみのレスポンスとして送信しました。この応答には、HTTP/2 レスポンスヘッダーと、「END_STREAM」フラグと「END_HEADERS」フラグの両方を伴った、単一の「HEADERS」フレームブロック内にあるトレーラーが含まれることになります。他の実装では、最終ステータスが、別の「HEADERS」フレーム内で、トレーラーとして送信されました。

相互運用性をローカルで確認した後、すべてのサービスをサポートする開発環境に対してテストハーネスを実行しました。これにより、意図しない副作用がgRPCリクエストに影響を与えることがないことを確認できました。

当社は試験運用が大好きです!エッジgRPCサポートを正常にデプロイした最初のサービスの1つは、Cloudflare drand ランダムネスビーコンです   。オンボーディングは簡単で、ここ数週間の本番運用では問題なくビーコンを稼働できています。

まとめ

新しいプロトコルをサポートするのは、エキサイティングな仕事です!既存のシステムに新しいテクノロジーのサポートを実装することは複雑で、多くの場合、実装速度とシステム全体の複雑さのトレードオフを伴います。 grPCの場合、Cloudflareエッジに大きな変更を必要としない方法で、迅速にサポートを構築することができました。これは、HTTP/2 gRPC と HTTP/1.1 grPC-Web フォーマットの間の変換のアイデアに落ち着く前に、実装オプションを慎重に検討することによって達成されました。この設計の選択により、ユーザーの期待と制約を満たしながら、サービスの統合が迅速かつ容易になりました。

Cloudflareを使用してgrPCサービスをセキュリティで保護し、高速化することに興味がある方は、  こちらで詳細を読むことができます    。この記事で説明しているような興味深いエンジニアリング課題に取り組みたい場合は、ご応募ください

gRPC® Linux Foundationの登録商標です。