OpenTelemetry のデータモデルとパイプライン
計装をベンダーロックインから解放する仕組みを原理から理解。SDKからCollectorへ流れるシグナルの共通リソースモデルと、receiver/processor/exporterの3段パイプラインを構成図のように追えます。
- 1.OpenTelemetryはAPI/SDK(生成)とCollector(収集・加工・転送)に分離され、計装コードとバックエンドの依存を切り離す。
- 2.traces/metrics/logsは別シグナルだが、共通のResource(生成元の属性)とSemantic Conventions(属性の命名規約)を共有し、後段で相関できる。
- 3.Collectorは receiver→processor→exporter のパイプラインで、protobuf定義のOTLPを内部表現(pdata)に変換して加工し、複数バックエンドへ扇形に送る。
OpenTelemetry(OTel)は「計装の標準化」と言われますが、上級者が押さえるべきはそのデータモデルとパイプラインの分離設計です。なぜ計装コードを書き換えずにバックエンドを差し替えられるのか、なぜ別物に見える traces/metrics/logs を相関できるのか。本稿はこの2点を内部構造から説明します。三本柱の情報理論的な意味は/devops/observability-pillars-internals/を前提とします。
全体像:生成と収集の二層分離
OTelは責務を2層に切り分けます。
- API/SDK 層(プロセス内):アプリケーションコードが span・メトリクス・ログレコードを生成する。APIは型と関数の定義だけ、SDKがサンプリングやバッチ化など実装を担う。
- Collector 層(プロセス外):生成済みデータを受け取り、加工し、1つ以上のバックエンド(Jaeger、Prometheus、Loki など)へ転送する独立プロセス。
この分離が肝心です。アプリはOTLP(OpenTelemetry Protocol)でCollectorに吐くことだけを知り、最終的な保存先を知りません。バックエンドを差し替えてもアプリの計装コードは無傷です。これが/devops/distributed-tracing/で「事実上の標準」とされる理由の中核です。
[アプリ + SDK] --OTLP(gRPC/HTTP)--> [Collector] --各種exporter--> [複数バックエンド]
生成(in-process) 収集・加工・転送 保存・可視化
共通データモデル:Resource とシグナル
3シグナルが「別パイプラインなのに相関できる」理由は、共通の土台を共有しているからです。
Resource:誰が出したかを表す不変属性
Resource は、テレメトリの生成元を表す属性集合です。service.name、service.version、host.name、k8s.pod.name などのキーと値を持ち、そのプロセスから出る全シグナルに共通で付与されます。traces も metrics も logs も同じ Resource を共有するため、後段で「同じ pod の同じ service が出したログとトレース」を突き合わせられます。
Semantic Conventions:属性の命名規約
属性キーをチームごとに自由に決めると相関できません。OTelはSemantic Conventions(セマンティック規約)で属性名を標準化します。HTTPサーバーのspanなら http.request.method、http.response.status_code、url.path のように、意味と型をスペックで固定する。これによりバックエンドやダッシュボードが「どのキーがHTTPメソッドか」を機種非依存で解釈できます。規約は安定版と実験版に分かれ、http.method から http.request.method への改名のように版を追って整理されてきました。
同じ「属性」でも層が違います。Resource属性は「生成元(service/host/pod)」を表し、プロセス内で不変。Span属性やLogRecord属性は「そのイベント固有」の値(リクエストパス、ステータス)。相関の主キーは前者のResourceと、後述の trace_id の二系統で張られます。
3シグナルの構造
| シグナル | 中核データ構造 | 時間の扱い |
|---|---|---|
| traces | Spanの木(trace_id/span_id/parent_span_id、開始・終了時刻、status、events) | 区間(開始〜終了の2点) |
| metrics | Sum/Gauge/Histogram等のデータポイント列(属性付き、累積 or 差分のtemporality) | 時刻つきの数値点 |
| logs | LogRecord(timestamp、severity、body、属性、任意のtrace_id/span_id) | 単一時刻のイベント |
重要なのは、logs の LogRecord が任意で trace_id/span_id を持てる点です。これにより、ログを発生元のspanへ直接ぶら下げられ、三本柱の相関が「時刻の近さ」ではなく共有キーで成立します。
OTLP:シグナルを運ぶ共通ワイヤ形式
SDKとCollectorの間、Collectorとバックエンドの間を流れるのがOTLPです。OTLPはProtocol Buffersでスキーマが定義され、gRPC(既定)またはHTTP/protobufで転送されます。
OTLPの構造は3シグナルで共通の入れ子になっています。トレースなら次の階層です。
ResourceSpans # ある Resource(service等)に属する
└ ScopeSpans # ある計装スコープ(ライブラリ名/版)に属する
└ Span[] # 実際の span 群
metrics は ResourceMetrics → ScopeMetrics → Metric[]、logs は ResourceLogs → ScopeLogs → LogRecord[] と同じ3段構造を取ります。最上位が必ず Resource でくくられているため、転送単位の時点で「どの生成元のデータか」が保たれます。SDKは内部でこのバッチを組み立て、BatchProcessor 相当の仕組みで一定件数・一定間隔ごとにまとめて送出します(1イベント1送信ではない)。
ResourceとSpanの間にある ScopeSpans の「スコープ」は、そのテレメトリを生成した計装ライブラリの名前と版です。io.opentelemetry.http-client のように識別され、どのインストルメンテーションが出力したかを後から切り分けられます。バグのあるライブラリ版からのデータだけ除外する、といった運用に効きます。
Collector の内部:3段パイプライン
Collectorの本体は receiver → processor → exporter の3段パイプラインです。各段はプラグインで、設定ファイルで組み替えます。
- receiver(受信):データを取り込む入口。OTLP receiver のほか、Prometheusのスクレイプ、Jaeger/Zipkin形式、ホストメトリクスなど多様な入力を受ける。受信した時点で内部表現に変換される。
- processor(加工):パイプライン上で順番に適用される変換。バッチ化(batch)、メモリ保護(memory_limiter)、属性の追加・削除(attributes)、リソース付与、フィルタ、そして全スパンをバッファしてから採否を決めるテールサンプリング(tail_sampling)などがある。順序が意味を持つ点が重要。
- exporter(送出):加工後データを外部へ出す出口。OTLP exporter、Prometheus exporter(リモートライト/スクレイプ)、各種ベンダー向けなど。1つのパイプラインから複数exporterへ扇形に複製でき、同じトレースをJaegerと長期ストレージへ同時送出できる。
# Collector 設定の骨子(パイプラインは signal ごとに定義)
receivers:
otlp: { protocols: { grpc: {}, http: {} } }
processors:
memory_limiter: {}
batch: {}
exporters:
otlp/tempo: { endpoint: "tempo:4317" }
prometheusremotewrite: { endpoint: "http://mimir/api/v1/push" }
service:
pipelines:
traces: { receivers: [otlp], processors: [memory_limiter, batch], exporters: [otlp/tempo] }
metrics: { receivers: [otlp], processors: [memory_limiter, batch], exporters: [prometheusremotewrite] }
パイプラインはシグナルごとに独立して定義します(traces 用、metrics 用、logs 用)。receiver と exporter はシグナルをまたいで共有できますが、processor の適用列はパイプラインごとに別です。
pdata:内部表現と変換コスト
Collectorは受け取ったOTLPを、内部で pdata と呼ばれる正規化表現に変換し、processor はこの pdata を操作します。exporter で再び目的の形式へ直列化します。つまりCollectorの実体は「任意の入力形式 → pdata → 任意の出力形式」という変換ハブです。これが、Prometheusで集めてOTLPで送る、といった異種接続を可能にします。一方で、形式変換と pdata のアロケーションは無視できないCPU・メモリ負荷で、memory_limiter による背圧と batch によるまとめ送りが運用上ほぼ必須になります。
processor はパイプライン上で記述順に適用されます。例えば memory_limiter は batch より前に置き、過負荷時に早く落とすのが定石です。テールサンプリングを attributes 加工の後に置くか前に置くかで、サンプリング判断に使える属性が変わります。「同じ部品でも並び順で挙動が変わる」のは、各 processor が pdata を逐次変換する設計の必然です。
デプロイ形態:Agent と Gateway
Collectorは配置で2形態に大別されます。
| 形態 | 配置 | 主な役割 |
|---|---|---|
| Agent(sidecar/daemonset) | 各ホスト・各Podの近く | 低遅延の受信、ホスト属性付与、一次バッチ |
| Gateway(中央集約) | クラスタ単位の集約層 | テールサンプリング、集約、バックエンド振り分け |
テールサンプリングは「1トレースの全スパンが同じCollectorインスタンスに集まる」必要があるため、ロードバランシングexporterでtrace_id単位に寄せた上でGateway層に置く、という構成が定番です。Agentだけではスパンが分散して尾部判断ができません。サンプリングの原理は/devops/tracing-context-propagation/を参照してください。
まとめ
- OTelは**API/SDK(生成)とCollector(収集・加工・転送)**の二層分離で、計装コードとバックエンドの依存を切る。アプリはOTLPで吐くことだけを知る。
- traces/metrics/logs は別シグナルだが、**共通のResource(生成元属性)とSemantic Conventions(命名規約)**を共有し、logs は
trace_idを持てるため、時刻ではなく共有キーで相関できる。 - OTLPはprotobuf定義で
Resource→Scope→シグナル本体の3段構造を3シグナル共通に取り、転送単位で生成元を保つ。 - Collectorは receiver→processor→exporter のパイプラインで、内部表現pdataへ変換して加工し、複数バックエンドへ扇形送出する。processorは順序が結果を左右し、テールサンプリングはGateway形態で機能する。
- 何をどれだけ保持・集約するかの判断は、基数の観点(/devops/metrics-cardinality/)と合わせて設計すると軸が定まります。
DevOps/インフラ Article
OpenTelemetry のデータモデルとパイプラインを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
OpenTelemetry
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 5
導入後に効く点
traces/metrics/logsは別シグナルだが、共通のResource(生成元の属性)とSemantic Conventions(属性の命名規約)を共有し、後段で相関できる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 5
判断チェックリスト
- 自社の用途が「OpenTelemetry / OTLP」に近いか確認する。
- 強みである「OpenTelemetryはAPI/SDK(生成)とCollector(収集・加工・転送)に分離され、計装コードとバックエンドの依存を切り離す。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。