OpenTelemetry

OpenTelemetry 包括的技術ガイド — 機能・アーキテクチャ・設定例


目次

  1. OpenTelemetry とは
  2. アーキテクチャ概要
  3. テレメトリの三本柱 — トレース
  4. テレメトリの三本柱 — メトリクス
  5. テレメトリの三本柱 — ログ
  6. SDK インストルメンテーション例 (Python)
  7. 自動インストルメンテーション (Auto-Instrumentation)
  8. 自動 vs 手動インストルメンテーション比較
  9. コンテキスト伝播 (Context Propagation)
  10. OpenTelemetry Collector アーキテクチャ
  11. Collector 設定例(フル構成)
  12. Connectors
  13. OTLP プロトコル (gRPC vs HTTP)
  14. サンプリング戦略
  15. Kubernetes デプロイメント (OTel Operator)
  16. セマンティック規約 (Semantic Conventions)
  17. テレメトリ相関 (Exemplars)
  18. ベストプラクティス

1. OpenTelemetry とは

1.1 概要

OpenTelemetry(OTel)は、CNCF(Cloud Native Computing Foundation) がホストするオープンソースのオブザーバビリティフレームワークである。Kubernetes に次ぐ CNCF で2番目にアクティブなプロジェクトであり、テレメトリデータ(トレース・メトリクス・ログ)の生成・収集・エクスポートを標準化する。

OpenTelemetry は以下の2つの先行プロジェクトを統合して2019年に誕生した:

  • OpenTracing: 分散トレーシングの標準 API
  • OpenCensus: Google 発のトレース・メトリクス収集ライブラリ

1.2 設計思想

+----------------------------------------------------------+
|                    OpenTelemetry の位置づけ                  |
+----------------------------------------------------------+
|                                                          |
|  アプリケーション                                          |
|  ┌──────────────────────────────────────────────────┐    |
|  │  ビジネスロジック                                    │    |
|  │       │                                          │    |
|  │       ▼                                          │    |
|  │  ┌──────────────────────┐                        │    |
|  │  │  OTel SDK / API      │  ← ベンダー非依存        │    |
|  │  │  (Traces, Metrics,   │                        │    |
|  │  │   Logs)              │                        │    |
|  │  └──────────┬───────────┘                        │    |
|  └─────────────┼────────────────────────────────────┘    |
|                │  OTLP (標準プロトコル)                     |
|                ▼                                         |
|  ┌──────────────────────┐                                |
|  │  OTel Collector       │  ← 収集・加工・転送             |
|  └──────────┬───────────┘                                |
|             │                                            |
|    ┌────────┼────────┐                                   |
|    ▼        ▼        ▼                                   |
|  Jaeger  Prometheus  Grafana   ← 任意のバックエンド        |
|  Datadog  Splunk     Loki                                |
+----------------------------------------------------------+

1.3 主要な特徴

特徴説明
ベンダー非依存特定の監視ツールに依存せず、バックエンドを自由に切り替え可能
W3C Trace Context分散トレーシングのコンテキスト伝播に W3C 標準を採用
OTLPテレメトリ転送の標準プロトコル(gRPC / HTTP)
多言語対応Java, Python, Go, .NET, JavaScript, Rust, C++, Swift 等のSDK
自動インストルメンテーションコード変更なしでテレメトリを自動取得
セマンティック規約テレメトリ属性の命名を標準化

1.4 W3C Trace Context

W3C Trace Context は、分散システム間でトレースコンテキストを伝播するための HTTP ヘッダー標準である。

# HTTP リクエストヘッダー例
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
             │   │                                │                  │
             │   │                                │                  └─ flags (01 = sampled)
             │   │                                └─ parent-id (8 bytes)
             │   └─ trace-id (16 bytes)
             └─ version

tracestate: congo=t61rcWkgMzE,rojo=00f067aa0ba902b7
            └─ ベンダー固有の追加コンテキスト(キー=値のリスト)
  • traceparent: トレースID、スパンID、サンプリングフラグを含む必須ヘッダー
  • tracestate: ベンダー固有の追加情報を伝播するオプションヘッダー

1.5 プロジェクトの成熟度

OpenTelemetry のシグナルごとの成熟度(2025年時点):

シグナルAPISDKプロトコル
TracesStableStableStable
MetricsStableStableStable
LogsStableStableStable
ProfilingExperimentalExperimentalExperimental

2. アーキテクチャ概要

2.1 コンポーネント構成

OpenTelemetry は以下の主要コンポーネントで構成される:

┌─────────────────────────────────────────────────────────────────────┐
│                     OpenTelemetry アーキテクチャ                       │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────────────────────────────────────┐                    │
│  │            Specification (仕様)               │                    │
│  │  ┌──────┐  ┌──────┐  ┌──────────────────┐  │                    │
│  │  │ API  │  │ SDK  │  │ Data Model (OTLP)│  │                    │
│  │  └──────┘  └──────┘  └──────────────────┘  │                    │
│  └─────────────────────────────────────────────┘                    │
│         │                                                           │
│         ▼                                                           │
│  ┌──────────────────────────────────────────────────┐               │
│  │         Language SDKs (言語別実装)                  │               │
│  │                                                    │               │
│  │  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐    │               │
│  │  │ Python │ │  Java  │ │   Go   │ │  .NET  │    │               │
│  │  └────────┘ └────────┘ └────────┘ └────────┘    │               │
│  │  ┌────────┐ ┌────────┐ ┌────────┐               │               │
│  │  │  Node  │ │  Rust  │ │  C++   │ ...           │               │
│  │  └────────┘ └────────┘ └────────┘               │               │
│  └──────────────────────────────────────────────────┘               │
│         │                                                           │
│         │ OTLP                                                      │
│         ▼                                                           │
│  ┌──────────────────────────────────────────────────┐               │
│  │            OpenTelemetry Collector                 │               │
│  │                                                    │               │
│  │  Receivers ──▶ Processors ──▶ Exporters           │               │
│  │                                                    │               │
│  └──────────────────────────────────────────────────┘               │
│         │                                                           │
│         ▼                                                           │
│  ┌──────────────────────────────────────────────────┐               │
│  │         Observability Backends                    │               │
│  │  Jaeger │ Tempo │ Prometheus │ Datadog │ Splunk  │               │
│  └──────────────────────────────────────────────────┘               │
│                                                                     │
│  ┌──────────────────────────────────────────────────┐               │
│  │        Kubernetes Operator (自動化)               │               │
│  │  Collector CRD │ Instrumentation CRD             │               │
│  └──────────────────────────────────────────────────┘               │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

2.2 API と SDK の分離

OpenTelemetry では APISDK が明確に分離されている:

  • API: インストルメンテーション(計測)のためのインターフェース定義。ライブラリ開発者はAPIのみに依存する。
  • SDK: APIの具体的な実装。サンプリング、エクスポート、リソース検出等の機能を提供する。
# API のみを使用(ライブラリコード)
from opentelemetry import trace

tracer = trace.get_tracer("my-library", "1.0.0")

with tracer.start_as_current_span("operation") as span:
    span.set_attribute("key", "value")

# SDK の設定(アプリケーションコード)
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

provider = TracerProvider()
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="localhost:4317"))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)

この分離により:

  • ライブラリは API のみに依存し、SDK への直接依存を避けられる
  • アプリケーション開発者がSDKの設定(バックエンド選択、サンプリング等)を制御できる
  • SDK が未設定の場合、API 呼び出しは no-op(何もしない)として動作する

2.3 リソース (Resource)

Resource はテレメトリを生成するエンティティ(サービス、ホスト、コンテナ等)を記述する属性のセットである。

from opentelemetry.sdk.resources import Resource

resource = Resource.create({
    "service.name": "payment-service",
    "service.version": "2.1.0",
    "service.namespace": "production",
    "deployment.environment": "prod",
    "host.name": "payment-pod-abc123",
    "cloud.provider": "aws",
    "cloud.region": "ap-northeast-1",
    "k8s.namespace.name": "payments",
    "k8s.pod.name": "payment-service-7d9f8c6b5-x2k4m",
})

3. テレメトリの三本柱 — トレース (Traces)

3.1 分散トレーシングの概念

分散トレースは、リクエストが複数のサービスを横断する際の一連の処理を追跡する仕組みである。

                        Trace (trace-id: abc123)
┌──────────────────────────────────────────────────────────────────┐
│                                                                  │
│  Service A: API Gateway                                          │
│  ┌─────────────────────────────────────────────────────────┐     │
│  │ Span: HTTP GET /api/orders/42     [0ms ─────── 350ms]   │     │
│  │ span-id: aaa111                                          │     │
│  └─────────────┬───────────────────────────────────────────┘     │
│                │                                                  │
│  Service B: Order Service                                        │
│  │             ┌──────────────────────────────────────┐          │
│  │             │ Span: getOrder       [20ms ── 280ms] │          │
│  │             │ span-id: bbb222                       │          │
│  │             │ parent: aaa111                         │          │
│  │             └────────┬──────────────────────────────┘          │
│  │                      │                                         │
│  Service C: Database                                              │
│  │                      ┌────────────────────────┐               │
│  │                      │ Span: SELECT  [50ms-120ms]│            │
│  │                      │ span-id: ccc333          │             │
│  │                      │ parent: bbb222           │             │
│  │                      └──────────────────────────┘             │
│  │                      │                                         │
│  Service D: Cache                                                 │
│  │                      ┌───────────────────────────┐            │
│  │                      │ Span: redis.get [130ms-140ms]│         │
│  │                      │ span-id: ddd444             │          │
│  │                      │ parent: bbb222              │          │
│  │                      └─────────────────────────────┘          │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

3.2 Span(スパン)

Span は分散トレースにおける作業の最小単位である。各 Span は以下の情報を持つ:

