gRPC Java で基本的な OpenTelemetry プラグインを設定する

1. はじめに

この Codelab では、gRPC を使用して、Java で記述されたルート マッピング アプリケーションの基盤となるクライアントとサーバーを作成します。

このチュートリアルを完了すると、gRPC OpenTelemetry プラグインで計測されたシンプルな gRPC HelloWorld アプリケーションが作成され、エクスポートされたオブザーバビリティ指標を Prometheus で確認できるようになります。

学習内容

  • 既存の gRPC Java アプリケーションに OpenTelemetry プラグインを設定する方法
  • ローカル Prometheus インスタンスの実行
  • Prometheus への指標のエクスポート
  • Prometheus ダッシュボードから指標を表示する

2. 始める前に

必要なもの

  • git
  • curl
  • JDK v8 以降

次コマンドで前提条件をインストールします。

sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install -y git curl

コードを取得する

学習を効率化するため、この Codelab では、すぐに始められるように、あらかじめ作成されたソースコード スキャフォールドが用意されています。次の手順では、アプリケーションで gRPC OpenTelemetry プラグインを計測する方法について説明します。

grpc-codelabs

この Codelab のスキャフォールディングのソースコードは、こちらの GitHub のディレクトリにあります。コードを自分で実装しない場合は、completed ディレクトリで完成したソースコードを利用できます。

まず、grpc codelab リポジトリのクローンを作成し、grpc-java-opentelemetry フォルダに移動します。

git clone https://github.com/grpc-ecosystem/grpc-codelabs.git
cd grpc-codelabs/codelabs/grpc-java-opentelemetry/

または、Codelab ディレクトリのみを含む .zip ファイルをダウンロードして、手動で解凍することもできます。

3. OpenTelemetry プラグインを登録する

gRPC OpenTelemetry プラグインを追加するには、gRPC アプリケーションが必要です。この Codelab では、gRPC OpenTelemetry プラグインでインストルメンテーションするシンプルな gRPC HelloWorld クライアントとサーバーを使用します。

まず、クライアントで Prometheus エクスポータで構成された OpenTelemetry プラグインを登録します。任意のエディタで codelabs/grpc-java-opentelemetry/start_here/src/main/java/io/grpc/codelabs/opentelemetry/OpenTelemetryClient.java を開き、main を変更して gRPC Java OpenTelemetry API を設定するコードを追加します。

クライアントで計測を設定する

Prometheus エクスポータを作成する

PrometheusHttpServer を作成して、OpenTelemetry 指標を Prometheus 形式に変換し、HttpServer 経由で公開します。次のコード スニペットは、新しい Prometheus Exporter を作成します。

// Default prometheus port i.e `prometheusPort` has been initialized to 9465
 
PrometheusHttpServer prometheusExporter = PrometheusHttpServer.builder()
        .setPort(prometheusPort)         
        .build();

OpenTelemetry SDK インスタンスを作成する

上記の prometheusExporter を MetricReader として登録し、SdkMeterProvider から指標を読み取ります。SdkMeterProvider は、指標設定の構成に使用されます。

SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder()
        .registerMetricReader(prometheusExporter)
        .build();

上記で作成した sdkMeterProvider を使用して、OpenTelemetry の SDK 実装用の OpenTelemetrySdk のインスタンスを作成します。

OpenTelemetrySdk openTelemetrySdk =OpenTelemetrySdk.builder()
        .setMeterProvider(sdkMeterProvider)
        .build();

GrpcOpenTelemetry インスタンスを作成する

GrpcOpenTelemetry API を使用して、Prometheus 指標エクスポータを使用する OpenTelemetry SDK を設定します。

GrpcOpenTelemetry grpcOpenTelmetry = GrpcOpenTelemetry.newBuilder()
        .sdk(openTelemetrySdk)
        .build();

// Registers gRPC OpenTelemetry globally.
grpcOpenTelmetry.registerGlobal();

GrpcOpenTelemetry インスタンスが registerGlobal を使用してグローバルに登録されると、その後作成されるすべての gRPC クライアントとサーバーが OpenTelemetry で計測されます。

OpenTelemetry Sdk をシャットダウンする

シャットダウンは ShutDownHook 内で行う必要があります。openTelemetrySdk.close() は SDK をシャットダウンし、SdkMeterProvider でシャットダウンも呼び出します。

サーバーで計測を設定する

同様に、GrpcOpenTelemetry をサーバーにも追加しましょう。codelabs/grpc-java-opentelemetry/start_here/src/main/java/io/grpc/codelabs/opentelemetry/OpenTelemetryServer.java を開き、GrpcOpenTelemetry を初期化するコードを追加します。

Prometheus エクスポータを作成する

この Codelab は同じマシンから実行される可能性があるため、PrometheusHttpServer の作成時にポートの競合を回避するために、gRPC サーバーサイド指標をホストする別のポートを使用しています。

// Default prometheus port i.e `prometheusPort` has been set to 9464

PrometheusHttpServer prometheusExporter = PrometheusHttpServer.builder()
        .setPort(prometheusPort)
        .build();

OpenTelemetry SDK インスタンスを作成する

SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder()
        .registerMetricReader(prometheusExporter)
        .build();

OpenTelemetry SDK を使用して GrpcOpenTelemetry を初期化する

OpenTelemetrySdk openTelemetrySdk =OpenTelemetrySdk.builder()
        .setMeterProvider(sdkMeterProvider)
        .build();

GrpcOpenTelemetry インスタンスを作成する

