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 -b v1 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 エクスポータを作成します。

// 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();

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

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

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

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

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

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

registerGlobal を使用して GrpcOpenTelemetry インスタンスがグローバルに登録されると、以降に作成されるすべての 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

Prometheus を使用して指標をエクスポートするように gRPC OpenTelemetry プラグインを設定しました。これらの指標は、サーバーの場合は 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 を送信できます。(このソリューションは提供されていません)。