Helidon 4.x で 分散トレーシング (OpenTelemetry Java Agent は使わない編)

## はじめに

既に、[「Helidon を用いた MicroProfile Telemetry Tracing」](https://qiita.com/shukawam/items/2ea5a1aeff03901edd2b) という投稿がありますので、OpenTelemetry と Java のマイクロサービス規格である Eclipse MicroProfile に関する状況等についてはそちらの記事を参照していただいて、ここでは OpenTelemetry プロジェクトが提供する Java Agent ([OpenTelemetry Instrumentation for Java](https://github.com/open-telemetry/opentelemetry-java-instrumentation)) を使わずに [<INS>OpenTelemetry Java SDK</INS>](https://github.com/open-telemetry/opentelemetry-java) ベースで Heldion 4.x の分散トレーシングを設定し、Oracle Cloud Infrastructure (OCI) の Application Performance Monitoring (APM) と連携して分散トレーシングを行う方法や、さらには Jaeger や Zipkin 等の他の分散トレーシングにも対応できるような MicroProfile 流の設定の方法を紹介します。

## Helidon による 分散トレーシングのための設定

基本的な設定方法については、[<INS>Helidon のドキュメンテーション</INS>](https://helidon.io/docs/latest/mp/telemetry) に書いてありますので、そちらにまずは目を通していただきたいですが、やることは主に二つです。
1. pom.xml に 必要な dependency を追加する
1. `microprofile-config.properties` に設定情報を追加する

複数の分散トレーシングに対応させたいので、設定が切り替えられるようにしたいと思います。

## pom.xml に 必要な dependency を追加する

dependency の話をする前に、このアプリケーションは Virtual Thread ベースで書き直された最新マイクロサービス・フレームワークである **Heldion MP 4.x** です、お間違いなく！

```xml
    <parent>
        <groupId>io.helidon.applications</groupId>
        <artifactId>helidon-mp</artifactId>
        <version>4.0.2</version>
        <relativePath />
    </parent>
```

dependency についてですが、ソースコードで `@WithSpan` や `Span` などの `io.opentelemetry` 関連パッケージを使っている場合はコンパイル時に確実に必要ですし、任意の 分散トレーシング Exporter にも共通的に必要なので、まずこの dependency を追加します。

```xml
        <!-- support OpenTelemetry -->
        <dependency>
            <groupId>io.helidon.microprofile.telemetry</groupId>
            <artifactId>helidon-microprofile-telemetry</artifactId>
        </dependency>
```
この中には OpenTelemetry SDK関連のモジュールが含まれます。

各 Exporter 用の dependency については、全部そのまま追加してもいいのですが、不要なものは追加したくないので、Maven の profile の機能を使ってみます。

```xml
    <profiles>
        <!-- tracing option dependencies-->
        <profile>
            <!-- jaeger -->
            <id>tracing-jaeger</id>
            <!-- activation>
                <activeByDefault>true</activeByDefault>
            </activation -->
            <dependencies>
                <dependency>
                    <groupId>io.opentelemetry</groupId>
                    <artifactId>opentelemetry-exporter-jaeger</artifactId>  
                </dependency>
            </dependencies>
        </profile>
        <profile>
            <!-- zipkin -->
            <id>tracing-zipkin</id>
            <dependencies>
                <dependency>
                    <groupId>io.opentelemetry</groupId>
                    <artifactId>opentelemetry-exporter-zipkin</artifactId>  
                </dependency>
            </dependencies>
        </profile>
        <profile>
            <!-- otlp -->
            <id>tracing-otlp</id>
            <dependencies>
                <dependency>
                    <groupId>io.opentelemetry</groupId>
                    <artifactId>opentelemetry-exporter-otlp</artifactId>
                </dependency>
            </dependencies>
        </profile>
    </profiles>
```

こうすることによって、ビルド時に必要なモジュールを追加することができます。
```
# 例: -P オプションで profile を指定する
mvn clean package -P tracing-otlp -DskipTests
```
全部盛りにしたい場合は、以下のように記述すればOKです。
```
# profile は複数指定可
mvn clean package -P tracing-jaeger,tracing-zipkin,tracing-otlp -DskipTests
```

## microprofile-config.properties に設定情報を追加する

MicroProfile アプリケーションは設定情報を `microprofile-config.properties` ファイルに集めるのが基本ですが、Helidon の OpenTelemetry 実装も、この流儀を踏襲しています。

```properties
##### OpenTelemetry #####
otel.agent.present=false
otel.propagators=tracecontext,baggage,b3,b3multi
otel.service.name=helidon-tracing-demo

# set to true in order to enabe tracing
otel.sdk.disabled=true

# specify exporter - either otlp, jaeger or zipkin
otel.traces.exporter=none
```

この設定では、
1. otel.sdk.disabled=true なので、トレーシング機能は無効化されている
  (後で、この値を `false` にして有効化します)
1. otel.traces.exporter=none なので、Exporter が設定されていない
  (後で、この値を `otlp` or `zipkin` or `jaeger` に変更します)

という状態となっています。

`otel.propagators` はトレース情報の伝播に使うフォーマットを指定します。デフォルト値は `tracecontext,baggage` (W3C Trace Context/W3C Baggage) ですが、ここでは Zipkin や APMブラウザ・エージェントなどで使われる "X-B3-" ヘッダに対応するために対応フォーマットを追加 (`b3,b3multi`) しています（現在設定可能な値は他にも `jaeger` `xray` `ottrace` があります）。

`otel.traces.exporter` で Exporter を指定した際の設定は、現状何も指定していないのでデフォルト値です。Exporter の設定値をカスタマイズしたい場合は、追加の設定を行います。

```properties
# for otlp
otel.exporter.otlp.traces.protocol=grpc
otel.exporter.otlp.traces.endpoint=http://localhost:4317
otel.exporter.otlp.traces.timeout=10000

# for jaeger
otel.exporter.jaeger.endpoint=http://localhost:14250
otel.exporter.jaeger.timeout=10000

# for zipkin
otel.exporter.zipkin.endpoint=http://localhost:9411/api/v2/spans
otel.exporter.zipkin.timeout=10000
```

otlp = OpenTelemetry Protocol の設定値に関しては [<INS>OpenTelemetry SDK のドキュメンテーション</INS>](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#otlp-exporter-span-metric-and-log-exporters) を参照してください。

OCI APM と連携したい場合は、otlp を使いますが、その際の設定は以下のようになります。
```properties
otel.exporter.otlp.traces.protocol=http/protobuf
otel.exporter.otlp.traces.endpoint={{APM endpint}}/20200101/opentelemetry/private/v1/traces
otel.exporter.otlp.traces.headers=Authorization=dataKey {{data key}}
```
{{APM endpint}} や {{data key}} に関しては [<INS>OCI のドキュメンテーション</INS>](https://docs.oracle.com/ja-jp/iaas/application-performance-monitoring/doc/configure-open-source-tracing-systems.html#GUID-4D941163-F357-4839-8B06-688876D4C61F) に詳しい説明があります。

# 分散トレーシングを有効化する

直接 `microprofile-config.properties` を編集してもよいのですが、それでは静的な設定となってあまり面白くないので、分散トレーシングの有効化と Exporter の指定を、Helidon アプリケーションの起動時に動的に行えるようにしてみます。[<INS>MicroProfile Config</INS>](https://download.eclipse.org/microprofile/microprofile-config-3.1/microprofile-config-spec-3.1.html) 仕様に準拠したやり方で、Java の System Property か 環境変数で `microprofile-config.properties` の設定内容を上書きすることができます。

```
java -Dotel.sdk.disabled=false -Dotel.traces.exporter=zipkin -jar target/helidon-demo.jar
```
上記の例では、`otel.sdk.disabled=false` で分散トレーシングを有効化し、`otel.traces.exporter=zipkin` で Exporter に Zipkin を指定しています。

複数の設定項目をシステム・プロパティでいちいち指定するのは面倒かもしれませんが、幸い MicroProfile Config には「プロファイル」という機能があります。`microprofile-config-<プロファイル名>.properties` という設定ファイルを作ってパッケージしておけば、起動時に `-Dmp.config.profile=<プロファイル名>` という風にシステムプロパティを指定すると、そのプロファイル用の設定ファイルの値がデフォルトの設定ファイルの同じ項目を上書きしてくれます。複数の設定ファイルを作って複数のプロファイルを切り替えることも可能です。

また別の例として、OCI APM に分散トレーシング情報を送りたいアプリケーションを、コンテナにして Kubernetes にデプロイする場合は、マニフェストで以下の環境変数を設定すればOKです（ビルド時にコンテナの環境変数として埋め込んでしまってももちろんOKです）。
+ OTEL_SDK_DISABLED (= false)
+ OTEL_TRACES_EXPORTER (= otlp)
+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (= APM endpoint)
+ OTEL_EXPORTER_OTLP_TRACES_HEADERS (= Authorization=dataKey ...)

各 Exporter をデフォルトの設定値のままで動作確認したい場合は、Helidon アプリケーションを動作させている同じホスト上で、Jaeger コンテナを動かして下さい。以下のサンプルで、すべての Exporter に対応できます。

```bash
#!/bin/bash

docker run -d --rm --name jaeger \
  -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
  -e COLLECTOR_OTLP_ENABLED=true \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 4317:4317 \
  -p 4318:4318 \
  -p 14250:14250 \
  -p 14268:14268 \
  -p 14269:14269 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.51
```
16686 番ポートが UI です。

# まとめ

Helidon 4.x は MicroProfile Telemetry Tracing に準拠していて、若干扱いが面倒な MicroProfile Telemetry Java Agent を使わなくても設定レベルで各種分散トレーシングに対応することができます。
さらに、OpenTelemetry に対応したことで、OCI APM をはじめとした多くの分散トレーシングのソリューションに汎用的に対応することができるようになりました。
Java の最新技術である Virtual Thread で動作するハイ・パフォーマンスな Helidon 4.x をこの機会に是非一度試してみてください！

参考

+ Helidon 4.x Documentation
https://helidon.io/docs/latest/

+ OpenTelemetry Java SDK (GitHub)
https://github.com/open-telemetry/opentelemetry-java/

+ Oracle Cloud Infrastructure Document - Application Performance Monitoring
https://docs.oracle.com/ja-jp/iaas/application-performance-monitoring/