GrpcOpenTelemetry grpcOpenTelmetry = GrpcOpenTelemetry.newBuilder()
        .sdk(openTelemetrySdk)
        .build();
    // Registers gRPC OpenTelemetry globally.
grpcOpenTelmetry.registerGlobal();

OpenTelemetry Sdk をシャットダウンする

gRPC チャネルがシャットダウンされた後。openTelemetrySdk.close() を呼び出すと、SDK がシャットダウンされ、SdkMeterProvider のシャットダウンも呼び出されます。

4. 例を実行して指標を表示する

サーバーを実行するには、次のコマンドを実行します。

cd start_here
../gradlew installDist
./build/install/start_here/bin/opentelemetry-server

設定が正常に完了すると、サーバーに次の出力が表示されます。

[date and time] io.grpc.codelabs.opentelemetry.OpenTelemetryServer start
INFO: Server started, listening on 50051

サーバーが実行されている間に、別のターミナルでクライアントを実行します。

./build/install/start_here/bin/opentelemetry-client world

実行が成功すると、次のようになります。

[date and time]io.grpc.codelabs.opentelemetry.OpenTelemetryClient greet
INFO: Greeting: Hello world 
[date and time] io.grpc.codelabs.opentelemetry.OpenTelemetryClient greet
INFO: Will try to greet world ...
[date and time]io.grpc.codelabs.opentelemetry.OpenTelemetryClient greet
INFO: Greeting: Hello world

gRPC OpenTelemetry プラグインは、Prometheus を使用して指標をエクスポートするように設定されているためです。これらの指標は、サーバーの場合は localhost:9464、クライアントの場合は localhost:9465 で確認できます。

クライアント指標を表示するには -

curl localhost:9465/metrics

結果は次の形式になります。

# HELP grpc_client_attempt_duration_seconds Time taken to complete a client call attempt
# TYPE grpc_client_attempt_duration_seconds histogram
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.002"} 0
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.003"} 2
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.004"} 14
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.005"} 29
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.1"} 33
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="+Inf"} 34
grpc_client_attempt_duration_seconds_count{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 34
grpc_client_attempt_duration_seconds_sum{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 0.46512665300000006
# HELP grpc_client_attempt_rcvd_total_compressed_message_size_bytes Compressed message bytes received per call attempt
# TYPE grpc_client_attempt_rcvd_total_compressed_message_size_bytes histogram
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.0"} 0
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_sum{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 442.0
# HELP grpc_client_attempt_sent_total_compressed_message_size_bytes Compressed message bytes sent per client call attempt
# TYPE grpc_client_attempt_sent_total_compressed_message_size_bytes histogram
grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.0"} 0
grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="1024.0"} 34
grpc_client_attempt_sent_total_compressed_message_size_bytes_sum{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 238.0
# HELP grpc_client_attempt_started_total Number of client call attempts started
# TYPE grpc_client_attempt_started_total counter
grpc_client_attempt_started_total{grpc_method="helloworld.Greeter/SayHello",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 34.0
# HELP grpc_client_call_duration_seconds Time taken by gRPC to complete an RPC from application's perspective
# TYPE grpc_client_call_duration_seconds histogram
grpc_client_call_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.0"} 0
grpc_client_call_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.003"} 2
grpc_client_call_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="+Inf"} 34
grpc_client_call_duration_seconds_count{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 34
grpc_client_call_duration_seconds_sum{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 0.512708707
# TYPE target_info gauge
target_info{service_name="unknown_service:java",telemetry_sdk_language="java",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="1.40.0"} 1

同様に、サーバーサイドの指標の場合 -

curl localhost:9464/metrics

5. Prometheus で指標を表示する

ここでは、prometheus を使用して指標をエクスポートする gRPC サンプル クライアントとサーバーをスクレイピングする prometheus インスタンスを設定します。

ご利用のプラットフォーム用の Prometheus の最新リリースをダウンロードし、展開して実行します。

tar xvfz prometheus-*.tar.gz
cd prometheus-*

次の内容で Prometheus 構成ファイルを作成します。

cat > grpc_otel_java_prometheus.yml <<EOF
scrape_configs:
  - job_name: "prometheus"
    scrape_interval: 5s
    static_configs:
      - targets: ["localhost:9090"]
  - job_name: "grpc-otel-java"
    scrape_interval: 5s
    static_configs:
      - targets: ["localhost:9464", "localhost:9465"]
EOF

新しい構成で Prometheus を起動します。

./prometheus --config.file=grpc_otel_java_prometheus.yml

これにより、クライアントとサーバーの Codelab プロセスから 5 秒ごとに指標がスクレイピングされるように構成されます。

http://localhost:9090/graph に移動して、指標を表示します。たとえば、次のクエリがあるとします。

histogram_quantile(0.5, rate(grpc_client_attempt_duration_seconds_bucket[1m]))

このクエリは、分位値の計算に 1 分間のタイム ウィンドウを使用して、試行レイテンシの中央値を示すグラフを表示します。

クエリのレート -

increase(grpc_client_attempt_duration_seconds_bucket[1m])

6. (省略可)ユーザー向けの演習

Prometheus ダッシュボードで、QPS が低いことがわかります。QPS を制限している疑わしいコードを特定できるかどうか、サンプルで確認してください。

熱心なユーザーのために、クライアント コードは、特定の時点で保留中の RPC を 1 つだけにするように制限されています。これを変更して、クライアントが前の RPC の完了を待たずに、より多くの RPC を送信するようにできます。(この解決策は提供されていません)。