kmuto’s blog

はてな社でMackerel CREをやっています。料理と旅行といろんなIT技術

stableになったOpenTelemetryの宣言的設定を眺めていた

OpenTelemetryの宣言的設定仕様が「stable(安定版)」になったとのことで、今後どう設定が変わっていきそうなのかを調べていた。

opentelemetry.io

アプリケーションにおいては、OpenTelemetryコレクターを介さないかあるいはコレクターに送る前に自前で設定するときには関わってくるかな、という印象だ。

宣言的設定とは

「宣言的(Declarative)設定」は、「どうあるべきか」という最終状態を指示する設定方法だ。現状と理想状態の間を埋める手順は処理系が行う。KubernetesやTerraformなどが代表的だが、OpenTelemetryコレクターの設定も宣言的である。

なお、宣言的設定の反対は「命令的(Imperative)設定」で、手続き的なシェルスクリプトや、コード内での順次実行がこれに相当する。

ある期待動作に対してどう実装するかは言語によって大きく異なるが、OpenTelemetryの宣言的設定であれば、SDKやアプリケーションの計装設定を共通の言葉で、かつ「どうあるべきか」を書いておくだけで済むようになる(はず)。

これまでもコードにベタ書きせずに環境変数で変更できる要素(たとえば投稿エンドポイント設定)はあったが、階層構造で複雑に表現するには無理があった。これが構造化データのモデルで表現可能になり、YAMLファイルで定義できる。

宣言的設定の内容にはConfigProvider APIを介してアクセスする。

宣言的設定の例

examplesフォルダに宣言的設定のサンプルとしてotel-getting-started.yaml、otel-sdk-migration-config.yaml、otel-sdk-config.yamlが用意されている。

file_format: "1.0"

resource:
  # Read resource attributes from the OTEL_RESOURCE_ATTRIBUTES environment variable.
  # This aligns well with the OpenTelemetry Operator and other deployment methods.
  attributes_list: ${OTEL_RESOURCE_ATTRIBUTES}
  detection/development: # /development properties may not be supported in all SDKs
    detectors:
      - service: # will add "service.instance.id" and "service.name" from the OTEL_SERVICE_NAME env var
      - host:
      - process:
      - container:

propagator:
  composite:
    - tracecontext:
    - baggage:

# Read backend endpoint from the OTEL_EXPORTER_OTLP_ENDPOINT environment variable.
# This aligns well with the OpenTelemetry Operator and other deployment methods.

tracer_provider:
  sampler:
    parent_based:
      root:
        always_on:
  processors:
    - batch:
        exporter:
          otlp_http:
            endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/traces
...
  • 構造化された意思表示:「リソースは属性群を持ち、自動検出にはこれを使う」「サンプラーはペアレントベースで、ルートならAlwaysOn。かつプロセッサはバッチ型で、エクスポートとしてはotlp_httpに送り、エンドポイントはこれ」といった、システムのあるべき姿を階層的に記述している
  • 環境変数の明示利用${環境変数}で環境変数を取り込むことになっている。逆にこうしないと、これまでのSDK向けに暗黙に採用される環境変数でさえ無視される。endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/tracesにあるように「変数値が無ならデフォルト値」の設定も可能

スキーマとしてはopentelemetry_configuration.jsonで定義されており、デフォルト値設定などのYAMLで可能なテクニックはConfiguration Data Modelにまとめられている。

file_formatはサンプルではどれも1.0

アプリケーションに設定ファイルを引き渡すには、環境変数OTEL_CONFIG_FILEにそのファイルのパスを指定する。YAMLのマージ機能はないので、指定できるのは単一のファイルとなる。バリエーションを付けたいときには環境変数で制御するか、呼び出し前にマージするかといった仕組みを別途カスタムに作ることになるだろう。

Javaで宣言的設定を試す

現在はまだ「宣言的設定の仕様が固まった」という段階であり、各言語のそれぞれのSDKの対応状況はまちまちだ。

実装として挙げられているのはC++、Go、Java、JavaScript、PHPで、.NETとPythonが検討中となっている。

その中でJavaはかなり要求を満たしており、先んじて試す上で最適そうだ(仕様が固まっただけで、実装はまだ大きく変わるかもしれないが)。現時点最新のJava Agent 2.26.0で実験してみよう。

実験台にはmackerelio/handson-get-start-apmのSpringBootアプリケーションを使ってみた。

git clone https://github.com/mackerelio/handson-get-start-apm.git
cd handson-get-start-apm
./gradlew build

設定はotel-sdk-config.yamlをまずそのまま使ってみる。

環境変数OTEL_CONFIG_FILEにパス(または-Dotel.experimental.config.file=パス)を渡して実行する。

OTEL_CONFIG_FILE=./otel-sdk-config.yaml java -javaagent:opentelemetry-javaagent.jar -jar build/libs/demo-0.0.1-SNAPSHOT.jar

シグナルを受け取るoteltuiを起動した上で、localhost:8080に接続。10回リロードした。

次に、以下のようにotel-sdk-config.yamlのリソースのサービス名と確率サンプリングレートを変更した。

...
resource:
  attributes:
    - name: service.name
      value: declarative-config-java
...
tracer_provider
...
  sampler:
    parent_based:
      root:
        trace_id_ratio_based:
          ratio: 0.1
...

同じくリロードを10回繰り返す。サービス名が変更されており、トレースについて0.1=10%の確率サンプリングが効いてそうだ。

インメモリにある設定内容を操作できると何か面白そうなことができるだろうか?と考えたが、SDKで設定可能な項目は限られている。

動的に確率サンプリングを変更する、バックエンドの負荷や予算に応じた調整を行う、といったあたりだろうか。ただそのためにコーディングするかというと微妙そうではある。Java Agentが何らかのSIGNALを受け付けてホットリロードするほうが望まれそうだ。

まとめと感想

環境変数を多数設定する地獄になるよりは、共通のスキーマにのっとってファイルで指定できるのは便利そうではあった。

運用で考えると、フィルタリングやテールサンプリングなどOpenTelemetryコレクターでしかできないこともあるため、完全な代替にはならない。

「そのままコレクターに送るには大きすぎるので、アプリケーション側でもある程度は制限や整頓しておきたい」という場面であれば活用できるかなと思った。