OTLPはストレージではなくプロトコルだ
一言でいうと
OpenTelemetryは、テレメトリを作って送る方法を標準化したプロジェクトです。保存したり表示したりすることはしません。OTLPはその転送プロトコルで、バックエンドはTempoでもJaegerでもClickHouseでも、皆さんが選びます。この境界を先に押さえておくと、「OpenTelemetryを入れればダッシュボードができますか」という誤解がなくなります。
なぜ必要なのか
計装ライブラリがベンダーごとに違っていた時代には、バックエンドを変えることがそのまま全サービスの再計装を意味しました。コードの中に特定ベンダーのSDK呼び出しが埋め込まれていたからです。OpenTelemetryはその部分を切り離しました。アプリケーションはOTel APIだけで話し、どこへ送るかはSDKの設定やコレクターの設定で決めます。そのため、バックエンドの入れ替えがコードレビューではなく設定変更になります。
どう動くのか
4つのシグナルがそれぞれ別の問いに答えます。
| シグナル | 答える問い | 代表的な限界 |
|---|---|---|
| Traces | このリクエストがどこで時間を使ったか | いつからか、何%かは見えません |
| Metrics | いつから、どれくらい悪くなったか | 個々のリクエストを復元できません |
| Logs | その中でなぜそうなったか | サービス間の相関は自分でつなぐ必要があります |
| Profiles | その時間をどのコードが使ったか | 最後に標準化されたシグナルです |
トレースは「どこ」に、メトリクスは「いつ、どれくらい」に、ログは「なぜ」に答えます。3つともそろってはじめて、調査が最後まで進みます。トレースからtrace IDをコピーしてログ検索に入れたとき、そのリクエストのログが出てくるか。これが、計装がつながっているかを確認する最も安上がりなテストです。
構成要素は3つの層で捉えます。
- API: アプリケーションコードが呼び出すインターフェースです。SDKがなければ何もしないno-opです。ライブラリの作者がAPIだけに依存して計装を入れられる理由です。
- SDK: APIの実装です。サンプラー、プロセッサー(バッチ)、エクスポーター、リソースを持ちます。
- Collector: アプリの外にある中継プロセスです。収集・加工・ルーティングを担当します。必須ではありませんが、ほとんどの場合は置きます。
OTLPは、これらの間を流れるプロトコルです。転送方式は2つあり、初心者が最もつまずく落とし穴がここにあります。
| 方式 | デフォルトポート | OTEL_EXPORTER_OTLP_PROTOCOL |
|---|---|---|
| gRPC | 4317 | grpc |
| HTTP/protobuf | 4318 | http/protobuf |
ポートとプロトコルが食い違うと、接続そのものが失敗し、エラーはアプリケーションのログにだけ残ります。コレクター側のメトリクスは0のまま動かないため、「コレクターが受け取っていない」と誤診しやすくなります。データがまったく入ってこないときに、最初に確認するのがこの組み合わせです。
エンドポイントの指定にもルールがあります。OTEL_EXPORTER_OTLP_ENDPOINTは全シグナル共通のベースアドレスで、HTTPを使うときはSDKが/v1/tracesのようなパスを付け足します。シグナルごとに別の宛先を使いたい場合は、OTEL_EXPORTER_OTLP_TRACES_ENDPOINTのようなシグナル別の変数を使いますが、この場合はパスまで含めた全体を書く必要があります。
現場での姿
コレクターのディストリビューションが2種類あることも、実務でよくつまずく原因になります。Coreは中核のコンポーネントだけを含むので小さく、Contribはコミュニティのコンポーネントを大量に含むのでずっと大きくなります。ドキュメントやブログで見たコンポーネント名をそのまま貼り付けたら、コレクターが「不明なタイプ」だと言って停止することがよくあります。このとき、ほとんどの人は設定の打ち間違いをまず疑います。実際の原因は、ディストリビューションが違う場合のほうがずっと多いです。otelcol componentsで、いまのバイナリに何が入っているか、各コンポーネントの安定性のレベルが何かを先に確認する習慣が必要です。
また、Jaeger v2は内部的にOpenTelemetry Collectorをベースに再設計されており、OTLPをネイティブに受け取れます。Zipkinエクスポーターは非推奨の状態なので、新規プロジェクトで新たに導入する理由はありません。
計装を実際に導入するときに決めること
OpenTelemetryは概念が広いため、学んだあとで実際に導入しようとすると、どこから手を付けるかで行き詰まります。順序があります。
まず自動計装を有効にします。ほとんどの言語にエージェントやライブラリがあり、コードを変更しなくても、HTTPのサーバーとクライアント、DBドライバーのスパンができます。ここまででも「どのリクエストが遅いか」に答えられ、これがオブザーバビリティの8割です。
次に手動でスパンを足します。自動計装が知らないのは、私たちの業務ロジックです。意味のある単位(注文の検証、在庫の確認、精算の計算)にだけスパンを置き、関数ごとには入れません。スパンが増えると読みにくくなり、コストも増えます。
属性名は規約に従います。http.request.method、db.system、service.nameのように決められた名前を使うと、ツールが自動で画面を作ってくれます。自分で付けた名前は、自分たちのダッシュボードでしか意味が通じません。社内独自の属性にはプレフィックスを付けて区別します。
伝播(propagation)を確認します。サービスをまたぐときにトレースが途切れるのが、最もよくある問題です。traceparentヘッダーがプロキシ・キュー・バッチを通過しているかを確認する必要があります。メッセージキューでは、ヘッダーを自分で入れたり取り出したりするコードを書く必要があることが多いです。
サンプリングは最初から決めておきます。全部送るとコストを賄いきれず、無作為に減らすと肝心の遅いリクエストが残りません。テールサンプリング(リクエストが終わったあとで、遅いものやエラーのものを選んで残す方式)が答えですが、コレクター側のメモリを使います。出発点は「エラーと遅いものはすべて、残りは1%」程度に置きます。
コレクターを間に挟みます。アプリケーションがバックエンドへ直接送ると、バックエンドを変えるときにすべて再デプロイする必要があります。コレクターを置けば、宛先の変更・フィルタリング・リトライがその1か所で完結します。
次の確認で見ること
このモジュールは概念モジュールなので、ラボはありません。クイズでシグナルの境界とOTLPの規約を確認したあと、次のモジュールでSDKの環境変数とリソース属性を実際に書き、KubernetesのDownward APIでインスタンス識別子を注入してみます。