GKEでのgRPCパフォーマンスベンチマーク
gRPC パフォーマンス ベンチマーク は GKE 上で実行されるように移行され、同様の結果が得られる一方で、柔軟性が大幅に向上しました。
背景
gRPC パフォーマンス テストには、gRPC パフォーマンス ベンチマークで説明されているように、テストドライバーとワーカー(1つ以上のクライアントとサーバー)が必要です。各テストは異なる構成、またはシナリオを持つことができ、これはドライバーに渡され、JSON ファイルとして指定されます。以前は、ドライバーは継続的インテグレーションプロセスによって実行され、ワーカーは長期稼働している GCE VM 上で実行されていました。これにより、いくつかの制限が生じました。
テストは順次実行され、固定された(同じ)VM 上で実行されるため、並列化が困難でした。
VM の状態は、各テストの開始時に同じであることが保証されていませんでした。
手動での実験の実行には、新しい VM の構成が必要でしたが、これは手動プロセスでした。または、既存の VM を再利用して、他のユーザーとの競合や、状態が不明な VM を使用するリスクがありました。
Kubernetes でのベンチマーク
現在のフレームワークのコアは、Kubernetes リソース(`LoadTest` という種類)を管理するための カスタムコントローラー です。このコントローラーは、ロードテストを実行する前に Kubernetes クラスターにデプロイする必要があります。コントローラーは kubebuilder で実装されています。コントローラーのコードは、Test Infra リポジトリに保存されています。個々の `LoadTest` フィールドに関する詳細については、`LoadTest` の実装 を参照してください。
`LoadTest` 構成は、テストのために作成されるドライバー、クライアント、およびサーバーの Pod を指定します。構成がクラスターに適用されると(例えば `kubectl apply -f` を使用)、コントローラーは Pod を作成し、テストが実行されます。複数の構成がクラスターに適用された場合、リソースが利用可能であればコントローラーは Pod を作成し、テストを並列実行できるようになります。
例 には、直接適用できる基本的な構成と、追加の手順とパラメータ置換が必要なテンプレートが含まれています。
基本的な構成は、コントローラーの各リリースにバンドルされている **clone**、**build**、および **runtime** ワーカーイメージに依存しています。clone および build イメージは、gRPC バイナリをビルドするために使用され、runtime コンテナーに渡されます。これらの構成は、例や一時的なテストに適しています。
テンプレート構成は、テストを開始する前にビルドされるワーカーイメージに依存しています。これらの **事前ビルド済みイメージ** には gRPC バイナリが含まれており、各テストの前に clone および build する必要がなくなります。テンプレート置換は、ワーカーイメージの場所を指定するために使用されます。これらの構成は、同じ gRPC バージョンで一連のテストを実行する場合や、同じテストを繰り返し実行する場合に適しています。
コントローラーに加えて、Test Infra リポジトリには、テストランナーや、事前ビルド済みワーカーイメージのビルドおよび削除ツール、さらに ダッシュボード の実装を含む一連の ツール が含まれています。
事前ビルド済みワーカーに関連するツールは内部で `gcloud` を使用しており、GKE に依存しています。それ以外は、フレームワークのすべてのコンポーネントは Kubernetes 自体上に構築されており、GKE に依存していません。つまり、コントローラーをデプロイし、カスタム Kubernetes クラスターまたは他のクラウドプロバイダーの Kubernetes オファリングでテストを実行できるはずです。
クラスターのセットアップ
ベンチマークジョブを実行するクラスターは、サポートしたい同時テストの数に合わせてノードプールを構成する必要があります。コントローラーは、さまざまな Pod タイプのノードセレクターとして `pool` を使用します。ワーカー Pod は相互にアフィニティがないため、Pod ごとに 1 つのノードが必要です。
例えば、継続的インテグレーションセットアップで使用されているノードプールは、以下のように構成されています。
| プール名 | ノード数 | マシンタイプ | Kubernetes ラベル |
|---|---|---|---|
| system | 2 | e2-standard-8 | default-system-pool:true, pool:system |
| drivers-ci | 8 | e2-standard-2 | pool:drivers-ci |
| workers-c2-8core-ci | 8 | c2-standard-8 | pool:workers-c2-8core-ci |
| workers-c2-30core-ci | 8 | c2-standard-30 | pool:workers-c2-30core-ci |
テストでは各シナリオにドライバー 1 つとワーカー 2 つが必要なため、この構成では 8 コアマシンで 4 つの同時テスト、30 コアマシンで 4 つの同時テストをサポートできます。ドライバーはリソースをほとんど必要とせず、相互にアフィニティがありません。ドライバープールをワーカープールと一緒にリサイズできるため、2 コアマシンにドライバーをスケジュールし、ノード数を必要なドライバー数に設定することは便利です。コントローラー自体は `system` プールにスケジュールされます。
継続的インテグレーションで使用されるプールに加えて、クラスターにはアドホックテストに使用できる追加のノードプールがあります。
| プール名 | ノード数 | マシンタイプ | Kubernetes ラベル |
|---|---|---|---|
| drivers | 8 | e2-standard-8 | default-driver-pool:true, pool:drivers |
| workers-8core | 8 | e2-standard-8 | default-worker-pool:true, pool:workers-8core |
| workers-32core | 8 | e2-standard-32 | pool:workers-32core |
一部のプールには `default-*-pool` ラベルが付いています。これらのラベルは、`LoadTest` 構成で指定されていない場合に、使用するプールを指定します。上記の構成では、これらのテスト(例えば、例 で指定されているテスト)は `drivers` および `workers-8core` プールを使用し、継続的インテグレーションジョブに干渉しません。デフォルトラベルはコントローラービルドの一部として定義されています。設定されていない場合、コントローラーは `pool` ラベルが明示的に指定されているテストのみを実行します。
コントローラーのデプロイ
コントローラーのビルドとデプロイの手順については、デプロイメントドキュメント で説明されています。
継続的インテグレーション
継続的インテグレーションセットアップについては、gRPC Core リポジトリの gRPC OSS ベンチマーク README で説明されています。主な継続的インテグレーションジョブは、`grpc_e2e_performance_gke.sh` スクリプト を使用して、gRPC パフォーマンス ベンチマーク ページにリンクされているダッシュボードに表示されるデータを生成します。
各継続的インテグレーション実行には 3 つのフェーズがあります。
- テスト構成の生成。
- ワーカーイメージのビルドとプッシュ。
- テストの実行。
各継続的インテグレーション実行は、8 コアワーカープールを使用して 122 のテストを実行し、30 コアワーカープールを使用して 98 のテストを実行します。各テストは 1 つのテストシナリオを実行します。C++、C#、Java、Python ワーカーを使用するテストは両方のプールで実行されます。Node.js、PHP、Ruby ワーカーを使用するテストは 8 コアプールでのみ実行されます。構成生成は、これらのすべての組み合わせに対してほぼ瞬時に (~1秒) 完了します。
継続的インテグレーションで使用される構成では、テスト対象の gRPC バイナリを含むワーカーイメージが必要です。これらのイメージはワーカーの言語にのみ依存するため、これらの 事前ビルド済みイメージ は事前にビルドされ、イメージリポジトリにプッシュされます。このプロセスには約 20 分かかります。
A テストランナー は、クラスターにテストが適用されるレートを管理し、テスト結果とログを収集し、テストが正常に完了したら削除します。各プールで同時に 2 つのテストを実行できます。このフェーズは完了までに約 50 分かかります。
各テストシナリオは 30 秒(Java の場合は 15 秒)の実行時間に、5 秒のウォームアップ期間が追加されるように構成されています。これにより、各テストの実行に必要な最小時間が決まります。8 コアプール(Java は 16 テスト)の 122 テストの観察された実行時間(一度に 2 つのテストを実行)は、Pod の作成と削除によって導入されるオーバーヘッドが約 12.8 秒/テストと控えめであることを示唆しています。
設定の生成
多数のテストが(さまざまな言語のドライバーとワーカーなど)同じコンポーネントを共有しているため、テストシナリオのみが異なる、繰り返し使用されるドライバーとワーカーの構成を含む構成を生成する必要があります。さらに、Kubernetes クラスターに適用されるリソースには一意の名前が必要なため、各構成には一意の名前が必要です。
これらの問題は、ツールを使用して ロードテスト構成を生成する ことで対処しています。このツールは gRPC Core リポジトリに保存されており、テストシナリオもここで定義されています。
事前ビルド済みイメージ
継続的インテグレーション用に生成された構成では、事前ビルド済みイメージのセットが使用されます。これらのイメージは、テストを実行する前にビルドされ、イメージリポジトリにプッシュされます。イメージは各テスト実行の終了時に削除されます。
イメージの準備と削除に使用されるツールについては、gRPC OSS ベンチマークでの事前ビルド済みイメージの使用 を参照してください。
テストランナー
The テストランナー は、以前生成されたテスト構成を取得し、各構成をクラスターに適用し、各 `LoadTest` リソースの完了をポーリングし、結果や Pod ログなどのアーティファクトを収集し、(オプションで)各テストが正常に完了したらリソースを削除します。
テストランナーは、クラスター上で同じリソースを必要とするテスト(例:8 コアまたは 30 コアのワーカーノード)のために、個別の *キュー* を維持します。同じキューに属するテスト構成は一度にクラスターに適用されるのではなく、各キューに設定された *並列度* に従って適用されます。継続的インテグレーションテストは 2 つのキュー(8 コアおよび 30 コアのワーカーノードに対応)で実行されます。各キューの並列度は 2 に設定されています。
構成がクラスターに適用されると、コントローラーはクライアント、ドライバー、サーバーの Pod を作成してテストを実行し、テスト実行を監視し、`LoadTest` リソースのステータスを更新します。
テストランナーの設計は次のように説明できます。
テストランナーを使用することで、継続的インテグレーションジョブはすべてのテストの完了を待ち、テストアーティファクトを収集し、結果を含むレポートを作成できます。
個別のキュー(各テスト構成のアノテーションで示されます)を使用することで、同じクラスターリソースを必要としないテストを互いに独立して管理できます。
制限された並列度を使用することで、一度にクラスターに適用されるテストの数が削減されます。これにはいくつかの利点があります。
テストランナーへの負荷が軽減されます。クラスター上の `LoadTest` リソースの数が少なくなり、ランナーはこれらのリソースを定期的にポーリングして完了を確認します。継続的インテグレーションでのポーリング間隔は 5 秒に設定されています。
コントローラーへの負荷が軽減されます。管理対象の `LoadTest` リソースの数が少なくなるためです。
各テストにはより短いタイムアウト期間を設定できます。これは、コントローラーが各テストを開始するのにかかる時間がより予測可能であるためです。タイムアウトは、クライアントまたはサーバーの Pod がハングしてテストの完了を妨げるエラーケースに対応するために必要です。これらのケースはまれですが、蓄積してクラスターリソースを消費し、他のテストの実行を妨げる可能性があります。継続的インテグレーションでのテストには 15 分のタイムアウトがあります。
並列度はクラスターの容量よりも低く設定できるため、ユーザーは他のユーザーが同時にテストを実行することを妨げることなく、一連のテストを実行できます。
各テストが正常に完了した後に(結果とログが収集された後)削除するオプションは、各テストのライフサイクルをより適切に制御します。
コントローラーのデフォルトの動作は、`LoadTest` リソースと関連する Pod を設定された TTL に達するまでクラスターに保持し、その後削除することです。継続的インテグレーションでは、各テストに 24 時間の TTL を指定しています。
完了した `LoadTest` に属する Pod は終了状態であり、クラスターリソースを消費しません。ただし、終了した Pod はいつでもガベージコレクションされる可能性があります。
継続的インテグレーションクラスターで完了したすべてのテストに属する Pod を保持すると、1 時間以内にガベージコレクションされることがわかりました。
正常に完了したテストの `LoadTest` リソースを削除すると、関連する Pod も削除されます。この場合、*失敗した* テストに属する Pod は、数が少なくデバッグに役立つ可能性があるにもかかわらず、24 時間の TTL に達するまでクラスターに残ります。
ダッシュボード
継続的インテグレーションからのテスト結果は、BigQuery に保存されます。BigQuery に保存されたデータは、ダッシュボードでの可視化のために Postgres データベースにレプリケートされます。
ダッシュボードのコード、およびメインの継続的インテグレーションダッシュボードの構成は、Test Infra リポジトリに保存されています。これにより、次の利点があります。
メインダッシュボードは、UI で直接更新するのではなく、保存された構成を更新することによって維持されます。
ユーザーは独自の構成を使用して、独自のダッシュボードをデプロイできます。
これは、Perfkit Explorer を使用して構築された以前のベンチマークのダッシュボードとは対照的です。これは UI で直接更新することによって維持されており、ユーザーが簡単に再現することはできませんでした。
詳細については、ダッシュボードの実装 を参照してください。


