TT Lab
はじめる
学ぶ 学習パス コース

OTCA — OpenTelemetry認定アソシエイト

ライブラリは API しか知らず、アプリケーションが SDK を差し込む

TT Labで続きを見る

一言でいうと

OpenTelemetryのクライアントは、シグナルごとに、API・SDK・セマンティック規約・Contribの4種類のパッケージに分かれます。計装コードはAPIだけに依存し、アプリケーションの所有者がSDKをインストールして、プロバイダー・プロセッサー・エクスポーターを組み立てます。この分離(composability)がなぜ必要なのか、SDKのパイプラインがどうつながるのか、そしてコードを変更しないエージェントが同じAPI・SDKをどう組み込むのかを、スペック概要とクライアント設計原則に沿って説明します。

なぜ必要なのか

計装は、ドキュメントの表現を借りれば「横断的関心事(cross-cutting concern)」です。Webフレームワーク、DBクライアント、メッセージキューのライブラリの中に、観測のコードが混ざり込みます。しかし、そのライブラリを使うアプリケーションが、どのバックエンドにテレメトリを送るのか、あるいはまったく送らないのかは、ライブラリの作者にはわかりません。ライブラリが特定の実装を引き込むと、それを望まないアプリケーションまで重くなります。

設計原則のドキュメントは、そのため3つを求めています。APIは実装と明確に分離されている必要があり、サードパーティのライブラリはAPIだけに依存する必要があり、最終的なアプリケーション開発者が、SDKをどう設定するか、あるいはまったく使わないかを決められる必要があります。そのために、APIとSDKは独立したアーティファクトとして提供されなければならない(MUST)と書かれています。

どう動くのか

APIの最小実装

APIパッケージは、それ自体で完結します。SDKがなくてもアプリケーションがビルドされて実行される必要があるため、API内に最小実装(no-op)が含まれています。ドキュメントは、この最小実装の返り値が有効でなければならないと強調しています。createSpan()は失敗せず、nullではないSpanを返す必要があり、呼び出し側は、いま最小実装が動いているかどうかを気にしなくてよい必要があります。そして、性能への負担がほとんどあってはなりません。これが、「計装されたライブラリを、OpenTelemetryを使わないアプリケーションでも使える」という要件を満たす方法であり、フレームワークが「計装版」と「非計装版」を別々に出す必要をなくします。

計装の作者に向けたルールは断固としていて、「計装の作者は、どんな種類のSDKパッケージも直接参照してはならない(MUST NOT)。APIだけを参照する」と定めています。

SDKの内側: プロバイダーからエクスポーターまで

SDKがインストールされると、最小実装を置き換えます。SDKはさらに2つに分かれます。プロトコルと無関係な共通ロジック(バッチ処理、プロセス情報の付与など)と、プロトコルに結び付いたエクスポーターです。エクスポーターは最小限の機能しか持たず、ベンダーが自分のプロトコルを簡単に追加できるようにします。スペックがSDKに求めるデフォルトのエクスポーターは、OTLP(ログ・メトリクス・トレース)、標準出力、インメモリ(テスト用)で、メトリクスにはPrometheus、トレースにはZipkinが加わります。ベンダー専用のエクスポーターは、クライアントに含めません。

組み立ての骨格は、シグナルごとに同じ形です。

シグナル プロバイダー 作るもの 記録の単位 プロセッサー → エクスポーター
トレース TracerProvider Tracer Span SpanProcessor → SpanExporter
メトリクス MeterProvider Meter → Instrument Measurement 集計状態 → MetricReader/Exporter
ログ LoggerProvider Logger LogRecord LogRecordProcessor → LogRecordExporter

トレースSDKのスペックによると、TracerProviderを作るときに、SpanProcessor、IdGenerator、SpanLimits、Samplerを設定します。デフォルトのサンプラーはParentBased(root=AlwaysOn)です。TracerProviderのShutdownは、登録されたすべてのプロセッサーのShutdownを呼び出し、ForceFlushも同様に伝わります。

プロセッサーチェーン

SpanProcessorは、スパンのライフサイクルのフックです。スパンが開始するときのOnStart、終了の直前のOnEnding、終了後のOnEnd、そしてShutdown・ForceFlushです。登録された順に呼び出され、すべてのプロセッサーのOnEndingが終わってからはじめて、OnEndが始まります。組み込みのプロセッサーは2つです。

Tracer ──► Span(끝) ──► [SpanProcessor 1] ──► [SpanProcessor 2: Batching] ──► SpanExporter(OTLP)
                              │ OnStart/OnEnding/OnEnd 훅

ログSDKも同じ構造です。LoggerProviderにLogRecordProcessorを登録し、SimpleまたはBatchingプロセッサーがLogRecordExporter(例: OTLP)へ渡します。組み込みのプロセッサーが「バッチ処理と変換」を担うと、スペックに書かれています。

エージェント: コードを変更せずに同じものを組み込む

ゼロコード計装の概念は、エージェントが行うことを、「OpenTelemetryのAPIとSDKの能力をアプリケーションに加えること」と定義しています。方法は言語によって異なります。バイトコード操作、モンキーパッチ、eBPFです。計装されるのは、皆さんが使うライブラリ(リクエスト・レスポンス、DB呼び出し、メッセージキュー)であって、皆さんのコードではなく、自分のコードを計装するには、コードベースの計装が必要です。設定は環境変数と言語ごとの手段で行い、始めるにはサービス名だけあれば十分です。

Javaエージェントは、opentelemetry-javaagent.jar1つです。Java 8以上のJVMに-javaagent:path/to/opentelemetry-javaagent.jarを付けると、バイトコードを動的に注入して、多くのライブラリからテレメトリを取得します。設定は、-Dotel.service.name=...のようなシステムプロパティ、OTEL_SERVICE_NAME・OTEL_TRACES_EXPORTERのような環境変数、JAVA_TOOL_OPTIONS、またはotel.javaagent.configuration-fileで指定したプロパティファイルの、どれでも行えます。

Pythonはモンキーパッチです。pip install opentelemetry-distro opentelemetry-exporter-otlpでAPI・SDKと2つのツールを取得し、opentelemetry-bootstrap -a installがsite-packagesを調べて、インストール済みのパッケージに合う計装ライブラリ(例: flask → opentelemetry-instrumentation-flask)を選んでインストールします。実行はopentelemetry-instrument python myapp.pyで、--traces_exporter console,otlpのような引数やOTEL_*環境変数で設定します。ドキュメントは、自動計装が動作するには、distroパッケージが必ず必要だと明言しています。

現場での姿

あるチームの社内HTTPクライアントライブラリが、SDKに直接依存していました。このライブラリを使うバッチジョブは、テレメトリを送る先がないのにOTLPエクスポーターが起動し、終了時にShutdownを待つため、終了が遅れていました。ライブラリをAPI依存に変えると、バッチジョブではno-opになり、Webサービスでは、アプリケーションが組み立てたSDKがそのまま使われました。

別のチームは、スパンが断続的に消える問題に悩まされていました。トラフィックが集中したときに、Batching processorのキュー(デフォルト2048)があふれて、スパンを捨てていたのです。キューを大きくすることと、maxExportBatchSize・scheduledDelayMillisを調整して出力の速度を上げることの間で、天秤にかける必要がありました。

次の記事で続くこと

続く記事では、メトリクスの計測器7種類とtemporality、ログレコードのフィールドとブリッジAPI、そしてスキーマURLを扱います。その次のクイズでは、APIとSDKを分離する理由、プロセッサーチェーン、Batching processorのデフォルト値、エージェントの動作の仕組みを確認します。