Skip to main content

OpenTelemetry で分散トレーシングを実装する: 入門から実践まで

マイクロサービスアーキテクチャでは、1 つのリクエストが複数のサービスを横断して処理されます。問題が発生した際に「どのサービスの、どの処理で遅延が発生しているのか」を特定するのは容易ではありません。CNCF Graduated プロジェクトである OpenTelemetry は、分散トレーシングを標準化し、この課題を解決するオブザーバビリティフレームワークです。Kubo のような K3s ベースの Kubernetes 環境でも、OpenTelemetry を導入することでサービス間のリクエストフローを完全に可視化できます。

分散トレーシングの基本概念

なぜ分散トレーシングが必要なのか

The New Stack の解説が指摘するように、Kubernetes 上のマイクロサービス環境では、メトリクスとログだけでは不十分です。あるリクエストが Pod 間、ノード間、Namespace 間を横断する際の全体像を把握するには、分散トレーシングが不可欠です。

OpenTelemetry のテレメトリシグナル

OpenTelemetry はベンダーニュートラルなオブザーバビリティフレームワークで、3 つのテレメトリシグナルを統合的に扱います:

  • トレース(Traces): リクエストのエンドツーエンドのフローを追跡。Span の親子関係でサービス間の呼び出しチェーンを表現
  • メトリクス(Metrics): 定量的な測定値。リクエスト数、レイテンシ、エラー率など
  • ログ(Logs): イベントの記録。トレース ID と相関付けることで、特定のリクエストに関連するログを特定

トレーシングの構成要素

text
Trace
├── Span A (API Gateway, 150ms)
│   ├── Span B (Auth Service, 20ms)
│   └── Span C (Product Service, 100ms)
│       ├── Span D (Database Query, 40ms)
│       └── Span E (Cache Lookup, 5ms)
  • Trace: 1 つのリクエストの全体的な処理フロー
  • Span: Trace 内の個々の操作単位。開始時刻、終了時刻、属性、イベントを持つ
  • Context: Trace ID と Span ID を含む情報。サービス間で伝播される

Captain.AI はトレーシングデータを AI で分析し、パフォーマンスボトルネックの自動検出と最適化の提案を行います。

OpenTelemetry Collector の構成

OpenTelemetry Collector は、テレメトリデータの受信、処理、エクスポートを担うベンダー非依存のコンポーネントです。Uptrace の解説Logit.io の実装ガイドを参考に構成を解説します。

Collector のアーキテクチャ

text
Receivers → Processors → Exporters
  (OTLP)     (batch)      (Jaeger)
  (Zipkin)   (filter)     (Tempo)
  (Jaeger)   (sampling)   (OTLP)

Kubernetes へのデプロイ(DaemonSet 方式)

yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-collector
  namespace: observability
spec:
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      containers:
      - name: collector
        image: otel/opentelemetry-collector-contrib:0.98.0
        args: ["--config=/conf/otel-collector-config.yaml"]
        volumeMounts:
        - name: config
          mountPath: /conf
      volumes:
      - name: config
        configMap:
          name: otel-collector-config

Collector の設定例

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-config
  namespace: observability
data:
  otel-collector-config.yaml: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318

    processors:
      batch:
        timeout: 5s
        send_batch_size: 1024
      memory_limiter:
        check_interval: 1s
        limit_mib: 512
      tail_sampling:
        decision_wait: 10s
        policies:
        - name: errors-policy
          type: status_code
          status_code: {status_codes: [ERROR]}
        - name: slow-traces
          type: latency
          latency: {threshold_ms: 1000}
        - name: probabilistic
          type: probabilistic
          probabilistic: {sampling_percentage: 10}

    exporters:
      otlp/tempo:
        endpoint: tempo:4317
        tls:
          insecure: true
      otlp/jaeger:
        endpoint: jaeger-collector:4317
        tls:
          insecure: true

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

Kubo で運用する K3s クラスタでは、DaemonSet 方式が推奨されます。ノードごとに 1 つの Collector を配置し、リソース効率を最大化します。

アプリケーションの計装

自動計装(Zero-Code Instrumentation)

OpenTelemetry の自動計装を使えば、コードを変更せずにトレーシングを導入できます。

Python の例:

bash
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
bash
# 環境変数での設定
export OTEL_SERVICE_NAME=my-python-service
export OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
export OTEL_TRACES_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=otlp

# 自動計装で起動
opentelemetry-instrument python app.py

Java の例:

bash
# Java Agent を使った自動計装
java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=my-java-service \
  -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
  -jar my-app.jar

Kubernetes Operator による自動注入