フィールド説明
Nameスパンの名前(例: HTTP GET /api/orders
SpanContexttrace_id, span_id, trace_flags, trace_state
Parent SpanContext親スパンの SpanContext
SpanKindスパンの種類(下記参照)
Start/End Timestamp開始・終了タイムスタンプ
Attributesキー=値ペアの属性
Eventsタイムスタンプ付きのイベント
Links他のスパンへのリンク
StatusOK, ERROR, UNSET

3.3 SpanContext

SpanContext はスパンを一意に識別し、トレース全体で伝播される情報である。

from opentelemetry import trace

span = trace.get_current_span()
ctx = span.get_span_context()

print(f"Trace ID:     {format(ctx.trace_id, '032x')}")   # 128-bit
print(f"Span ID:      {format(ctx.span_id, '016x')}")    # 64-bit
print(f"Trace Flags:  {ctx.trace_flags}")                  # 8-bit (01 = sampled)
print(f"Trace State:  {ctx.trace_state}")                  # vendor-specific key-value pairs
print(f"Is Remote:    {ctx.is_remote}")                    # リモートから伝播されたか

3.4 SpanKind(スパン種別)

SpanKind はスパンの役割を示し、トレースの構造を理解しやすくする。

SpanKind説明使用場面
INTERNAL内部操作(デフォルト)ローカルな関数呼び出し、内部処理
SERVERサーバー側のリクエスト処理HTTP サーバーがリクエストを受信
CLIENTクライアント側のリクエスト送信HTTP クライアントがリクエストを送信
PRODUCER非同期メッセージの送信Kafka にメッセージを publish
CONSUMER非同期メッセージの受信Kafka からメッセージを consume
from opentelemetry import trace
from opentelemetry.trace import SpanKind

tracer = trace.get_tracer("example")

# SERVER スパン: HTTP リクエストを受け取るサーバー側
with tracer.start_as_current_span("handle_request", kind=SpanKind.SERVER) as span:
    span.set_attribute("http.request.method", "GET")
    span.set_attribute("url.path", "/api/orders/42")
    span.set_attribute("http.response.status_code", 200)

    # CLIENT スパン: 別のサービスへのリクエスト
    with tracer.start_as_current_span("call_inventory", kind=SpanKind.CLIENT) as client_span:
        client_span.set_attribute("http.request.method", "POST")
        client_span.set_attribute("server.address", "inventory-service")

    # PRODUCER スパン: メッセージの送信
    with tracer.start_as_current_span("send_notification", kind=SpanKind.PRODUCER) as producer_span:
        producer_span.set_attribute("messaging.system", "kafka")
        producer_span.set_attribute("messaging.destination.name", "order-events")

3.5 SpanEvent(スパンイベント)

SpanEvent はスパンのライフタイム中に発生する特定の時点のイベントを記録する。ログのような情報をスパンに関連付けられる。

import time
from opentelemetry import trace

tracer = trace.get_tracer("example")

with tracer.start_as_current_span("process_order") as span:
    # イベント: 注文のバリデーション完了
    span.add_event("validation_completed", attributes={
        "order.items_count": 3,
        "order.total": 15000,
    })

    # 処理を実行
    time.sleep(0.1)

    # イベント: 支払い処理開始
    span.add_event("payment_processing_started", attributes={
        "payment.method": "credit_card",
        "payment.provider": "stripe",
    })

    # 例外イベント: エラー発生時
    try:
        raise ValueError("Insufficient inventory")
    except Exception as e:
        span.set_status(trace.Status(trace.StatusCode.ERROR, str(e)))
        span.record_exception(e, attributes={
            "exception.escaped": False,
            "order.id": "ORD-12345",
        })

3.6 SpanLink(スパンリンク)

SpanLink は因果関係はないが関連性のあるスパン同士を紐付ける。バッチ処理や非同期処理で特に有用である。

from opentelemetry import trace
from opentelemetry.trace import Link

tracer = trace.get_tracer("example")

# シナリオ: バッチ処理で複数の個別リクエストを一括処理
# 各リクエストの SpanContext を取得
request_contexts = [
    trace.get_current_span().get_span_context()  # 実際には各リクエストから取得
    for _ in range(3)
]

# バッチ処理のスパンに、元のリクエストスパンをリンク
links = [
    Link(ctx, attributes={"request.index": i})
    for i, ctx in enumerate(request_contexts)
]

with tracer.start_as_current_span("batch_process", links=links) as span:
    span.set_attribute("batch.size", len(links))
    # バッチ処理の実行
    pass

SpanLink の主なユースケース:

  • バッチ処理: 複数の個別リクエストをまとめて処理する場合
  • 非同期 Fan-out/Fan-in: 一つのリクエストが複数の並列処理を起動する場合
  • キュー消費: キューからのメッセージが複数のプロデューサーに由来する場合
  • トレース再開: タイムアウト後にリトライされた処理

3.7 Span Status

from opentelemetry import trace
from opentelemetry.trace import StatusCode

tracer = trace.get_tracer("example")

with tracer.start_as_current_span("risky_operation") as span:
    try:
        result = perform_operation()
        span.set_status(StatusCode.OK)
    except Exception as e:
        span.set_status(StatusCode.ERROR, str(e))
        span.record_exception(e)
        raise

Status の種類:

  • UNSET (デフォルト): 明示的にステータスが設定されていない
  • OK: 操作が正常に完了
  • ERROR: エラーが発生

4. テレメトリの三本柱 — メトリクス (Metrics)

4.1 メトリクスの概要

OpenTelemetry のメトリクスは、システムの状態を数値で計測するためのシグナルである。Prometheus のメトリクスモデルと互換性があり、時系列データとして集約される。

4.2 メトリクスの種類(Instrument Types)

Instrument同期/非同期単調性用途
Counter同期単調増加累積カウントリクエスト数、エラー数
UpDownCounter同期増減可現在の数量アクティブ接続数、キューサイズ
Histogram同期-値の分布レイテンシ、リクエストサイズ
Gauge非同期-現在値のスナップショットCPU使用率、メモリ使用量
ObservableCounter非同期単調増加コールバックで取得する累積値システムCPU時間
ObservableUpDownCounter非同期増減可コールバックで取得する現在数量プロセスメモリ使用量
ObservableGauge非同期-コールバックで取得する現在値温度センサー

4.3 Counter(カウンター)

単調増加のみ可能なカウンター。リクエスト数やエラー数の計測に使用する。

from opentelemetry import metrics

meter = metrics.get_meter("example", "1.0.0")

# Counter の作成
request_counter = meter.create_counter(
    name="http.server.request.count",
    description="HTTP リクエストの総数",
    unit="1",
)

# 使用例
def handle_request(method: str, route: str, status_code: int):
    # 属性(ラベル)付きでカウント
    request_counter.add(1, attributes={
        "http.request.method": method,
        "http.route": route,
        "http.response.status_code": status_code,
    })

# 呼び出し
handle_request("GET", "/api/orders", 200)
handle_request("POST", "/api/orders", 201)
handle_request("GET", "/api/orders", 500)

4.4 UpDownCounter

増減両方が可能なカウンター。アクティブな接続数やキューの深さなど、増減する値に使用する。

meter = metrics.get_meter("example", "1.0.0")

# UpDownCounter の作成
active_connections = meter.create_up_down_counter(
    name="http.server.active_connections",
    description="アクティブな HTTP 接続数",
    unit="1",
)

queue_depth = meter.create_up_down_counter(
    name="message.queue.depth",
    description="メッセージキューの現在の深さ",
    unit="1",
)

# 使用例
def on_connection_open(service: str):
    active_connections.add(1, attributes={"service.name": service})

def on_connection_close(service: str):
    active_connections.add(-1, attributes={"service.name": service})

def on_message_enqueue(queue: str):
    queue_depth.add(1, attributes={"queue.name": queue})

def on_message_dequeue(queue: str):
    queue_depth.add(-1, attributes={"queue.name": queue})

4.5 Histogram(ヒストグラム)

値の分布を記録する。レイテンシやリクエストサイズなど、統計的な分析が必要な値に使用する。

import time

meter = metrics.get_meter("example", "1.0.0")

# Histogram の作成
request_duration = meter.create_histogram(
    name="http.server.request.duration",
    description="HTTP リクエストの処理時間",
    unit="ms",
)

request_size = meter.create_histogram(
    name="http.server.request.body.size",
    description="HTTP リクエストボディのサイズ",
    unit="By",
)

# 使用例
def handle_request(method: str, route: str):
    start = time.time()
    try:
        result = process_request()
        status_code = 200
    except Exception:
        status_code = 500
    finally:
        duration_ms = (time.time() - start) * 1000
        request_duration.record(duration_ms, attributes={
            "http.request.method": method,
            "http.route": route,
            "http.response.status_code": status_code,
        })

# カスタムバケット境界の設定(SDK設定時)
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.view import View, ExplicitBucketHistogramAggregation

latency_view = View(
    instrument_name="http.server.request.duration",
    aggregation=ExplicitBucketHistogramAggregation(
        boundaries=[0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 10000]
    ),
)

4.6 Gauge(ゲージ)

非同期のコールバックで現在の値を報告する。CPU使用率やメモリ使用量など、ポーリングで取得する値に使用する。

import psutil

meter = metrics.get_meter("example", "1.0.0")

# Gauge (Observable Gauge) の作成 — コールバック関数を登録
def cpu_usage_callback(options):
    """CPU使用率を返すコールバック"""
    for i, percent in enumerate(psutil.cpu_percent(percpu=True)):
        yield metrics.Observation(
            value=percent,
            attributes={"cpu.id": str(i)},
        )

def memory_usage_callback(options):
    """メモリ使用量を返すコールバック"""
    mem = psutil.virtual_memory()
    yield metrics.Observation(
        value=mem.used,
        attributes={"state": "used"},
    )
    yield metrics.Observation(
        value=mem.available,
        attributes={"state": "available"},
    )

meter.create_observable_gauge(
    name="system.cpu.utilization",
    description="CPU 使用率",
    unit="1",
    callbacks=[cpu_usage_callback],
)

meter.create_observable_gauge(
    name="system.memory.usage",
    description="メモリ使用量",
    unit="By",
    callbacks=[memory_usage_callback],
)

4.7 メトリクス集約パイプライン

┌──────────────────────────────────────────────────────────────────┐
│                    メトリクスパイプライン                            │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Instrument (Counter, Histogram, etc.)                           │
│       │                                                          │
│       ▼                                                          │
│  ┌──────────────────┐                                            │
│  │  Measurement      │  個々の計測値                               │
│  │  (value + attrs)  │                                            │
│  └────────┬─────────┘                                            │
│           │                                                      │
│           ▼                                                      │
│  ┌──────────────────┐  View で集約方法を制御                       │
│  │  Aggregation      │  - Sum (Counter)                           │
│  │                   │  - Histogram (Histogram)                   │
│  │                   │  - LastValue (Gauge)                       │
│  └────────┬─────────┘                                            │
│           │                                                      │
│           ▼                                                      │
│  ┌──────────────────┐  Temporality:                              │
│  │  MetricReader     │  - Cumulative (Prometheus互換)              │
│  │                   │  - Delta (効率的な転送)                      │
│  └────────┬─────────┘                                            │
│           │                                                      │
│           ▼                                                      │
│  ┌──────────────────┐                                            │
│  │  MetricExporter   │  OTLP, Prometheus, Console 等              │
│  └──────────────────┘                                            │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

5. テレメトリの三本柱 — ログ (Logs)

5.1 OTel Logs の概要

OpenTelemetry のログは、既存のログ基盤との統合を重視して設計されている。既存のログライブラリ(Python の logging、Java の Log4j 等)からのログを取り込み、トレースコンテキストと関連付ける。

5.2 ログデータモデル

┌──────────────────────────────────────────────────────────────────┐
│                      LogRecord                                    │
├──────────────────────────────────────────────────────────────────┤
│  Timestamp:           2025-01-15T10:30:45.123456Z                │
│  ObservedTimestamp:   2025-01-15T10:30:45.123500Z                │
│  SeverityNumber:      9  (INFO)                                   │
│  SeverityText:        "INFO"                                      │
│  Body:                "Order processed successfully"              │
│  Attributes:                                                      │
│    order.id:          "ORD-12345"                                 │
│    customer.id:       "CUST-67890"                                │
│  Resource:                                                        │
│    service.name:      "order-service"                             │
│    service.version:   "2.1.0"                                     │
│  TraceId:             4bf92f3577b34da6a3ce929d0e0e4736            │
│  SpanId:              00f067aa0ba902b7                             │
│  TraceFlags:          01                                          │
└──────────────────────────────────────────────────────────────────┘

5.3 トレースとの相関

ログとトレースの相関は、同じリクエストコンテキスト内で出力されたログに自動的に trace_id と span_id が付与されることで実現される。

import logging
from opentelemetry import trace
from opentelemetry._logs import set_logger_provider
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter

# ログプロバイダーのセットアップ
logger_provider = LoggerProvider(resource=resource)
logger_provider.add_log_record_processor(
    BatchLogRecordProcessor(OTLPLogExporter(endpoint="localhost:4317"))
)
set_logger_provider(logger_provider)

# Python の標準 logging と統合
handler = LoggingHandler(
    level=logging.INFO,
    logger_provider=logger_provider,
)
logging.getLogger().addHandler(handler)

# 使用例: トレースコンテキスト内でのログ出力
logger = logging.getLogger("order-service")
tracer = trace.get_tracer("order-service")

def process_order(order_id: str):
    with tracer.start_as_current_span("process_order") as span:
        span.set_attribute("order.id", order_id)

        # このログは自動的に trace_id, span_id が付与される
        logger.info(
            "Order processing started",
            extra={"order.id": order_id}
        )

        try:
            validate_order(order_id)
            logger.info("Order validation passed", extra={"order.id": order_id})

            charge_payment(order_id)
            logger.info("Payment charged successfully", extra={"order.id": order_id})

        except Exception as e:
            logger.error(
                f"Order processing failed: {e}",
                extra={"order.id": order_id},
                exc_info=True,
            )
            raise

5.4 Severity Levels マッピング

SeverityNumberSeverityTextPython loggingJava Log4j
1-4TRACE-TRACE
5-8DEBUGDEBUG (10)DEBUG
9-12INFOINFO (20)INFO
13-16WARNWARNING (30)WARN
17-20ERRORERROR (40)ERROR
21-24FATALCRITICAL (50)FATAL

6. SDK インストルメンテーション例 (Python)

6.1 完全なセットアップ例

以下は、トレース・メトリクス・ログの3つのシグナルすべてを設定する完全な Python アプリケーション例である。

# otel_setup.py — OpenTelemetry 完全セットアップ

from opentelemetry import trace, metrics
from opentelemetry._logs import set_logger_provider

# --- Resource(共通)---
from opentelemetry.sdk.resources import Resource

resource = Resource.create({
    "service.name": "order-service",
    "service.version": "2.1.0",
    "deployment.environment": "production",
})

# ===================================
# 1. トレースの設定
# ===================================
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

trace_provider = TracerProvider(resource=resource)
trace_provider.add_span_processor(
    BatchSpanProcessor(
        OTLPSpanExporter(endpoint="http://otel-collector:4317"),
        max_queue_size=2048,
        max_export_batch_size=512,
        schedule_delay_millis=5000,
    )
)
trace.set_tracer_provider(trace_provider)

# ===================================
# 2. メトリクスの設定
# ===================================
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter

metric_reader = PeriodicExportingMetricReader(
    OTLPMetricExporter(endpoint="http://otel-collector:4317"),
    export_interval_millis=60000,  # 60秒ごとにエクスポート
)
metrics_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
metrics.set_meter_provider(metrics_provider)

# ===================================
# 3. ログの設定
# ===================================
import logging
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter

logger_provider = LoggerProvider(resource=resource)
logger_provider.add_log_record_processor(
    BatchLogRecordProcessor(
        OTLPLogExporter(endpoint="http://otel-collector:4317")
    )
)
set_logger_provider(logger_provider)

# Python 標準 logging に OTel ハンドラーを追加
handler = LoggingHandler(level=logging.INFO, logger_provider=logger_provider)
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)

# ===================================
# シャットダウン関数
# ===================================
def shutdown():
    trace_provider.shutdown()
    metrics_provider.shutdown()
    logger_provider.shutdown()