結果
GKE 上の gRPC ベンチマークの結果とユーザーエクスペリエンスから、次の観察結果が得られます。
パフォーマンスメトリクス(レイテンシ、QPS など)は、GCE 上の古いベンチマークと同等またはそれ以上の結果を生成します。
GKE での各テストにおける Pod の作成と削除のオーバーヘッドは、ベンチマーククラスターでは小さい(15 秒未満)です。
テストイメージは Docker 化されており、各テストで再起動されるため、いくつかの利点があります。
結果はより一貫性があります。
実行時エラーはまれです。
システムは明確に定義されたコンポーネントに分割されており、アップグレードが容易になります。
テストは簡単に並列化できるため、実行時間が短縮されます。
実験が容易になります。
ベストプラクティスと実験から得られた洞察の例
クライアントとサーバーには `c2` インスタンスを使用してください(インスタンスタイプは、観測されるレイテンシとそのばらつき、および測定されるスループットに大きく影響します)。
GKE の Pod 間ネットワークは、生の GCE ネットワークよりもオーバーヘッドが非常に小さいです。ベンチマーク Pod で `hostnetworking:true` を設定することで、生の GCE ネットワークパフォーマンスを得ることができます。
Docker 上の Java では、JVM が利用可能なプロセッサ数を自動的に検出できない場合があります。gRPC は検出されたプロセッサ数を使用してイベント処理用のスレッドプールをサイジングするため、これは非常に悲観的な結果につながる可能性があります。回避策として、プロセッサ数を明示的に設定します。この回避策は ここで 実装されています。
独自の実行
The Test Infra リポジトリのコードを使用すると、任意のユーザーがクラスターを作成し、コントローラーをデプロイし、gRPC ベンチマークを実行し、独自のダッシュボードに結果を表示できます。パフォーマンスに興味があり、独自のベンチマークを実行している場合は、お知らせください!