Medium の実装記事が解説するように、OpenTelemetry Operator を使えば Pod のアノテーションで自動計装を注入できます:

yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    instrumentation.opentelemetry.io/inject-python: "true"
spec:
  containers:
  - name: my-app
    image: my-python-app:latest

手動計装の例(Go)

go
import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/trace"
)

func handleRequest(ctx context.Context) {
    tracer := otel.Tracer("my-service")
    ctx, span := tracer.Start(ctx, "handleRequest")
    defer span.End()

    // カスタム属性の追加
    span.SetAttributes(
        attribute.String("user.id", userID),
        attribute.Int("http.status_code", 200),
    )

    // 子 Span の作成
    ctx, childSpan := tracer.Start(ctx, "database-query")
    result := queryDatabase(ctx)
    childSpan.End()
}

Captain.AI と OpenTelemetry を組み合わせることで、AI がトレースデータからサービス間のボトルネックを自動的に特定し、改善提案を生成することが可能です。

バックエンド連携:Jaeger と Grafana Tempo

Jaeger との連携

Jaeger は CNCF Graduated の分散トレーシングバックエンドです。Medium のステップバイステップガイドを参考に:

yaml
# Jaeger Operator によるデプロイ
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: jaeger
  namespace: observability
spec:
  strategy: production
  storage:
    type: elasticsearch
    options:
      es.server-urls: http://elasticsearch:9200

Grafana Tempo との連携

Civo の実践ガイドが詳しく解説するように、Grafana Tempo は大規模なトレースデータの保存に最適化されたバックエンドです:

yaml
# Tempo の設定例
apiVersion: v1
kind: ConfigMap
metadata:
  name: tempo-config
data:
  tempo.yaml: |
    server:
      http_listen_port: 3200
    distributor:
      receivers:
        otlp:
          protocols:
            grpc:
    storage:
      trace:
        backend: s3
        s3:
          bucket: tempo-traces
          endpoint: minio:9000

コンテキスト伝播

dasroot.net の解説が強調するように、分散トレーシングで最も重要なのが コンテキスト伝播 です。W3C Trace Context 標準に基づくヘッダーを使用します:

text
traceparent: 00-{trace-id}-{span-id}-{flags}
tracestate: vendor-specific-data

すべてのサービスがこのヘッダーを伝播することで、エンドツーエンドのトレースが完成します。

サンプリング戦略とパフォーマンス最適化

テールサンプリング

すべてのトレースを保存するとストレージコストが爆発的に増加します。markaicode の実装ガイドが推奨するテールサンプリング戦略:

yaml
processors:
  tail_sampling:
    decision_wait: 10s
    policies:
    # エラーを含むトレースは 100% 保存
    - name: errors
      type: status_code
      status_code: {status_codes: [ERROR]}
    # 1秒以上かかったトレースは 100% 保存
    - name: slow-traces
      type: latency
      latency: {threshold_ms: 1000}
    # その他は 10% サンプリング
    - name: probabilistic
      type: probabilistic
      probabilistic: {sampling_percentage: 10}

パフォーマンス最適化のポイント

  • バッチ処理: Collector でバッチサイズとタイムアウトを適切に設定
  • メモリリミッター: OOM を防止するためのメモリ制限設定
  • DaemonSet デプロイ: サイドカーよりリソース効率が高い
  • 属性の最適化: 不要な属性を削除し、ペイロードサイズを削減

Andrew Odendaal のガイドによると、適切なサンプリング戦略により、トレースデータ量を 90% 削減しつつ、重要なトレースは 100% 保持できます。

まとめ

OpenTelemetry による分散トレーシングは、マイクロサービス環境のオブザーバビリティを根本的に改善します。本記事のポイントをまとめると:

  1. OpenTelemetry はトレース・メトリクス・ログを統合するベンダーニュートラルなフレームワーク
  2. Collector による柔軟なテレメトリパイプラインの構築
  3. 自動計装 でコード変更なしにトレーシングを導入可能
  4. Jaeger / Grafana Tempo との連携でトレースの保存・可視化
  5. テールサンプリング でコストを抑えつつ重要なトレースを保持

Kubo は K3s ベースで CNCF エコシステムとの高い親和性を持ち、OpenTelemetry の導入によりマイクロサービス環境の可視性を飛躍的に向上させることができます。分散システムのオブザーバビリティに取り組む方は、ぜひ Kubo をご検討ください。

AI によるトレースデータの自動分析に興味がある方は、Captain.AI の詳細をご確認ください。導入のご相談はお問い合わせからお気軽にどうぞ。

← Back to all posts