6.2 トレースの実装例

# tracing_example.py
import time
from opentelemetry import trace
from opentelemetry.trace import StatusCode, SpanKind

tracer = trace.get_tracer("order-service", "2.1.0")

def create_order(request):
    """注文作成の処理 — トレーシング付き"""
    with tracer.start_as_current_span(
        "create_order",
        kind=SpanKind.SERVER,
        attributes={
            "http.request.method": "POST",
            "url.path": "/api/orders",
            "user.id": request.user_id,
        },
    ) as span:
        try:
            # ステップ1: バリデーション
            with tracer.start_as_current_span("validate_order") as val_span:
                validate_result = validate(request)
                val_span.set_attribute("validation.items_count", len(request.items))
                val_span.add_event("validation_complete", attributes={
                    "valid": True,
                })

            # ステップ2: 在庫チェック(外部サービス呼び出し)
            with tracer.start_as_current_span(
                "check_inventory",
                kind=SpanKind.CLIENT,
            ) as inv_span:
                inv_span.set_attribute("server.address", "inventory-service")
                inv_span.set_attribute("rpc.system", "grpc")
                inventory_ok = check_inventory(request.items)

            # ステップ3: 支払い処理
            with tracer.start_as_current_span(
                "process_payment",
                kind=SpanKind.CLIENT,
            ) as pay_span:
                pay_span.set_attribute("payment.method", request.payment_method)
                pay_span.set_attribute("payment.amount", request.total)
                payment_result = process_payment(request)
                pay_span.add_event("payment_completed", attributes={
                    "transaction.id": payment_result.transaction_id,
                })

            # ステップ4: 確認メール送信(非同期)
            with tracer.start_as_current_span(
                "send_confirmation",
                kind=SpanKind.PRODUCER,
            ) as msg_span:
                msg_span.set_attribute("messaging.system", "kafka")
                msg_span.set_attribute("messaging.destination.name", "order-confirmations")
                send_to_kafka("order-confirmations", {
                    "order_id": request.order_id,
                    "email": request.email,
                })

            span.set_status(StatusCode.OK)
            return {"order_id": request.order_id, "status": "created"}

        except Exception as e:
            span.set_status(StatusCode.ERROR, str(e))
            span.record_exception(e)
            raise

6.3 メトリクスの実装例

# metrics_example.py
import time
from opentelemetry import metrics

meter = metrics.get_meter("order-service", "2.1.0")

# --- Counter ---
orders_created = meter.create_counter(
    name="orders.created.total",
    description="作成された注文の総数",
    unit="1",
)

# --- UpDownCounter ---
orders_in_progress = meter.create_up_down_counter(
    name="orders.in_progress",
    description="現在処理中の注文数",
    unit="1",
)

# --- Histogram ---
order_processing_duration = meter.create_histogram(
    name="orders.processing.duration",
    description="注文処理にかかった時間",
    unit="ms",
)

order_total_amount = meter.create_histogram(
    name="orders.total.amount",
    description="注文の合計金額の分布",
    unit="JPY",
)

# --- Observable Gauge ---
def active_workers_callback(options):
    """アクティブなワーカースレッド数を返す"""
    import threading
    yield metrics.Observation(
        value=threading.active_count(),
        attributes={"worker.type": "thread"},
    )

meter.create_observable_gauge(
    name="workers.active",
    description="アクティブなワーカー数",
    unit="1",
    callbacks=[active_workers_callback],
)

# 使用例
def process_order(order):
    orders_in_progress.add(1, attributes={"order.type": order.type})
    start = time.time()

    try:
        result = _do_process(order)
        orders_created.add(1, attributes={
            "order.type": order.type,
            "status": "success",
        })
        order_total_amount.record(order.total, attributes={
            "order.type": order.type,
            "currency": "JPY",
        })
        return result
    except Exception as e:
        orders_created.add(1, attributes={
            "order.type": order.type,
            "status": "failure",
        })
        raise
    finally:
        duration_ms = (time.time() - start) * 1000
        order_processing_duration.record(duration_ms, attributes={
            "order.type": order.type,
        })
        orders_in_progress.add(-1, attributes={"order.type": order.type})

6.4 ログの実装例

# logging_example.py
import logging
from opentelemetry import trace

logger = logging.getLogger("order-service")
tracer = trace.get_tracer("order-service")

def process_order(order_id: str, items: list):
    """トレースコンテキスト付きログの例"""

    with tracer.start_as_current_span("process_order") as span:
        span.set_attribute("order.id", order_id)

        # INFO レベルのログ — 自動的に trace_id, span_id が付与される
        logger.info(
            "Starting order processing",
            extra={
                "order.id": order_id,
                "items.count": len(items),
            },
        )

        for item in items:
            with tracer.start_as_current_span("process_item") as item_span:
                item_span.set_attribute("item.sku", item["sku"])

                logger.info(
                    f"Processing item: {item['sku']}",
                    extra={"item.sku": item["sku"], "item.quantity": item["qty"]},
                )

                if item["qty"] > 100:
                    logger.warning(
                        f"Large quantity order for item {item['sku']}",
                        extra={"item.sku": item["sku"], "item.quantity": item["qty"]},
                    )

        logger.info(
            "Order processing completed",
            extra={"order.id": order_id},
        )

6.5 環境変数による設定

SDK は環境変数で設定をオーバーライドできる:

# エクスポーター設定
export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4317"
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"        # grpc or http/protobuf
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer token123"
export OTEL_EXPORTER_OTLP_TIMEOUT="10000"         # ミリ秒

# リソース設定
export OTEL_SERVICE_NAME="order-service"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production,service.version=2.1.0"

# トレース設定
export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.1"              # 10% サンプリング

# メトリクス設定
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_METRIC_EXPORT_INTERVAL="60000"         # 60秒

# ログ設定
export OTEL_LOGS_EXPORTER="otlp"

# 伝播設定
export OTEL_PROPAGATORS="tracecontext,baggage"

# SDK の無効化(デバッグ用)
export OTEL_SDK_DISABLED="false"

7. 自動インストルメンテーション (Auto-Instrumentation)

7.1 概要

自動インストルメンテーションは、アプリケーションのソースコードを変更せずにテレメトリデータを自動的に取得する仕組みである。一般的なフレームワーク・ライブラリ(HTTP サーバー、データベースクライアント、メッセージキュー等)の呼び出しを自動的に計測する。

7.2 Python 自動インストルメンテーション

# インストール
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install  # 検出されたライブラリ用のインストルメンテーションを自動インストール

# 実行 — コード変更不要
opentelemetry-instrument \
    --service_name order-service \
    --traces_exporter otlp \
    --metrics_exporter otlp \
    --logs_exporter otlp \
    --exporter_otlp_endpoint http://otel-collector:4317 \
    python app.py

自動的に計測されるライブラリの例:

ライブラリ計測内容
Flask / Django / FastAPIHTTP リクエスト/レスポンスのスパン
requests / httpx / aiohttp外部 HTTP 呼び出しのスパン
SQLAlchemy / psycopg2 / pymysqlデータベースクエリのスパン
redisRedis コマンドのスパン
celeryタスクの実行スパン
boto3 (AWS SDK)AWS API 呼び出しのスパン
kafka-pythonKafka 送受信のスパン
grpcgRPC 呼び出しのスパン

7.3 Java 自動インストルメンテーション

Java は Java Agent メカニズムを使用し、バイトコードレベルでインストルメンテーションを注入する。

# Java Agent のダウンロード
curl -L -o opentelemetry-javaagent.jar \
    https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

# 実行 — コード変更不要
java -javaagent:opentelemetry-javaagent.jar \
     -Dotel.service.name=payment-service \
     -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
     -Dotel.traces.exporter=otlp \
     -Dotel.metrics.exporter=otlp \
     -Dotel.logs.exporter=otlp \
     -jar payment-service.jar

Java Agent が自動計測する主なフレームワーク:

カテゴリフレームワーク
Web フレームワークSpring Boot, Spring MVC, JAX-RS, Servlet, Netty
HTTP クライアントHttpURLConnection, OkHttp, Apache HttpClient, WebClient
データベースJDBC, Hibernate, MyBatis, R2DBC
メッセージングKafka, RabbitMQ, JMS, Amazon SQS
RPCgRPC, Apache Dubbo
キャッシュRedis (Jedis, Lettuce), Memcached
ログLog4j2, Logback, JUL
クラウドAWS SDK v1/v2, GCP, Azure

7.4 Node.js 自動インストルメンテーション

# インストール
npm install @opentelemetry/sdk-node \
            @opentelemetry/auto-instrumentations-node \
            @opentelemetry/exporter-trace-otlp-grpc \
            @opentelemetry/exporter-metrics-otlp-grpc
// tracing.js — Node.js アプリ起動前にロード
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-grpc');
const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics');

const sdk = new NodeSDK({
    serviceName: 'frontend-service',
    traceExporter: new OTLPTraceExporter({
        url: 'http://otel-collector:4317',
    }),
    metricReader: new PeriodicExportingMetricReader({
        exporter: new OTLPMetricExporter({
            url: 'http://otel-collector:4317',
        }),
        exportIntervalMillis: 60000,
    }),
    instrumentations: [
        getNodeAutoInstrumentations({
            '@opentelemetry/instrumentation-http': {
                ignoreIncomingRequestHook: (req) => {
                    return req.url === '/health';  // ヘルスチェックを除外
                },
            },
            '@opentelemetry/instrumentation-express': {
                enabled: true,
            },
            '@opentelemetry/instrumentation-pg': {
                enhancedDatabaseReporting: true,
            },
        }),
    ],
});

sdk.start();

// グレースフルシャットダウン
process.on('SIGTERM', () => {
    sdk.shutdown().then(() => process.exit(0));
});
# 実行
node --require ./tracing.js app.js

# または環境変数で
export NODE_OPTIONS="--require ./tracing.js"
node app.js

8. 自動 vs 手動インストルメンテーション比較

8.1 自動インストルメンテーションで取得できるもの

Java Agent を例に、自動インストルメンテーションが捕捉する情報と捕捉できない情報を示す。

自動で取得できる情報:

┌──────────────────────────────────────────────────────────────────┐
│              自動インストルメンテーションの可視範囲                    │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ✅ HTTP リクエスト/レスポンス                                      │
│     - メソッド、URL、ステータスコード、レイテンシ                      │
│     - リクエスト/レスポンスヘッダー                                  │
│                                                                  │
│  ✅ データベースクエリ                                               │
│     - SQL 文、データベース名、接続情報、レイテンシ                     │
│                                                                  │
│  ✅ 外部 HTTP 呼び出し                                              │
│     - 宛先ホスト、メソッド、ステータスコード                           │
│                                                                  │
│  ✅ メッセージング                                                   │
│     - Kafka/RabbitMQ のトピック、プロデュース/コンシューム              │
│                                                                  │
│  ✅ gRPC 呼び出し                                                   │
│     - サービス名、メソッド名、ステータス                               │
│                                                                  │
│  ✅ キャッシュ操作                                                   │
│     - Redis コマンド、キー、レイテンシ                                │
│                                                                  │
│  ✅ フレームワークのルーティング                                      │
│     - Spring MVC のコントローラーマッピング                           │
│     - JAX-RS のリソースパス                                         │
│                                                                  │
│  ✅ コンテキスト伝播                                                 │
│     - HTTP ヘッダーからの自動抽出・注入                               │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

8.2 自動インストルメンテーションで見えないもの

┌──────────────────────────────────────────────────────────────────┐
│           自動インストルメンテーションでは見えない領域                  │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ❌ ビジネスロジックの内部処理                                       │
│     - 注文バリデーションの各ステップ                                  │
│     - 価格計算のロジック                                            │
│     - 割引適用ルール                                               │
│                                                                  │
│  ❌ ビジネスメトリクス                                               │
│     - 注文金額、商品数                                              │
│     - ユーザーセグメント別の処理件数                                  │
│     - コンバージョン率                                              │
│                                                                  │
│  ❌ 分岐・判断のコンテキスト                                         │
│     - どの条件分岐が選択されたか                                     │
│     - A/B テストのバリアント                                        │
│     - フォールバックが発生したか                                     │
│                                                                  │
│  ❌ バッチ処理の進捗                                                │
│     - 処理済み件数、スキップ件数                                     │
│     - 個別アイテムの処理結果                                        │
│                                                                  │
│  ❌ カスタムコンテキストの伝播                                       │
│     - テナントID、リクエスト元リージョン                              │
│     - ビジネスレベルの相関ID                                        │
│                                                                  │
│  ❌ エラーの詳細コンテキスト                                         │
│     - ビジネスバリデーションエラーの理由                               │
│     - リトライ回数と各試行の結果                                     │
│     - 部分的な成功/失敗                                             │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

8.3 手動インストルメンテーションが必要なケース — Java コード例

ケース1: ビジネスロジックの内部処理

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.StatusCode;

public class OrderService {
    private static final Tracer tracer =
        GlobalOpenTelemetry.getTracer("order-service", "2.1.0");

