Managed Service for OpenTelemetry は、分散アプリケーションの開発者に、トレースの可視化、リクエスト量の統計、アプリケーション トポロジー、依存関係分析を含む包括的なツールセットを提供します。このトピックでは、ASM でヘッダーを使用して gRPC トレーシングを実装する方法について説明します。
前提条件
-
Service Mesh (ASM) インスタンスを作成済みであること。詳細については、「ASM インスタンスの作成」をご参照ください。
-
Managed Service for OpenTelemetry をアクティブ化済みであること。料金については、「課金」をご参照ください。
サンプルプロジェクト
サンプル gRPC プロジェクトは、hello-servicemesh-grpc で入手できます。このトピックで言及されているすべてのディレクトリは、hello-servicemesh-grpc リポジトリ内にあります。
gRPC ヘッダーのプログラミング手法
サーバーでのヘッダー取得
-
基本的な方法
-
Java: サーバーでヘッダーを取得します。
ServerInterceptorインターフェースのinterceptCall(ServerCall<ReqT, RespT> call, final Metadata m, ServerCallHandler<ReqT, RespT> h)メソッドを実装します。String v = m.get(k)を使用してヘッダー情報を取得します。getメソッドのパラメータ型はMetadata.Key<String>です。 -
Go: サーバーでヘッダーを取得します。
metadata.FromIncomingContext(ctx)(md MD, ok bool)。ここで、MD はmap[string][]stringです。 -
Node.js: サーバーでヘッダーを取得します。
call.metadata.getMap()の戻り値の型は[key: string]: MetadataValueであり、MetadataValue型はstring/Bufferとして定義されています。 -
Python: サーバーでヘッダーを取得します。
context.invocation_metadata()は、('k','v')形式の 2 要素タプルの配列を返します。m.key, m.valueを使用してキーと値のペアを取得します。
-
-
Unary RPC
-
Java: Unary RPC のヘッダーを取得します。
メソッドはヘッダーを認識しません。
-
Go: Unary RPC のヘッダーを取得します。
メソッド内で直接
metadata.FromIncomingContext(ctx)を呼び出します。コンテキストパラメータctxは、Talkメソッドの入力パラメータから取得されます。 -
Node.js: Unary RPC のヘッダーを取得します。
メソッド内で直接
call.metadata.getMap()を呼び出します。 -
Python: Unary RPC のヘッダーを取得します。
メソッド内で直接
context.invocation_metadata()を呼び出します。
-
-
サーバー ストリーミング RPC
-
Java: サーバー ストリーミング RPC のヘッダーを取得します。
メソッドはヘッダーを認識しません。
-
Go: サーバー ストリーミング RPC のヘッダーを取得します。
メソッド内で直接
metadata.FromIncomingContext(ctx)を呼び出します。コンテキストパラメータctxは、TalkOneAnswerMoreのstream入力パラメータでstream.Context()を呼び出すことで取得します。 -
Node.js: サーバー ストリーミング RPC のヘッダーを取得します。
メソッド内で直接
call.metadata.getMap()を呼び出します。 -
Python: サーバー ストリーミング RPC のヘッダーを取得します。
メソッド内で直接
context.invocation_metadata()を呼び出します。
-
-
クライアント ストリーミング RPC
-
Java: クライアント ストリーミング RPC のヘッダーを取得します。
メソッドはヘッダーを認識しません。
-
Go: クライアント ストリーミング RPC のヘッダーを取得します。
メソッド内で直接
metadata.FromIncomingContext(ctx)を呼び出します。コンテキストパラメータctxは、TalkMoreAnswerOneメソッドのstream入力パラメータでstream.Context()を呼び出すことで取得します。 -
Node.js: クライアント ストリーミング RPC のヘッダーを取得します。
メソッド内で直接
call.metadata.getMap()を呼び出します。 -
Python: クライアント ストリーミング RPC のヘッダーを取得します。
メソッド内で直接
context.invocation_metadata()を呼び出します。
-
-
双方向ストリーミング RPC
-
Java: 双方向ストリーミング RPC のヘッダーを取得します。
メソッドはヘッダーを認識しません。
-
Go: 双方向ストリーミング RPC のヘッダーを取得します。
メソッド内で直接
metadata.FromIncomingContext(ctx)を呼び出します。コンテキストパラメータctxは、TalkBidirectionalメソッドのstream入力パラメータでstream.Context()を呼び出すことで取得します。 -
Node.js: 双方向ストリーミング RPC のヘッダーを取得します。
メソッド内で直接
call.metadata.getMap()を呼び出します。 -
Python: 双方向ストリーミング RPC のヘッダーを取得します。
メソッド内で直接
context.invocation_metadata()を呼び出します。
-
クライアントからのヘッダー送信
-
基本的な方法
-
Java: クライアントからヘッダーを送信します。
ClientInterceptorインターフェイスのinterceptCall(MethodDescriptor<ReqT, RespT> m,CallOptions o, Channel c)メソッドを実装し、返されたClientCall<ReqT,RespT>オブジェクトでstart((Listener<RespT> l, Metadata h))メソッドを実装します。h.put(k, v)を使用してヘッダー情報を追加します。putメソッドの場合、パラメーターkはMetadata.Key<String>型で、vはString型です。 -
Go: クライアントからヘッダーを送信します。
metadata.AppendToOutgoingContext(ctx, kv...) context.Context -
Node.js: クライアントからヘッダーを送信します。
metadata = call.metadata.getMap(); metadata.add(key, headers[key]) -
Python: クライアントからヘッダーを送信します。
metadata_dict = {}変数はmetadata_dict[c.key] = c.valueで設定され、最終的にlist tuple型のlist(metadata_dict.items())に変換されます。
-
-
Unary RPC
-
Java: Unary RPC のヘッダーを送信します。
メソッドはヘッダーを認識しません。
-
Go: Unary RPC のヘッダーを送信します。
メソッド内で直接
metadata.AppendToOutgoingContext(ctx,kv)を呼び出します。 -
Node.js: Unary RPC のヘッダーを送信します。
RPC メソッド内で直接基本的な方法を使用します。
-
Python: Unary RPC のヘッダーを送信します。
RPC メソッド内で直接基本的な方法を使用します。
-
-
サーバー ストリーミング RPC
-
Java: サーバー ストリーミング RPC のヘッダーを送信します。
メソッドはヘッダーを認識しません。
-
Go: サーバー ストリーミング RPC のヘッダーを送信します。
メソッド内で直接
metadata.AppendToOutgoingContext(ctx,kv)を呼び出します。 -
Node.js: サーバー ストリーミング RPC のヘッダーを送信します。
RPC メソッド内で直接基本的な方法を使用します。
-
Python: サーバー ストリーミング RPC のヘッダーを送信します。
RPC メソッド内で直接基本的な方法を使用します。
-
-
クライアント ストリーミング RPC
-
Java: クライアント ストリーミング RPC のヘッダーを送信します。
メソッドはヘッダーを認識しません。
-
Go: クライアント ストリーミング RPC のヘッダーを送信します。
メソッド内で直接
metadata.AppendToOutgoingContext(ctx,kv)を呼び出します。 -
Node.js: クライアント ストリーミング RPC のヘッダーを送信します。
RPC メソッド内で直接基本的な方法を使用します。
-
Python: クライアント ストリーミング RPC のヘッダーを送信します。
RPC メソッド内で直接基本的な方法を使用します。
-
-
双方向ストリーミング RPC
-
Java: 双方向ストリーミング RPC のヘッダーを送信します。
メソッドはヘッダーを認識しません。
-
Go: 双方向ストリーミング RPC のヘッダーを送信します。
メソッド内で直接
metadata.AppendToOutgoingContext(ctx,kv)を呼び出します。 -
Node.js: 双方向ストリーミング RPC のヘッダーを送信します。
RPC メソッド内で直接基本的な方法を使用します。
-
Python: 双方向ストリーミング RPC のヘッダーを送信します。
RPC メソッド内で直接基本的な方法を使用します。
-
ヘッダーの伝播
エンドツーエンドトレーシングを機能させるには、上流サービスからのトレースメタデータを下流サービスに渡して、完全なトレースを形成する必要があります。このため、サーバーが受信したトレーシング関連のヘッダーを、下流リクエストを行うクライアントに伝播する必要があります。
Go、Node.js、Python では、通信モデルメソッドがヘッダーを認識します。これにより、同じ RPC メソッド内でヘッダーの読み取り、受け渡し、送信を順次実行できます。
Java では、ヘッダーの読み取りと書き込みは 2 つの別々のインターセプターによって処理されます。このため、単一の順次プロセスでヘッダー伝播を実装することはできません。同時実行と、一意のトレース ID への読み取りインターセプターの排他的アクセスにより、単純なキャッシュメカニズムを使用して 2 つのインターセプターを橋渡しすることはできません。
Java は、この問題を解決するために Metadata-Context 伝播メカニズムを提供しています。
サーバーインターセプターでは、ctx.withValue(key, metadata) を使用して Metadata/Header を Context に格納します。ここで、key は Context.Key<String> 型です。次に、クライアントインターセプターで、key.get() を使用して Context から Metadata を取得します。get メソッドは、デフォルトで Context.current() を使用します。これにより、単一のリクエストのヘッダーが同じコンテキストから読み取られ、同じコンテキストに書き込まれることが保証されます。
この伝播メカニズムを使用することで、gRPC ベースのサービスのトレーシングを確実に実装できます。
メッシュトポロジーのデプロイと検証
gRPC トレーシングを実装する前に、メッシュトポロジーをデプロイして検証し、すべてのサービスが相互に通信できることを確認する必要があります。
サンプルプロジェクトの tracing ディレクトリに移動します。このディレクトリには、4 つのプログラミング言語すべてのデプロイスクリプトが含まれています。次の例では、Go バージョンを使用してメッシュトポロジーをデプロイおよび検証します。
cd go
# トポロジーをデプロイします
sh apply.sh
# トポロジーを検証します
sh test.sh
エラーが報告されない場合、メッシュトポロジーは正しく通信しています。
デプロイ後の Service Mesh トポロジーを次の図に示します。
トレースの表示
-
ASM インスタンスを設定して、トレースデータを Managed Service for OpenTelemetry にレポートします。詳細については、「トレースデータをレポートするように ASM を設定する」をご参照ください。
-
Managed Service for OpenTelemetry コンソールにログインします。左側のナビゲーションウィンドウで、[トレースエントランス] をクリックします。
-
[トレースエントランス] ページで、対象のアプリケーションを見つけて、[アプリケーション トポロジー] をクリックします。
完全なトレースが表示されます。これには、ローカルリクエストクライアントから Ingressgateway、そして grpc-server-svc1、grpc-server-svc2、grpc-server-svc3 へのパスが含まれます。このページには、対象アプリケーション (
istio-ingressgatewayなど) のサービス呼び出しチェーンの垂直トポロジーが表示され、istio-ingressgatewayから複数レイヤーのgrpc-serverサービスへの呼び出し関係が示されます。各ノードには、成功率と呼び出し乗数がラベル付けされています。 -
[エンドツーエンド集約] ページで、[エンドツーエンド集約] タブをクリックして、エンドツーエンド集約を表示します。
エンドツーエンド集約ページには、集約されたトレース情報が階層テーブルで表示されます。列には、[Span 名]、[アプリケーション名]、[リクエスト数/リクエスト比率]、[リクエストあたりの Span 数]、[平均自己実行時間/比率]、[平均実行時間]、[エラー数/エラー比率] が含まれます。呼び出し階層の各レベルを展開して、各 Span のパフォーマンスメトリクスを表示できます。
-
[エンドツーエンド集約] タブで、[Span 名] 列のトレースをクリックして、その詳細を表示します。
トレース詳細ページの上部には、[開始時刻]、[実行時間]、[アプリケーション]、[トレース深度]、[合計 Span 数] を含むメタデータが表示されます。下のテーブルには、各 Span の階層ビューが表示され、[Span 名]、[タイムライン] の可視化、[アプリケーション名]、[開始時刻]、[IP アドレス]、[ステータス] の列があります。これにより、サービス間の呼び出し階層と実行時間の分布を可視化できます。