    public OrderResult processOrder(Order order) {
        // 自動では見えない: 注文処理の内部ステップ
        Span span = tracer.spanBuilder("processOrder")
            .setAttribute("order.id", order.getId())
            .setAttribute("order.total", order.getTotal().doubleValue())
            .setAttribute("order.items.count", order.getItems().size())
            .startSpan();

        try (var scope = span.makeCurrent()) {
            // ステップ1: ビジネスバリデーション
            Span validationSpan = tracer.spanBuilder("validateBusinessRules")
                .startSpan();
            try (var vScope = validationSpan.makeCurrent()) {
                ValidationResult result = validateBusinessRules(order);
                validationSpan.setAttribute("validation.rules.checked", result.rulesChecked());
                validationSpan.setAttribute("validation.passed", result.isPassed());
                validationSpan.addEvent("validation_complete", Attributes.of(
                    AttributeKey.booleanKey("passed"), result.isPassed()
                ));
            } finally {
                validationSpan.end();
            }

            // ステップ2: 価格計算(自動では見えない内部処理)
            Span pricingSpan = tracer.spanBuilder("calculatePricing")
                .startSpan();
            try (var pScope = pricingSpan.makeCurrent()) {
                PricingResult pricing = calculatePricing(order);
                pricingSpan.setAttribute("pricing.subtotal", pricing.getSubtotal());
                pricingSpan.setAttribute("pricing.discount", pricing.getDiscount());
                pricingSpan.setAttribute("pricing.tax", pricing.getTax());
                pricingSpan.setAttribute("pricing.discount.reason", pricing.getDiscountReason());
                pricingSpan.addEvent("discount_applied", Attributes.of(
                    AttributeKey.stringKey("discount.type"), pricing.getDiscountType(),
                    AttributeKey.doubleKey("discount.rate"), pricing.getDiscountRate()
                ));
            } finally {
                pricingSpan.end();
            }

            span.setStatus(StatusCode.OK);
            return OrderResult.success(order.getId());

        } catch (BusinessException e) {
            span.setStatus(StatusCode.ERROR, e.getMessage());
            span.recordException(e);
            return OrderResult.failure(e.getErrorCode());
        } finally {
            span.end();
        }
    }
}

ケース2: ビジネスメトリクス

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.common.Attributes;

public class OrderMetrics {
    private static final Meter meter =
        GlobalOpenTelemetry.getMeter("order-service", "2.1.0");

    // ビジネスメトリクス — 自動では取得できない
    private static final LongCounter ordersPlaced = meter
        .counterBuilder("business.orders.placed")
        .setDescription("注文が確定された総数")
        .setUnit("1")
        .build();

    private static final DoubleHistogram orderRevenue = meter
        .histogramBuilder("business.orders.revenue")
        .setDescription("注文金額の分布")
        .setUnit("JPY")
        .build();

    private static final LongCounter conversionEvents = meter
        .counterBuilder("business.conversion.events")
        .setDescription("コンバージョンイベント数")
        .setUnit("1")
        .build();

    public void recordOrderPlaced(Order order) {
        Attributes attrs = Attributes.builder()
            .put("order.type", order.getType().name())
            .put("customer.segment", order.getCustomerSegment())
            .put("channel", order.getChannel())
            .put("region", order.getRegion())
            .build();

        ordersPlaced.add(1, attrs);
        orderRevenue.record(order.getTotal().doubleValue(), attrs);
    }

    public void recordConversion(String funnelStep, String variant) {
        conversionEvents.add(1, Attributes.of(
            AttributeKey.stringKey("funnel.step"), funnelStep,
            AttributeKey.stringKey("ab.variant"), variant
        ));
    }
}

ケース3: 分岐・判断のトラッキング

public class FraudDetectionService {
    private static final Tracer tracer =
        GlobalOpenTelemetry.getTracer("fraud-detection", "1.0.0");

    public FraudResult evaluate(Transaction tx) {
        Span span = tracer.spanBuilder("evaluateFraud")
            .setAttribute("transaction.id", tx.getId())
            .setAttribute("transaction.amount", tx.getAmount())
            .startSpan();

        try (var scope = span.makeCurrent()) {
            // 自動では見えない: どのルールがトリガーされたか
            List<RuleResult> results = new ArrayList<>();

            for (FraudRule rule : rules) {
                Span ruleSpan = tracer.spanBuilder("evaluateRule." + rule.getName())
                    .setAttribute("rule.name", rule.getName())
                    .setAttribute("rule.version", rule.getVersion())
                    .startSpan();

                try (var rScope = ruleSpan.makeCurrent()) {
                    RuleResult result = rule.evaluate(tx);
                    ruleSpan.setAttribute("rule.triggered", result.isTriggered());
                    ruleSpan.setAttribute("rule.score", result.getScore());
                    ruleSpan.setAttribute("rule.reason", result.getReason());
                    results.add(result);
                } finally {
                    ruleSpan.end();
                }
            }

            double totalScore = results.stream().mapToDouble(RuleResult::getScore).sum();
            String decision = totalScore > THRESHOLD ? "BLOCK" : "ALLOW";

            // 判断結果の記録
            span.setAttribute("fraud.total_score", totalScore);
            span.setAttribute("fraud.decision", decision);
            span.setAttribute("fraud.rules_triggered",
                results.stream().filter(RuleResult::isTriggered).count());
            span.addEvent("fraud_decision", Attributes.of(
                AttributeKey.stringKey("decision"), decision,
                AttributeKey.doubleKey("score"), totalScore,
                AttributeKey.longKey("rules_checked"), (long) results.size()
            ));

            return new FraudResult(decision, totalScore, results);

        } finally {
            span.end();
        }
    }
}

ケース4: バッチ処理の進捗

public class BatchProcessor {
    private static final Tracer tracer =
        GlobalOpenTelemetry.getTracer("batch-processor", "1.0.0");
    private static final Meter meter =
        GlobalOpenTelemetry.getMeter("batch-processor", "1.0.0");

    private static final LongCounter processedItems = meter
        .counterBuilder("batch.items.processed")
        .setDescription("バッチ処理済みアイテム数")
        .build();

    public BatchResult processBatch(List<Item> items) {
        Span batchSpan = tracer.spanBuilder("processBatch")
            .setAttribute("batch.size", items.size())
            .startSpan();

        int success = 0, failure = 0, skipped = 0;

        try (var scope = batchSpan.makeCurrent()) {
            for (int i = 0; i < items.size(); i++) {
                Item item = items.get(i);
                Span itemSpan = tracer.spanBuilder("processItem")
                    .setAttribute("item.id", item.getId())
                    .setAttribute("item.index", i)
                    .startSpan();

                try (var iScope = itemSpan.makeCurrent()) {
                    if (shouldSkip(item)) {
                        skipped++;
                        itemSpan.setAttribute("item.result", "skipped");
                        itemSpan.setAttribute("item.skip.reason", getSkipReason(item));
                    } else {
                        processItem(item);
                        success++;
                        itemSpan.setAttribute("item.result", "success");
                    }
                } catch (Exception e) {
                    failure++;
                    itemSpan.setAttribute("item.result", "failure");
                    itemSpan.recordException(e);
                    itemSpan.setStatus(StatusCode.ERROR);
                } finally {
                    itemSpan.end();
                }

                // 定期的に進捗を記録
                if (i % 100 == 0) {
                    batchSpan.addEvent("batch_progress", Attributes.of(
                        AttributeKey.longKey("processed"), (long) (i + 1),
                        AttributeKey.longKey("total"), (long) items.size(),
                        AttributeKey.longKey("success"), (long) success,
                        AttributeKey.longKey("failure"), (long) failure
                    ));
                }
            }

            // 最終結果を記録
            batchSpan.setAttribute("batch.result.success", success);
            batchSpan.setAttribute("batch.result.failure", failure);
            batchSpan.setAttribute("batch.result.skipped", skipped);

            processedItems.add(success, Attributes.of(
                AttributeKey.stringKey("result"), "success"));
            processedItems.add(failure, Attributes.of(
                AttributeKey.stringKey("result"), "failure"));
            processedItems.add(skipped, Attributes.of(
                AttributeKey.stringKey("result"), "skipped"));

            return new BatchResult(success, failure, skipped);

        } finally {
            batchSpan.end();
        }
    }
}

ケース5: カスタムコンテキスト伝播

import io.opentelemetry.api.baggage.Baggage;

public class MultiTenantService {
    public void handleRequest(HttpServletRequest request) {
        String tenantId = request.getHeader("X-Tenant-ID");
        String region = request.getHeader("X-Region");

        // Baggage にビジネスコンテキストを設定
        // 下流サービスに自動的に伝播される
        Baggage baggage = Baggage.builder()
            .put("tenant.id", tenantId)
            .put("request.region", region)
            .put("request.priority", determinePriority(request))
            .build();

        try (var scope = baggage.makeCurrent()) {
            // 以降の処理では、下流サービスでも tenant.id が利用可能
            processRequest(request);
        }
    }

    // 下流サービスで Baggage を読み取る
    public void downstreamProcess() {
        String tenantId = Baggage.current().getEntryValue("tenant.id");
        String region = Baggage.current().getEntryValue("request.region");

        Span.current().setAttribute("tenant.id", tenantId);
        Span.current().setAttribute("request.region", region);
    }
}

ケース6: エラーコンテキストの強化

public class PaymentService {
    private static final Tracer tracer =
        GlobalOpenTelemetry.getTracer("payment-service", "1.0.0");
    private static final LongCounter retryCounter = meter
        .counterBuilder("payment.retries")
        .setDescription("支払いリトライ回数")
        .build();

    public PaymentResult processPayment(PaymentRequest request) {
        Span span = tracer.spanBuilder("processPayment")
            .setAttribute("payment.method", request.getMethod())
            .setAttribute("payment.amount", request.getAmount())
            .setAttribute("payment.currency", request.getCurrency())
            .startSpan();

        int maxRetries = 3;

        try (var scope = span.makeCurrent()) {
            for (int attempt = 1; attempt <= maxRetries; attempt++) {
                try {
                    PaymentResult result = gateway.charge(request);
                    span.setAttribute("payment.attempt", attempt);
                    span.setAttribute("payment.transaction_id", result.getTransactionId());

                    if (attempt > 1) {
                        span.addEvent("payment_succeeded_after_retry", Attributes.of(
                            AttributeKey.longKey("attempt"), (long) attempt
                        ));
                    }

                    return result;

                } catch (TransientException e) {
                    retryCounter.add(1, Attributes.of(
                        AttributeKey.stringKey("payment.method"), request.getMethod(),
                        AttributeKey.longKey("attempt"), (long) attempt
                    ));

                    span.addEvent("payment_retry", Attributes.of(
                        AttributeKey.longKey("attempt"), (long) attempt,
                        AttributeKey.stringKey("error.type"), e.getClass().getSimpleName(),
                        AttributeKey.stringKey("error.message"), e.getMessage(),
                        AttributeKey.stringKey("retry.reason"), e.getRetryReason()
                    ));

                    if (attempt == maxRetries) {
                        span.setStatus(StatusCode.ERROR,
                            "Payment failed after " + maxRetries + " attempts");
                        span.recordException(e);
                        throw new PaymentFailedException(
                            "Max retries exceeded", e, attempt);
                    }

                    backoff(attempt);
                }
            }
        } finally {
            span.end();
        }
        throw new IllegalStateException("Unreachable");
    }
}

8.4 推奨アプローチ

┌───────────────────────────────────────────────────────────────────┐
│            推奨: ハイブリッドアプローチ                                │
├───────────────────────────────────────────────────────────────────┤
│                                                                   │
│  レイヤー1: 自動インストルメンテーション(ベースライン)                  │
│  ─────────────────────────────────────────────                    │
│  • HTTP、DB、メッセージング等の基盤レベルのテレメトリ                   │
│  • コード変更不要、即座に可視性を獲得                                  │
│  • すべてのサービスに適用                                            │
│                                                                   │
│  レイヤー2: 手動インストルメンテーション(ビジネス価値)                  │
│  ─────────────────────────────────────────────                    │
│  • ビジネスロジックの重要なステップ                                    │
│  • ビジネスメトリクス(売上、コンバージョン等)                         │
│  • 判断ロジックのトラッキング                                        │
│  • エラーコンテキストの強化                                          │
│  • 重要なサービスに選択的に適用                                       │
│                                                                   │
│  ⚠️ 注意事項:                                                     │
│  • 過剰なインストルメンテーションはパフォーマンスに影響する               │
│  • Span の粒度が細かすぎるとノイズになる                              │
│  • ビジネス上重要なパスに集中する                                     │
│  • サンプリングと組み合わせてコストを制御する                           │
│                                                                   │
└───────────────────────────────────────────────────────────────────┘

9. コンテキスト伝播 (Context Propagation)

9.1 概要

コンテキスト伝播は、分散システムにおいてリクエストのコンテキスト情報(トレースID、Baggage 等)をサービス間で受け渡す仕組みである。

┌──────────────────────────────────────────────────────────────────┐
│                  コンテキスト伝播の流れ                               │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Service A                          Service B                    │
│  ┌──────────────────┐              ┌──────────────────┐          │
│  │  Span (aaa111)   │   HTTP       │  Span (bbb222)   │          │
│  │                  │ ─────────▶   │                  │          │
│  │  Inject context  │  Headers:    │  Extract context │          │
│  │  into headers    │  traceparent │  from headers    │          │
│  │                  │  tracestate  │                  │          │
│  │                  │  baggage     │  parent=aaa111   │          │
│  └──────────────────┘              └──────────────────┘          │
│                                                                  │
│  Propagator が Inject/Extract を担当                               │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

9.2 W3C Trace Context フォーマット

traceparent ヘッダー

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

フォーマット: {version}-{trace-id}-{parent-id}-{trace-flags}

version:     00             (固定: 現行バージョン)
trace-id:    4bf92f...4736  (128-bit, 32文字の16進数)
parent-id:   00f067...02b7  (64-bit, 16文字の16進数)
trace-flags: 01             (8-bit: 01 = sampled)

tracestate ヘッダー

tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE

ベンダー固有のコンテキストをキー=値のリストで伝播:
- 複数ベンダーの情報を同時に伝播可能
- 左側ほど新しい(最後に更新したベンダーが先頭)
- 最大 32 エントリ、合計 512 バイト

9.3 Propagator の設定

from opentelemetry import propagate
from opentelemetry.propagators.composite import CompositePropagator
from opentelemetry.propagators.textmap import TextMapPropagator
from opentelemetry.trace.propagation import TraceContextTextMapPropagator
from opentelemetry.baggage.propagation import W3CBaggagePropagator

# デフォルト: W3C Trace Context + Baggage
propagate.set_global_textmap(
    CompositePropagator([
        TraceContextTextMapPropagator(),   # traceparent, tracestate
        W3CBaggagePropagator(),             # baggage ヘッダー
    ])
)

# B3 形式も追加する場合(Zipkin 互換)
from opentelemetry.propagators.b3 import B3MultiFormat

propagate.set_global_textmap(
    CompositePropagator([
        TraceContextTextMapPropagator(),
        W3CBaggagePropagator(),
        B3MultiFormat(),  # x-b3-traceid, x-b3-spanid, x-b3-sampled
    ])
)

環境変数での設定:

# 使用する Propagator を指定
export OTEL_PROPAGATORS="tracecontext,baggage"

# B3 も追加
export OTEL_PROPAGATORS="tracecontext,baggage,b3multi"

# Jaeger 形式も追加
export OTEL_PROPAGATORS="tracecontext,baggage,jaeger"

9.4 Baggage(バゲージ)

Baggage はビジネスコンテキスト(テナントID、ユーザーリージョン等)をサービス間で伝播する仕組みである。

from opentelemetry import baggage, context

# Baggage の設定
ctx = baggage.set_baggage("tenant.id", "acme-corp")
ctx = baggage.set_baggage("user.region", "ap-northeast-1", context=ctx)
ctx = baggage.set_baggage("request.priority", "high", context=ctx)

# コンテキストをアクティブにする
token = context.attach(ctx)
try:
    # 以降の HTTP 呼び出しで自動的に baggage ヘッダーとして伝播
    # baggage: tenant.id=acme-corp,user.region=ap-northeast-1,request.priority=high
    make_downstream_call()
finally:
    context.detach(token)

# 下流サービスでの Baggage 読み取り
tenant_id = baggage.get_baggage("tenant.id")
region = baggage.get_baggage("user.region")

HTTP ヘッダーでの表現:

baggage: tenant.id=acme-corp,user.region=ap-northeast-1,request.priority=high

Baggage の注意点:

  • Baggage はすべての下流サービスに伝播される
  • 機密情報(パスワード、トークン等)を含めてはならない
  • サイズが大きくなるとネットワークオーバーヘッドが増加する
  • Baggage はテレメトリ属性に自動設定されない(明示的な設定が必要)

9.5 手動でのコンテキスト伝播

非標準のトランスポート(キュー、ファイル等)では手動で伝播する必要がある。

from opentelemetry import propagate, trace, context

# --- 送信側 (Producer) ---
def send_message_to_queue(message: dict):
    """メッセージキューにコンテキストを注入して送信"""
    carrier = {}
    propagate.inject(carrier)  # 現在のコンテキストを carrier に注入

    message["_otel_context"] = carrier  # メッセージにコンテキストを含める
    queue.send(message)

# --- 受信側 (Consumer) ---
def handle_message(message: dict):
    """メッセージキューからコンテキストを抽出して処理"""
    carrier = message.get("_otel_context", {})
    ctx = propagate.extract(carrier)  # carrier からコンテキストを抽出

    tracer = trace.get_tracer("consumer")
    with tracer.start_as_current_span(
        "process_message",
        context=ctx,
        kind=trace.SpanKind.CONSUMER,
    ) as span:
        span.set_attribute("messaging.system", "custom_queue")
        process(message)

10. OpenTelemetry Collector アーキテクチャ

10.1 概要

OpenTelemetry Collector は、テレメトリデータの受信・処理・エクスポートを行う中間コンポーネントである。ベンダー非依存のプロキシとして機能し、テレメトリパイプラインの中核を担う。

10.2 パイプラインアーキテクチャ

┌──────────────────────────────────────────────────────────────────────┐
│                   OpenTelemetry Collector                             │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌────────────┐    ┌────────────────┐    ┌──────────────┐            │
│  │ Receivers  │───▶│  Processors    │───▶│  Exporters   │            │
│  │            │    │                │    │              │            │
│  │ ・OTLP     │    │ ・batch        │    │ ・OTLP       │            │
│  │ ・Prometheus│    │ ・memory_limiter│   │ ・Prometheus │            │
│  │ ・Kafka    │    │ ・attributes   │    │ ・Loki       │            │
│  │ ・hostmetrics│   │ ・filter      │    │ ・Kafka      │            │
│  │ ・filelog   │    │ ・tail_sampling│   │ ・debug      │            │
│  └────────────┘    └────────────────┘    └──────────────┘            │
│                                                                      │
│  ┌─────────────────────────────────────────┐                         │
│  │              Extensions                  │                         │
│  │  ・health_check  ・pprof  ・zpages      │                         │
│  │  ・memory_ballast ・oauth2client         │                         │
│  └─────────────────────────────────────────┘                         │
│                                                                      │
│  ┌─────────────────────────────────────────┐                         │
│  │              Connectors                  │                         │
│  │  ・spanmetrics  ・count  ・routing       │                         │
│  └─────────────────────────────────────────┘                         │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

10.3 デプロイパターン

パターン1: Agent(サイドカー / DaemonSet)

┌─────────────────────────────────────────────────────────┐
│                        Node                              │
│                                                          │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐                 │
│  │  App 1  │  │  App 2  │  │  App 3  │                 │
│  └────┬────┘  └────┬────┘  └────┬────┘                 │
│       │            │            │                        │
│       └────────────┼────────────┘                        │
│                    │                                     │
│              ┌─────▼─────┐                               │
│              │ Collector  │  ← Agent モード               │
│              │ (DaemonSet)│     ノードごとに1つ            │
│              └─────┬─────┘                               │
│                    │                                     │
└────────────────────┼─────────────────────────────────────┘
                     │ OTLP
                     ▼
              Observability Backend

メリット:

  • アプリケーションに最も近い場所で処理
  • ローカル通信のため低レイテンシ
  • ノードレベルのメトリクス(hostmetrics)も収集可能

パターン2: Gateway(中央集約)

┌──────────┐  ┌──────────┐  ┌──────────┐
│  App 1   │  │  App 2   │  │  App 3   │
└────┬─────┘  └────┬─────┘  └────┬─────┘
     │             │             │
     └─────────────┼─────────────┘
                   │ OTLP
                   ▼
         ┌─────────────────┐
         │   Collector      │  ← Gateway モード
         │   (Deployment)   │     クラスタで1-N個
         │                  │     ロードバランサー配下
         └────────┬────────┘
                  │
        ┌─────────┼─────────┐
        ▼         ▼         ▼
     Jaeger   Prometheus   Loki

メリット:

  • 中央での一括設定・管理
  • サンプリング等の高度な処理を一か所で実行
  • バックエンドへのコネクション数を削減

パターン3: Agent + Gateway(推奨構成)

┌─────────────────── Node 1 ────────────────────┐
│  ┌─────┐  ┌─────┐                             │
│  │App 1│  │App 2│                             │
│  └──┬──┘  └──┬──┘                             │
│     └────┬───┘                                │
│    ┌─────▼─────┐                              │
│    │ Agent     │ ← ローカル処理               │
│    │ Collector │   (バッチ、ベース属性付与)      │
│    └─────┬─────┘                              │
└──────────┼────────────────────────────────────┘
           │
┌──────────┼─── Node 2 ────────────────────────┐
│  ┌─────┐ │                                    │
│  │App 3│ │                                    │
│  └──┬──┘ │                                    │
│  ┌──▼──────▼──┐                               │
│  │ Agent      │                               │
│  │ Collector  │                               │
│  └─────┬──────┘                               │
└────────┼──────────────────────────────────────┘
         │
         │ OTLP (全ノードから)
         ▼
  ┌──────────────────┐
  │ Gateway Collector │ ← 中央処理
  │ (Deployment)      │   (サンプリング、ルーティング、
  │                   │    高度な加工)
  └────────┬─────────┘
           │
  ┌────────┼────────┐
  ▼        ▼        ▼
Tempo  Prometheus  Loki

10.4 Collector のディストリビューション

ディストリビューション説明
otelcol (Core)最小限のコンポーネントのみ含む公式ビルド
otelcol-contribコミュニティ提供のレシーバー・プロセッサー・エクスポーターを含む
Custom buildocb (OpenTelemetry Collector Builder) で必要なコンポーネントのみを含むカスタムビルド
# カスタム Collector のビルド例 (builder-config.yaml)
cat <<'EOF' > builder-config.yaml
dist:
  name: custom-otelcol
  output_path: ./dist
  otelcol_version: "0.96.0"

receivers:
  - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.96.0
  - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.96.0

processors:
  - gomod: go.opentelemetry.io/collector/processor/batchprocessor v0.96.0
  - gomod: go.opentelemetry.io/collector/processor/memorylimiterprocessor v0.96.0

exporters:
  - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.96.0
  - gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.96.0
EOF

# ビルド実行
ocb --config builder-config.yaml

11. Collector 設定例(フル構成)

以下は、本番環境を想定した包括的な Collector 設定例である。

# otel-collector-config.yaml — 包括的な設定例

# ==============================================================
# Extensions: ヘルスチェック、デバッグ、パフォーマンス
# ==============================================================
extensions:
  health_check:
    endpoint: "0.0.0.0:13133"
    path: "/health"
    check_collector_pipeline:
      enabled: true
      exporter_failure_threshold: 5

  pprof:
    endpoint: "0.0.0.0:1777"

  zpages:
    endpoint: "0.0.0.0:55679"

  memory_ballast:
    size_mib: 512  # GC 圧力を軽減

# ==============================================================
# Receivers: テレメトリデータの受信
# ==============================================================
receivers:
  # --- OTLP Receiver (gRPC + HTTP) ---
  otlp:
    protocols:
      grpc:
        endpoint: "0.0.0.0:4317"
        max_recv_msg_size_mib: 4
        keepalive:
          server_parameters:
            max_connection_age: 60s
            max_connection_age_grace: 5s
      http:
        endpoint: "0.0.0.0:4318"
        cors:
          allowed_origins:
            - "https://*.example.com"
          allowed_headers:
            - "Content-Type"
            - "X-Custom-Header"

  # --- Prometheus Receiver ---
  # Prometheus 形式のメトリクスをスクレイプ
  prometheus:
    config:
      scrape_configs:
        - job_name: "kubernetes-pods"
          scrape_interval: 30s
          kubernetes_sd_configs:
            - role: pod
          relabel_configs:
            - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
              action: keep
              regex: true
            - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
              action: replace
              target_label: __metrics_path__
              regex: (.+)
            - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port, __meta_kubernetes_pod_ip]
              action: replace
              regex: (\d+);(([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})
              replacement: "[$2]:$1"
              target_label: __address__

        - job_name: "otel-collector"
          scrape_interval: 15s
          static_configs:
            - targets: ["localhost:8888"]

  # --- Host Metrics Receiver ---
  # ホストレベルのシステムメトリクスを収集
  hostmetrics:
    collection_interval: 30s
    scrapers:
      cpu:
        metrics:
          system.cpu.utilization:
            enabled: true
      memory:
        metrics:
          system.memory.utilization:
            enabled: true
      disk:
      filesystem:
        exclude_mount_points:
          mount_points: ["/dev/*", "/proc/*", "/sys/*"]
          match_type: regexp
      load:
      network:
      paging:
      processes:

  # --- Kafka Receiver ---
  # Kafka からテレメトリデータを受信
  kafka:
    brokers:
      - "kafka-1:9092"
      - "kafka-2:9092"
      - "kafka-3:9092"
    topic: "otlp-spans"
    group_id: "otel-collector"
    encoding: otlp_proto
    protocol_version: "2.0.0"
    auth:
      sasl:
        mechanism: PLAIN
        username: "${env:KAFKA_USERNAME}"
        password: "${env:KAFKA_PASSWORD}"
      tls:
        insecure: false
        ca_file: /etc/ssl/certs/kafka-ca.pem

  # --- Filelog Receiver ---
  # ファイルからログを収集
  filelog:
    include:
      - /var/log/pods/*/*/*.log
    exclude:
      - /var/log/pods/*/otel-collector/*.log
    start_at: end
    include_file_path: true
    include_file_name: false
    operators:
      # Docker/CRI ログフォーマットのパース
      - type: router
        id: container_format
        routes:
          - output: parse_docker
            expr: 'body matches "^\\{"'
          - output: parse_cri
            expr: 'body matches "^[^ Z]+ "'
      - type: json_parser
        id: parse_docker
        output: extract_metadata
        timestamp:
          parse_from: attributes.time
          layout: "%Y-%m-%dT%H:%M:%S.%LZ"
      - type: regex_parser
        id: parse_cri
        regex: '^(?P<time>[^ Z]+) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) ?(?P<log>.*)$'
        output: extract_metadata
        timestamp:
          parse_from: attributes.time
          layout: "%Y-%m-%dT%H:%M:%S.%LZ"
      - type: move
        id: extract_metadata
        from: attributes.log
        to: body

# ==============================================================
# Processors: テレメトリデータの加工
# ==============================================================
processors:
  # --- Batch Processor ---
  # テレメトリをバッチ化してエクスポート効率を向上
  batch:
    send_batch_size: 8192
    send_batch_max_size: 16384
    timeout: 5s

  # --- Memory Limiter Processor ---
  # メモリ使用量を制限してOOMを防止
  # ※ パイプラインの最初に配置すること
  memory_limiter:
    check_interval: 1s
    limit_mib: 1536        # ハードリミット
    spike_limit_mib: 512   # スパイク許容量
    # limit_percentage: 75  # または割合で指定
    # spike_limit_percentage: 25

  # --- Attributes Processor ---
  # スパン/メトリクス/ログの属性を追加・更新・削除
  attributes:
    actions:
      # 環境情報を追加
      - key: deployment.environment
        value: "production"
        action: upsert
      # 機密情報をマスク
      - key: http.request.header.authorization
        action: delete
      - key: db.statement
        action: hash  # SQL をハッシュ化
      # 属性名の変換
      - key: http.status_code
        from_attribute: http.response.status_code
        action: upsert

  # --- Resource Processor ---
  # リソース属性を追加
  resource:
    attributes:
      - key: cloud.provider
        value: "aws"
        action: upsert
      - key: cloud.region
        value: "ap-northeast-1"
        action: upsert
      - key: k8s.cluster.name
        value: "production-cluster"
        action: upsert

  # --- Filter Processor ---
  # 不要なテレメトリを除外
  filter:
    error_mode: ignore
    traces:
      span:
        # ヘルスチェックのスパンを除外
        - 'attributes["http.route"] == "/health"'
        - 'attributes["http.route"] == "/ready"'
        - 'attributes["http.route"] == "/metrics"'
    metrics:
      metric:
        # 特定のメトリクスを除外
        - 'name == "http.server.duration" and resource.attributes["service.name"] == "debug-service"'
    logs:
      log_record:
        # DEBUG レベルのログを除外
        - 'severity_number < 9'

  # --- Tail Sampling Processor ---
  # トレース全体を見てからサンプリング判断
  # ※ Gateway Collector でのみ使用推奨
  tail_sampling:
    decision_wait: 30s
    num_traces: 100000
    expected_new_traces_per_sec: 1000
    policies:
      # ポリシー1: エラーを含むトレースは100%保持
      - name: errors-policy
        type: status_code
        status_code:
          status_codes:
            - ERROR
      # ポリシー2: 高レイテンシのトレースを保持
      - name: latency-policy
        type: latency
        latency:
          threshold_ms: 1000
      # ポリシー3: 特定のサービスは100%保持
      - name: critical-services
        type: string_attribute
        string_attribute:
          key: service.name
          values:
            - payment-service
            - auth-service
      # ポリシー4: その他は10%サンプリング
      - name: probabilistic-policy
        type: probabilistic
        probabilistic:
          sampling_percentage: 10
      # 複合ポリシー: AND 条件
      - name: composite-policy
        type: and
        and:
          and_sub_policy:
            - name: slow-traces
              type: latency
              latency:
                threshold_ms: 500
            - name: not-health-check
              type: string_attribute
              string_attribute:
                key: http.route
                values:
                  - "/health"
                  - "/ready"
                invert_match: true

  # --- Span Metrics Processor (Connector) ---
  # スパンからメトリクスを自動生成(RED メトリクス)
  # ※ 現在は Connector として実装
  # spanmetrics:
  #   metrics_exporter: prometheus
  #   dimensions:
  #     - name: http.method
  #     - name: http.route
  #     - name: http.status_code

  # --- K8s Attributes Processor ---
  # Kubernetes のメタデータをテレメトリに付与
  k8sattributes:
    auth_type: "serviceAccount"
    passthrough: false
    filter:
      node_from_env_var: KUBE_NODE_NAME
    extract:
      metadata:
        - k8s.namespace.name
        - k8s.pod.name
        - k8s.pod.uid
        - k8s.deployment.name
        - k8s.node.name
        - k8s.container.name
      labels:
        - tag_name: app.label.team
          key: team
          from: pod
        - tag_name: app.label.version
          key: app.kubernetes.io/version
          from: pod
      annotations:
        - tag_name: app.annotation.config
          key: config-hash
          from: pod
    pod_association:
      - sources:
          - from: resource_attribute
            name: k8s.pod.ip
      - sources:
          - from: resource_attribute
            name: k8s.pod.uid
      - sources:
          - from: connection

  # --- Transform Processor ---
  # OTTL (OpenTelemetry Transformation Language) でデータ変換
  transform:
    error_mode: ignore
    trace_statements:
      - context: span
        statements:
          - set(attributes["processed_by"], "otel-collector")
          - truncate_all(attributes, 256)
    log_statements:
      - context: log
        statements:
          - set(severity_text, "INFO") where severity_number >= 9 and severity_number < 13

# ==============================================================
# Exporters: テレメトリデータの送信先
# ==============================================================
exporters:
  # --- OTLP Exporter (gRPC) ---
  otlp:
    endpoint: "tempo.monitoring:4317"
    tls:
      insecure: false
      ca_file: /etc/ssl/certs/ca.pem
      cert_file: /etc/ssl/certs/client.pem
      key_file: /etc/ssl/certs/client-key.pem
    headers:
      Authorization: "Bearer ${env:OTLP_AUTH_TOKEN}"
    retry_on_failure:
      enabled: true
      initial_interval: 5s
      max_interval: 30s
      max_elapsed_time: 300s
    sending_queue:
      enabled: true
      num_consumers: 10
      queue_size: 5000
    compression: gzip

  # --- OTLP/HTTP Exporter ---
  otlphttp:
    endpoint: "https://otlp-gateway.example.com:4318"
    headers:
      X-API-Key: "${env:API_KEY}"
    compression: gzip

  # --- Prometheus Remote Write Exporter ---
  prometheusremotewrite:
    endpoint: "http://prometheus.monitoring:9090/api/v1/write"
    tls:
      insecure: true
    resource_to_telemetry_conversion:
      enabled: true
    add_metric_suffixes: true

  # --- Loki Exporter ---
  loki:
    endpoint: "http://loki.monitoring:3100/loki/api/v1/push"
    default_labels_enabled:
      exporter: true
      job: true
      instance: true
      level: true

  # --- Debug Exporter ---
  # 開発・デバッグ時にコンソール出力
  debug:
    verbosity: detailed
    sampling_initial: 5
    sampling_thereafter: 200

  # --- Kafka Exporter ---
  kafka:
    brokers:
      - "kafka-1:9092"
      - "kafka-2:9092"
    topic: "otlp-spans-processed"
    encoding: otlp_proto
    producer:
      max_message_bytes: 10000000
      compression: snappy

# ==============================================================
# Connectors: パイプライン間の接続
# ==============================================================
connectors:
  # Span から RED メトリクスを生成
  spanmetrics:
    histogram:
      explicit:
        buckets: [5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 2.5s, 5s, 10s]
    dimensions:
      - name: http.request.method
      - name: http.route
      - name: http.response.status_code
      - name: service.name
    exemplars:
      enabled: true
    metrics_flush_interval: 30s

  # カウントコネクター
  count:
    traces:
      span.count:
        description: "スパン数のカウント"
    logs:
      log.record.count:
        description: "ログレコード数のカウント"

# ==============================================================
# Service: パイプライン定義
# ==============================================================
service:
  extensions: [health_check, pprof, zpages, memory_ballast]

  telemetry:
    logs:
      level: "info"
      development: false
      encoding: "json"
    metrics:
      level: "detailed"
      address: "0.0.0.0:8888"

  pipelines:
    # --- トレースパイプライン ---
    traces:
      receivers: [otlp, kafka]
      processors:
        - memory_limiter    # 最初に配置
        - k8sattributes
        - resource
        - attributes
        - filter
        - tail_sampling
        - batch             # 最後に配置
      exporters: [otlp, debug]

    # --- メトリクスパイプライン ---
    metrics:
      receivers: [otlp, prometheus, hostmetrics]
      processors:
        - memory_limiter
        - k8sattributes
        - resource
        - attributes
        - filter
        - batch
      exporters: [prometheusremotewrite, debug]

    # --- メトリクスパイプライン (spanmetrics から) ---
    metrics/spanmetrics:
      receivers: [spanmetrics]
      processors: [batch]
      exporters: [prometheusremotewrite]

    # --- ログパイプライン ---
    logs:
      receivers: [otlp, filelog]
      processors:
        - memory_limiter
        - k8sattributes
        - resource
        - attributes
        - filter
        - transform
        - batch
      exporters: [loki, debug]

    # --- Connector: traces → spanmetrics → metrics ---
    traces/spanmetrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [spanmetrics]

12. Connectors

12.1 概要

Connector は OpenTelemetry Collector v0.86.0 で導入された機能で、あるパイプラインの出力を別のパイプラインの入力に接続する。Exporter と Receiver の両方の役割を持つ。

┌──────────────────────────────────────────────────────────────────┐
│                    Connector の役割                                │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Pipeline A (Traces)                  Pipeline B (Metrics)       │
│  ┌──────────┐  ┌──────────┐          ┌──────────┐               │
│  │ Receiver │─▶│Processor │─▶ ┌────────────┐ ─▶│ Exporter │    │
│  └──────────┘  └──────────┘   │ Connector  │    └──────────┘    │
│                                │(spanmetrics)│                    │
│                                │             │                    │
│                                │ Exporter側: │                    │
│                                │  traces受取 │                    │
│                                │ Receiver側: │                    │
│                                │  metrics生成│                    │
│                                └────────────┘                    │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

12.2 主な Connector

Connector入力 → 出力説明
spanmetricsTraces → Metricsスパンから RED メトリクスを生成
countAny → Metricsテレメトリアイテムのカウント
routingAny → Any条件に基づくルーティング
forwardAny → Anyパイプライン間のデータ転送
failoverAny → Anyフェイルオーバー処理
servicegraphTraces → Metricsサービスグラフメトリクスを生成

12.3 spanmetrics Connector の設定例

connectors:
  spanmetrics:
    histogram:
      explicit:
        buckets: [2ms, 4ms, 6ms, 8ms, 10ms, 50ms, 100ms, 200ms, 500ms, 1s, 5s, 10s]
    dimensions:
      - name: http.request.method
      - name: http.route
      - name: http.response.status_code
      - name: rpc.grpc.status_code
      - name: service.name
    dimensions_cache_size: 1000
    aggregation_temporality: "AGGREGATION_TEMPORALITY_CUMULATIVE"
    exemplars:
      enabled: true
    metrics_flush_interval: 15s
    namespace: "traces.spanmetrics"

service:
  pipelines:
    # トレースを受信 → spanmetrics Connector にエクスポート
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp, spanmetrics]  # spanmetrics は Exporter として動作

    # spanmetrics Connector からメトリクスを受信
    metrics/spanmetrics:
      receivers: [spanmetrics]        # spanmetrics は Receiver として動作
      processors: [batch]
      exporters: [prometheusremotewrite]

生成されるメトリクス:

  • traces.spanmetrics.calls — リクエスト数(Counter)
  • traces.spanmetrics.duration — レイテンシ分布(Histogram)
  • traces.spanmetrics.size — スパンサイズ(Histogram、オプション)

13. OTLP プロトコル (gRPC vs HTTP)

13.1 OTLP の概要

OTLP (OpenTelemetry Protocol) は、テレメトリデータを転送するためのOpenTelemetry 標準プロトコルである。

13.2 gRPC vs HTTP/protobuf vs HTTP/JSON

特性OTLP/gRPCOTLP/HTTP (protobuf)OTLP/HTTP (JSON)
ポート431743184318
エンコーディングProtocol BuffersProtocol BuffersJSON
パフォーマンス最高高い低い(デバッグ用)
ストリーミング対応非対応非対応
ロードバランサーL4/L7 (HTTP/2)L7L7
ファイアウォールHTTP/2 対応必要HTTP/1.1 対応HTTP/1.1 対応
圧縮gzipgzipgzip
ブラウザ対応非対応対応対応

13.3 エンドポイント

# gRPC
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317
  → traces:  http://collector:4317 (gRPC service)
  → metrics: http://collector:4317 (gRPC service)
  → logs:    http://collector:4317 (gRPC service)

# HTTP
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318
  → traces:  http://collector:4318/v1/traces   (POST)
  → metrics: http://collector:4318/v1/metrics  (POST)
  → logs:    http://collector:4318/v1/logs     (POST)

13.4 OTLP リクエスト/レスポンスの例

# HTTP/JSON リクエストの例 (POST /v1/traces)
{
  "resourceSpans": [
    {
      "resource": {
        "attributes": [
          { "key": "service.name", "value": { "stringValue": "order-service" } },
          { "key": "service.version", "value": { "stringValue": "2.1.0" } }
        ]
      },
      "scopeSpans": [
        {
          "scope": {
            "name": "order-service",
            "version": "2.1.0"
          },
          "spans": [
            {
              "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
              "spanId": "00f067aa0ba902b7",
              "parentSpanId": "",
              "name": "HTTP GET /api/orders",
              "kind": 2,
              "startTimeUnixNano": "1704067200000000000",
              "endTimeUnixNano": "1704067200350000000",
              "attributes": [
                { "key": "http.request.method", "value": { "stringValue": "GET" } },
                { "key": "http.response.status_code", "value": { "intValue": "200" } }
              ],
              "status": { "code": 1 }
            }
          ]
        }
      ]
    }
  ]
}

13.5 推奨選択基準

選択フローチャート:

ブラウザ/フロントエンドから送信?
  │
  ├─ Yes → OTLP/HTTP (protobuf or JSON)
  │
  └─ No
      │
      HTTP/2 がファイアウォールで制限?
      │
      ├─ Yes → OTLP/HTTP (protobuf)
      │
      └─ No
          │
          高スループットが必要?
          │
          ├─ Yes → OTLP/gRPC(推奨)
          │
          └─ No → OTLP/gRPC or HTTP(どちらでも可)

14. サンプリング戦略

14.1 サンプリングの種類

┌──────────────────────────────────────────────────────────────────┐
│                  サンプリングポイント                                │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  アプリケーション (SDK)           Collector (Gateway)              │
│  ┌─────────────────────┐        ┌─────────────────────┐          │
│  │  Head Sampling       │        │  Tail Sampling       │          │
│  │                     │        │                     │          │
│  │  • TraceIdRatio     │  OTLP  │  • 全スパン収集後に  │          │
│  │  • ParentBased      │ ──────▶│    判断             │          │
│  │  • AlwaysOn/Off     │        │  • エラー含むトレース │          │
│  │                     │        │    を100%保持       │          │
│  │  ⚡ 低オーバーヘッド  │        │  • 高レイテンシを保持│          │
│  │  ❌ 結果を見ずに判断 │        │                     │          │
│  └─────────────────────┘        │  ⚡ 正確な判断       │          │
│                                  │  ❌ 高メモリ使用量   │          │
│                                  └─────────────────────┘          │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

14.2 Head Sampling(SDK側)

from opentelemetry.sdk.trace.sampling import (
    TraceIdRatioBased,
    ParentBased,
    ALWAYS_ON,
    ALWAYS_OFF,
)

# 1. TraceIdRatioBased: トレースIDに基づく確率的サンプリング
sampler_10_percent = TraceIdRatioBased(0.1)  # 10%

# 2. ParentBased: 親スパンのサンプリング決定を尊重
sampler = ParentBased(
    root=TraceIdRatioBased(0.1),          # ルートスパン: 10%
    remote_parent_sampled=ALWAYS_ON,       # 親がサンプルされている: 常にON
    remote_parent_not_sampled=ALWAYS_OFF,  # 親がサンプルされていない: 常にOFF
    local_parent_sampled=ALWAYS_ON,
    local_parent_not_sampled=ALWAYS_OFF,
)

# TracerProvider に設定
from opentelemetry.sdk.trace import TracerProvider
provider = TracerProvider(sampler=sampler, resource=resource)

環境変数での設定:

export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.1"  # 10%

14.3 Tail Sampling(Collector側)

Tail Sampling は Collector の tail_sampling プロセッサーで実現する。トレース全体のスパンが揃ってからサンプリング判断を行うため、より賢い判断が可能である。

前述の Collector 設定例(セクション11)の tail_sampling プロセッサーを参照。

14.4 サンプリング戦略の選択指針

戦略適用場面メリットデメリット
Always On開発環境、低トラフィック全データ保持コスト大
Head (比率)高トラフィック環境低オーバーヘッドエラーを見逃す可能性
ParentBasedマイクロサービス一貫したサンプリングルートでの判断に依存
Tail Sampling本番環境(推奨)最適な判断メモリ・CPU消費大
Head + Tail大規模本番環境バランス良い設定が複雑

15. Kubernetes デプロイメント (OTel Operator)

15.1 OpenTelemetry Operator 概要

OpenTelemetry Operator は Kubernetes Operator パターンで実装され、以下の CRD(Custom Resource Definition)を提供する:

  • OpenTelemetryCollector: Collector の自動デプロイ・管理
  • Instrumentation: Pod への自動インストルメンテーション注入

15.2 Operator のインストール

# cert-manager のインストール(前提条件)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml

# OpenTelemetry Operator のインストール
kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml

# または Helm を使用
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm install opentelemetry-operator open-telemetry/opentelemetry-operator \
    --namespace opentelemetry-operator-system \
    --create-namespace \
    --set "manager.collectorImage.repository=otel/opentelemetry-collector-contrib" \
    --set admissionWebhooks.certManager.enabled=true

15.3 OpenTelemetryCollector CRD

DaemonSet モード(Agent)

apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
  name: otel-agent
  namespace: monitoring
spec:
  mode: daemonset  # daemonset | deployment | statefulset | sidecar
  image: otel/opentelemetry-collector-contrib:0.96.0

  # リソース制限
  resources:
    limits:
      cpu: "500m"
      memory: "512Mi"
    requests:
      cpu: "100m"
      memory: "128Mi"

  # 環境変数
  env:
    - name: KUBE_NODE_NAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName

  # ボリュームマウント(ホストログ収集用)
  volumeMounts:
    - name: varlog
      mountPath: /var/log
      readOnly: true
    - name: varlibdockercontainers
      mountPath: /var/lib/docker/containers
      readOnly: true

  volumes:
    - name: varlog
      hostPath:
        path: /var/log
    - name: varlibdockercontainers
      hostPath:
        path: /var/lib/docker/containers

  # Tolerations(全ノードにデプロイ)
  tolerations:
    - operator: Exists

  # Collector 設定
  config:
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: "0.0.0.0:4317"
          http:
            endpoint: "0.0.0.0:4318"
      hostmetrics:
        collection_interval: 30s
        scrapers:
          cpu: {}
          memory: {}
          disk: {}
          network: {}

    processors:
      memory_limiter:
        check_interval: 1s
        limit_mib: 400
        spike_limit_mib: 100
      batch:
        send_batch_size: 1024
        timeout: 5s
      k8sattributes:
        auth_type: serviceAccount
        extract:
          metadata:
            - k8s.namespace.name
            - k8s.pod.name
            - k8s.deployment.name
            - k8s.node.name

    exporters:
      otlp:
        endpoint: "otel-gateway.monitoring:4317"
        tls:
          insecure: true

    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [memory_limiter, k8sattributes, batch]
          exporters: [otlp]
        metrics:
          receivers: [otlp, hostmetrics]
          processors: [memory_limiter, k8sattributes, batch]
          exporters: [otlp]
        logs:
          receivers: [otlp]
          processors: [memory_limiter, k8sattributes, batch]
          exporters: [otlp]

Deployment モード(Gateway)

apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
  name: otel-gateway
  namespace: monitoring
spec:
  mode: deployment
  replicas: 3
  image: otel/opentelemetry-collector-contrib:0.96.0

  resources:
    limits:
      cpu: "2"
      memory: "4Gi"
    requests:
      cpu: "500m"
      memory: "1Gi"

  # Horizontal Pod Autoscaler
  autoscaler:
    minReplicas: 2
    maxReplicas: 10
    targetCPUUtilization: 70
    targetMemoryUtilization: 80

  # PodDisruptionBudget
  podDisruptionBudget:
    minAvailable: 1

  config:
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: "0.0.0.0:4317"

    processors:
      memory_limiter:
        check_interval: 1s
        limit_mib: 3072
        spike_limit_mib: 1024
      batch:
        send_batch_size: 8192
        timeout: 5s
      tail_sampling:
        decision_wait: 30s
        num_traces: 100000
        policies:
          - name: errors
            type: status_code
            status_code:
              status_codes: [ERROR]
          - name: slow
            type: latency
            latency:
              threshold_ms: 1000
          - name: default
            type: probabilistic
            probabilistic:
              sampling_percentage: 10

    exporters:
      otlp/tempo:
        endpoint: "tempo.monitoring:4317"
        tls:
          insecure: true
      prometheusremotewrite:
        endpoint: "http://prometheus.monitoring:9090/api/v1/write"
      loki:
        endpoint: "http://loki.monitoring:3100/loki/api/v1/push"

    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [memory_limiter, tail_sampling, batch]
          exporters: [otlp/tempo]
        metrics:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [prometheusremotewrite]
        logs:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [loki]

15.4 Instrumentation CRD(自動インストルメンテーション)

apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: otel-instrumentation
  namespace: default
spec:
  # 共通設定
  exporter:
    endpoint: "http://otel-agent.monitoring:4317"
  propagators:
    - tracecontext
    - baggage
  sampler:
    type: parentbased_traceidratio
    argument: "0.25"  # 25% サンプリング

  # リソース属性
  resource:
    addK8sUIDAttributes: true

  # 環境変数(全言語共通)
  env:
    - name: OTEL_EXPORTER_OTLP_TIMEOUT
      value: "10000"

  # --- 言語別設定 ---

  # Python
  python:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest
    env:
      - name: OTEL_PYTHON_LOG_CORRELATION
        value: "true"
      - name: OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED
        value: "true"

  # Java
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
    env:
      - name: OTEL_JAVAAGENT_DEBUG
        value: "false"
      - name: OTEL_INSTRUMENTATION_JDBC_ENABLED
        value: "true"

  # Node.js
  nodejs:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latest

  # .NET
  dotnet:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-dotnet:latest

  # Go (eBPF ベース)
  go:
    image: ghcr.io/open-telemetry/opentelemetry-go-instrumentation/autoinstrumentation-go:latest

15.5 Pod アノテーション

Pod またはNamespace にアノテーションを付与することで、自動インストルメンテーションを有効化する。

# --- Pod レベルでの有効化 ---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
      annotations:
        # Python アプリケーションの自動インストルメンテーション
        instrumentation.opentelemetry.io/inject-python: "true"

        # Java アプリケーションの場合
        # instrumentation.opentelemetry.io/inject-java: "true"

        # Node.js アプリケーションの場合
        # instrumentation.opentelemetry.io/inject-nodejs: "true"

        # .NET アプリケーションの場合
        # instrumentation.opentelemetry.io/inject-dotnet: "true"

        # Go アプリケーションの場合
        # instrumentation.opentelemetry.io/inject-go: "true"

        # 特定の Instrumentation CR を指定する場合
        # instrumentation.opentelemetry.io/inject-python: "monitoring/otel-instrumentation"

        # コンテナ名を指定(マルチコンテナ Pod の場合)
        # instrumentation.opentelemetry.io/container-names: "app,worker"

    spec:
      containers:
        - name: app
          image: order-service:2.1.0
          ports:
            - containerPort: 8080
          env:
            - name: OTEL_SERVICE_NAME
              value: "order-service"
          resources:
            limits:
              cpu: "500m"
              memory: "256Mi"
# --- Namespace レベルでの有効化 ---
# Namespace 内のすべての Pod に自動適用
apiVersion: v1
kind: Namespace
metadata:
  name: production
  annotations:
    instrumentation.opentelemetry.io/inject-java: "true"

15.6 Operator が行う処理

Operator は Pod アノテーションを検出すると、以下を自動的に行う:

  1. Init Container の注入: インストルメンテーションライブラリをダウンロード
  2. 環境変数の設定: OTEL_* 環境変数を Pod に注入
  3. ボリュームマウント: インストルメンテーションライブラリのマウント
  4. Java の場合: -javaagent JVM 引数を JAVA_TOOL_OPTIONS に追加
Operator が変換する Pod:

Before:                          After:
┌──────────────┐                ┌──────────────────────────┐
│ Container:   │                │ InitContainer:           │
│   app        │                │   otel-instrumentation   │
│              │                │   (ライブラリDL)          │
│              │                ├──────────────────────────┤
│              │                │ Container:               │
│              │                │   app                    │
│              │   ──Operator──▶│   env:                   │
│              │                │     OTEL_SERVICE_NAME    │
│              │                │     OTEL_EXPORTER_*      │
│              │                │     JAVA_TOOL_OPTIONS    │
│              │                │   volumeMounts:          │
│              │                │     /otel-auto-instr     │
└──────────────┘                └──────────────────────────┘

16. セマンティック規約 (Semantic Conventions)

16.1 概要

セマンティック規約は、テレメトリ属性の命名とデータ型を標準化する仕様である。これにより、異なるライブラリ・フレームワーク・言語間で一貫したテレメトリデータが生成される。

16.2 主要な属性カテゴリ

HTTP セマンティック規約

# HTTP サーバースパン属性(Stable)
http.request.method: "GET"                    # HTTP メソッド
http.response.status_code: 200                # ステータスコード
url.scheme: "https"                           # スキーム
url.path: "/api/orders/42"                    # パス
url.query: "include=items"                    # クエリ文字列
http.route: "/api/orders/{orderId}"           # ルートテンプレート
server.address: "api.example.com"             # サーバーホスト名
server.port: 443                              # サーバーポート
http.request.body.size: 1234                  # リクエストボディサイズ
http.response.body.size: 5678                 # レスポンスボディサイズ
network.protocol.name: "http"                 # プロトコル名
network.protocol.version: "1.1"               # プロトコルバージョン
user_agent.original: "Mozilla/5.0..."         # User-Agent
client.address: "192.168.1.1"                 # クライアントIP

データベースセマンティック規約

# データベーススパン属性
db.system: "postgresql"                       # DB システム
db.namespace: "orders_db"                     # DB 名/スキーマ
db.operation.name: "SELECT"                   # 操作名
db.query.text: "SELECT * FROM orders WHERE id = $1"  # クエリテキスト
db.query.parameter.id: "42"                   # クエリパラメータ
server.address: "db.example.com"              # DB ホスト
server.port: 5432                             # DB ポート
db.response.status_code: "00000"              # SQLSTATE

メッセージングセマンティック規約

# メッセージングスパン属性
messaging.system: "kafka"                     # メッセージングシステム
messaging.destination.name: "order-events"    # トピック/キュー名
messaging.operation.name: "publish"           # 操作 (publish/receive/process)
messaging.message.id: "msg-123"               # メッセージID
messaging.kafka.consumer.group: "order-processors"
messaging.kafka.message.offset: 42
messaging.kafka.destination.partition: 3
messaging.batch.message_count: 10             # バッチサイズ

リソースセマンティック規約

# サービス
service.name: "order-service"                 # 必須
service.version: "2.1.0"
service.namespace: "production"
service.instance.id: "order-service-7d9f8c6b5-x2k4m"

# Kubernetes
k8s.cluster.name: "production-cluster"
k8s.namespace.name: "orders"
k8s.deployment.name: "order-service"
k8s.pod.name: "order-service-7d9f8c6b5-x2k4m"
k8s.pod.uid: "abc-123-def-456"
k8s.container.name: "app"
k8s.node.name: "node-1"

# クラウド
cloud.provider: "aws"                         # aws / gcp / azure
cloud.region: "ap-northeast-1"
cloud.availability_zone: "ap-northeast-1a"
cloud.account.id: "123456789012"
cloud.platform: "aws_eks"

# ホスト
host.name: "ip-10-0-1-42"
host.id: "i-0abc123def456"
host.type: "m5.xlarge"
host.arch: "amd64"

# OS
os.type: "linux"
os.version: "5.15.0"

# プロセス
process.pid: 1234
process.command: "java"
process.runtime.name: "OpenJDK Runtime Environment"
process.runtime.version: "17.0.1"

# テレメトリ SDK
telemetry.sdk.name: "opentelemetry"
telemetry.sdk.language: "python"
telemetry.sdk.version: "1.22.0"

16.3 メトリクスセマンティック規約

# HTTP メトリクス(Stable)
http.server.request.duration:                 # Histogram (秒)
  unit: "s"
  description: "HTTP サーバーリクエストの処理時間"

http.server.active_requests:                  # UpDownCounter
  unit: "1"
  description: "アクティブな HTTP リクエスト数"

http.client.request.duration:                 # Histogram (秒)
  unit: "s"
  description: "HTTP クライアントリクエストの時間"

# RPC メトリクス
rpc.server.duration:                          # Histogram (ミリ秒)
  unit: "ms"
rpc.client.duration:                          # Histogram (ミリ秒)
  unit: "ms"

# システムメトリクス
system.cpu.utilization:                       # Gauge
  unit: "1"
system.memory.usage:                          # UpDownCounter
  unit: "By"
system.disk.io:                               # Counter
  unit: "By"

17. テレメトリ相関 (Exemplars)

17.1 Exemplars の概要

Exemplar はメトリクスデータポイントにトレースへのリンクを埋め込む仕組みである。集約されたメトリクスから個別のトレースにドリルダウンできるようになる。

┌──────────────────────────────────────────────────────────────────┐
│                    Exemplars の役割                                │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  メトリクス: http.server.request.duration                         │
│                                                                  │
│  Histogram:                                                      │
│    p50 = 45ms                                                    │
│    p90 = 120ms                                                   │
│    p99 = 850ms  ← この値に対応するトレースは?                      │
│        │                                                         │
│        ▼ Exemplar                                                │
│    ┌──────────────────────────────────────┐                      │
│    │ trace_id: 4bf92f...4736              │                      │
│    │ span_id:  00f067...02b7              │                      │
│    │ value:    847ms                      │                      │
│    │ timestamp: 2025-01-15T10:30:45Z      │                      │
│    │ attributes:                          │                      │
│    │   http.route: "/api/orders/{id}"     │                      │
│    └──────────────────────────────────────┘                      │
│        │                                                         │
│        ▼ リンク先のトレース                                       │
│    ┌──────────────────────────────────────┐                      │
│    │ Trace: 4bf92f...4736                 │                      │
│    │ ├─ HTTP GET /api/orders/42 (847ms)   │                      │
│    │ │  ├─ getOrder (780ms)               │                      │
│    │ │  │  ├─ DB query (650ms) ← ボトルネック!│                  │
│    │ │  │  └─ cache miss                   │                      │
│    │ │  └─ serialize (60ms)                │                      │
│    └──────────────────────────────────────┘                      │
│                                                                  │
│  メトリクス → Exemplar → トレース でドリルダウン                    │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

17.2 Exemplars の設定

from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.view import View
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader

# Exemplars はデフォルトで有効(サンプリングされたスパンに対して)
# SDK の設定時に自動的にトレースコンテキストが Exemplar として関連付けられる

# 環境変数での設定
# OTEL_METRICS_EXEMPLAR_FILTER=trace_based   # デフォルト: サンプリングされたトレースのみ
# OTEL_METRICS_EXEMPLAR_FILTER=always_on     # 常に Exemplar を記録
# OTEL_METRICS_EXEMPLAR_FILTER=always_off    # Exemplar を無効化

17.3 Grafana での活用

Grafana では、Prometheus メトリクスの Exemplar をクリックすると、対応するトレース(Tempo/Jaeger)に直接遷移できる。

Grafana Dashboard:
  ┌──────────────────────────────────────────┐
  │  HTTP Request Duration (p99)              │
  │                                           │
  │  ▄▄▄▄▆▆▆▇▇█████▇▇▆▆▄▄▄▄                │
  │  ▂▂▃▃▄▄▅▅▆▆▇▇████▇▇▆▆▅▅▄▄▃▃▂▂          │
  │       ◆  ◆    ◆◆ ◆  ◆     ← Exemplar点  │
  │       │                                   │
  │       ▼ クリック                            │
  │  ┌─────────────────────────────────┐      │
  │  │ Trace: 4bf92f...                │      │
  │  │ Duration: 847ms                 │      │
  │  │ Service: order-service          │      │
  │  │ [View in Tempo →]               │      │
  │  └─────────────────────────────────┘      │
  └──────────────────────────────────────────┘

18. ベストプラクティス

18.1 SDK 設定のベストプラクティス

カテゴリ推奨事項
Resourceservice.name は必ず設定する。deployment.environmentservice.version も推奨
サンプリング本番では parentbased_traceidratio を使用。100% サンプリングは開発環境のみ
バッチ処理BatchSpanProcessor を使用(SimpleSpanProcessor は開発環境のみ)
環境変数コード内のハードコーディングを避け、環境変数で設定する
シャットダウンアプリ終了時に必ず provider.shutdown() を呼ぶ(データ損失防止)

18.2 Collector 設定のベストプラクティス

# 推奨: Processor の順序
processors:
  # 1. memory_limiter: 最初に配置(OOM 防止)
  # 2. k8sattributes: メタデータ付与
  # 3. resource/attributes: 属性の追加・変更
  # 4. filter: 不要データの除外
  # 5. tail_sampling: サンプリング(Gateway のみ)
  # 6. batch: 最後に配置(エクスポート効率化)

18.3 命名規約

# スパン名
✅ "HTTP GET /api/orders/{orderId}"    # 低カーディナリティ
❌ "HTTP GET /api/orders/12345"         # 高カーディナリティ(具体的なID)

✅ "process_order"                       # 操作名
❌ "OrderService.processOrder()"         # クラス名.メソッド名は冗長

# 属性名
✅ "order.id"                            # セマンティック規約に準拠
✅ "http.request.method"                 # ドット区切り
❌ "order_id"                            # アンダースコア(非推奨)
❌ "orderID"                             # キャメルケース(非推奨)

# メトリクス名
✅ "http.server.request.duration"        # ドット区切り、単位は別途指定
❌ "http_server_request_duration_ms"     # 単位を名前に含めない

18.4 パフォーマンス考慮事項

項目推奨
属性のカーディナリティ属性値の種類を制限する(user_id 等は避ける)
スパンの粒度ループ内で大量のスパンを作らない
Baggage のサイズ最小限に保つ(全リクエストに付加される)
メトリクスの次元数属性の組み合わせ爆発に注意(カーディナリティ爆発)
ログの量高頻度のログはサンプリングを検討
Collector のリソースmemory_limiter は必ず設定する

18.5 セキュリティ考慮事項

# NG: 機密情報を属性に含めない
span.set_attribute("user.password", password)          # ❌
span.set_attribute("db.query.text", "... WHERE ssn='123-45-6789'")  # ❌
baggage.set("auth.token", "Bearer abc123")             # ❌

# OK: 機密情報をマスクまたは除外
# Collector の attributes processor でフィルタリング
processors:
  attributes:
    actions:
      - key: http.request.header.authorization
        action: delete
      - key: http.request.header.cookie
        action: delete
      - key: db.query.text
        action: hash

18.6 段階的な導入戦略

Phase 1: 基盤構築(1-2週間)
──────────────────────────
• OTel Collector を Agent + Gateway 構成でデプロイ
• 1-2 サービスで自動インストルメンテーションを有効化
• トレースバックエンド(Tempo/Jaeger)で確認

Phase 2: 可視性の拡大(2-4週間)
─────────────────────────────
• 全サービスに自動インストルメンテーションを展開
• メトリクスの収集を開始(Prometheus + OTel)
• ログの相関(trace_id 付きログ)を設定

Phase 3: 最適化(4-8週間)
────────────────────────
• Tail Sampling の導入
• 重要なサービスに手動インストルメンテーション追加
• ビジネスメトリクスの定義と収集
• ダッシュボードとアラートの構築

Phase 4: 高度な活用(継続的)
──────────────────────────
• Exemplars によるメトリクス → トレースの相関
• spanmetrics Connector で RED メトリクス自動生成
• SLO (Service Level Objectives) の監視
• 障害調査ワークフローの確立

まとめ

OpenTelemetry は、クラウドネイティブなオブザーバビリティの標準として急速に普及している。本ガイドで解説した主要なポイントを振り返る:

  1. ベンダー非依存の標準: 一度のインストルメンテーションで、任意のバックエンドにデータを送信可能
  2. 三本柱の統合: トレース・メトリクス・ログを統一的なフレームワークで扱う
  3. 自動 + 手動: 自動インストルメンテーションで迅速に可視性を獲得し、手動で補完
  4. Collector の柔軟性: Receiver → Processor → Exporter パイプラインで自在にデータを加工・転送
  5. Kubernetes ネイティブ: Operator と CRD による宣言的な管理
  6. 段階的な導入: 小さく始めて、段階的に深い可視性を獲得する

SRE チームにとって、OpenTelemetry は障害の早期検知・迅速な原因特定・システムの信頼性向上に不可欠なツールとなるだろう。