LabHub

ブログ

OpenTelemetry Collectorパイプライン設計と運用ガイド:収集からバックエンド連携まで

한국어English日本語

OpenTelemetry Collector Pipeline

はじめに

分散システムの複雑度が増すほど、可観測性 (Observability) の重要性は指数関数的に大きくなる。数百のマイクロサービスが相互作用する環境で、Traces、Metrics、Logs という三つのテレメトリシグナルを統合的に収集し、加工し、適切なバックエンドへルーティングするパイプラインの設計は、プラットフォームエンジニアリングの中核的な能力である。

OpenTelemetry Collector は CNCF プロジェクトとして、ベンダーニュートラルなテレメトリパイプラインを提供する。特定のモニタリングソリューションに縛られることなく、Receiver でさまざまなフォーマットのデータを収集し、Processor で加工とフィルタリングを行ったうえで、Exporter で任意のバックエンドへ送信する柔軟なアーキテクチャを備えている。2026 年現在、Collector は v0.120 以上へと成熟し、プロダクション環境での安定性は十分に検証されている。

この記事では、OpenTelemetry Collector の内部アーキテクチャから、Receiver、Processor、Exporter の実践的な設定、Agent/Gateway のデプロイパターン、Kubernetes 環境における DaemonSet/Deployment マニフェスト、Tail Sampling 戦略、メモリ管理とバックプレッシャーのメカニズム、トラブルシューティングガイド、そして障害復旧手順まで、運用の観点で必要になる内容をすべて扱う。

OpenTelemetry Collector アーキテクチャ

主要コンポーネント構成

OpenTelemetry Collector のアーキテクチャは四つの主要コンポーネントで構成される。Receiver が外部ソースからテレメトリデータを受信し、Processor がデータを加工し、Exporter が最終的な宛先へ送信する。さらに Extension が付加的な機能 (ヘルスチェック、認証、zPages など) を提供する。

[Application / Infrastructure]
        |
        v
+-------------------+
|    Receivers       |  <-- OTLP, Prometheus, Filelog, Kafka, etc.
+-------------------+
        |
        v
+-------------------+
|    Processors      |  <-- Memory Limiter, Batch, Attributes, Tail Sampling
+-------------------+
        |
        v
+-------------------+
|    Exporters       |  <-- OTLP, Prometheus Remote Write, Loki, Kafka, etc.
+-------------------+

+-------------------+
|    Extensions      |  <-- Health Check, zPages, pprof, Bearer Token Auth
+-------------------+

一つの Collector インスタンスの中に複数のパイプラインを定義できる。各パイプラインは一つのシグナルタイプ (traces、metrics、logs) を処理し、それぞれ異なる Receiver、Processor、Exporter の組み合わせを持てる。この設計のおかげで、トレースは Tempo へ、メトリクスは Mimir へ、ログは Loki へそれぞれルーティングする構成が、単一の Collector 設定ファイルの中で実現できる。

Core と Contrib のディストリビューション

OpenTelemetry Collector は二つのディストリビューションを提供する。

項目CoreContrib
含まれるコンポーネント主要な Receiver/Processor/Exporter のみコミュニティ提供コンポーネントを多数含む
バイナリサイズ約 50MB約 200MB+
セキュリティ表面積小さい広い
更新周期隔週隔週
プロダクション推奨Custom Build 推奨テスト/PoC に適する
主なユースケース最小限のコンポーネントのみ必要な場合多様なソース/宛先の連携が必要な場合

プロダクション環境では、OpenTelemetry Collector Builder (OCB) を使って必要なコンポーネントだけを含むカスタムバイナリをビルドすることが、セキュリティと性能の両面から推奨される。

データモデルとシグナルタイプ

Collector の内部でデータは pdata (Pipeline Data) 形式で表現される。この内部データモデルは OTLP (OpenTelemetry Protocol) のプロトコルバッファ定義を基礎としており、三つのシグナルタイプをサポートする。

Receiver の設定

Receiver はテレメトリデータの入口である。Push 方式 (OTLP、Kafka など) と Pull 方式 (Prometheus、hostmetrics など) の両方をサポートし、同じタイプの Receiver を名前を変えて複数定義できる。

OTLP Receiver

OTLP は OpenTelemetry のネイティブプロトコルで、gRPC と HTTP/protobuf の二つの転送方式を提供する。ほとんどの OpenTelemetry SDK は OTLP Exporter をデフォルトで使うため、この Receiver はほぼすべての Collector 構成に含まれる。

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
        max_recv_msg_size_mib: 8 # 大容量バッチを許可
        max_concurrent_streams: 256 # 同時ストリーム数
        keepalive:
          server_parameters:
            max_connection_idle: 30s
            max_connection_age: 60s
            max_connection_age_grace: 10s
          enforcement_policy:
            min_time: 10s
            permit_without_stream: true
      http:
        endpoint: 0.0.0.0:4318
        cors:
          allowed_origins:
            - 'https://*.company.com'
          allowed_headers:
            - 'Content-Type'
            - 'X-Custom-Header'
          max_age: 600

gRPC 転送は HTTP/2 ベースでバイナリシリアライズと多重化をサポートし、大容量テレメトリに効率的だ。HTTP 転送はブラウザベースの計測 (Web SDK) やファイアウォール制約のある環境で使う。

Prometheus Receiver

Prometheus Receiver は既存の Prometheus エコシステムとの互換性を提供する。Prometheus の scrape_configs 文法をそのまま使えるため、これまで Prometheus で収集していたメトリクスを Collector 経由で別のバックエンドへルーティングできる。

receivers:
  prometheus:
    config:
      scrape_configs:
        - job_name: 'kubernetes-pods'
          scrape_interval: 30s
          scrape_timeout: 10s
          kubernetes_sd_configs:
            - role: pod
          relabel_configs:
            # prometheus.io/scrape アノテーションを持つ Pod のみスクレイピング
            - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
              action: keep
              regex: true
            # カスタムポートの指定
            - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
              action: replace
              target_label: __address__
              regex: (.+)
              replacement: '$$1'
            # ネームスペースラベルを追加
            - source_labels: [__meta_kubernetes_namespace]
              action: replace
              target_label: namespace
            # Pod 名ラベルを追加
            - source_labels: [__meta_kubernetes_pod_name]
              action: replace
              target_label: pod
        - job_name: 'node-exporter'
          scrape_interval: 15s
          static_configs:
            - targets: ['node-exporter.monitoring.svc:9100']

Filelog Receiver

Filelog Receiver はファイルシステム上のログファイルをリアルタイムに収集する。Kubernetes 環境でコンテナログを収集する中核コンポーネントであり、オペレーターチェーンを通じてログのパース、フィルタリング、変換を行う。

receivers:
  filelog:
    include:
      - /var/log/pods/*/*/*.log
    exclude:
      - /var/log/pods/*/otel-collector*/*.log
      - /var/log/pods/kube-system_*/*/*.log
    start_at: end # 新規ログのみ収集 (beginning なら既存ログから)
    include_file_path: true
    include_file_name: false
    retry_on_failure:
      enabled: true
      initial_interval: 1s
      max_interval: 30s
    operators:
      # CRI ログフォーマットのパース (containerd)
      - type: regex_parser
        id: parser-cri
        regex: '^(?P<time>[^ Z]+Z) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) ?(?P<log>.*)$'
        timestamp:
          parse_from: attributes.time
          layout: '%Y-%m-%dT%H:%M:%S.%fZ'
      # JSON ログ本文のパース
      - type: json_parser
        id: parser-json
        parse_from: attributes.log
        parse_to: body
        on_error: send_quiet # パース失敗時は原本を保持
      # 重大度のマッピング
      - type: severity_parser
        parse_from: attributes.level
        mapping:
          fatal: [FATAL, fatal, F]
          error: [ERROR, error, E]
          warn: [WARN, warn, W]
          info: [INFO, info, I]
          debug: [DEBUG, debug, D]

Receiver タイプ比較表

Receiver タイプ方式シグナル主な用途
otlpPushTraces, Metrics, LogsOTel SDK で計測したアプリケーション
prometheusPullMetricsPrometheus 互換メトリクスのスクレイピング
filelogPullLogsコンテナ/ファイルログの収集
hostmetricsPullMetricsCPU、Memory、Disk、Network のホストメトリクス
k8s_eventsPullLogsKubernetes イベントの収集
kafkaPushTraces, Metrics, LogsKafka トピックからのテレメトリ消費
zipkinPushTracesZipkin フォーマットのトレース受信
jaegerPushTracesJaeger フォーマットのトレース受信

Processor パイプライン

Processor は Receiver と Exporter の間でデータを加工する中間レイヤーである。順序が重要で、パイプラインに定義された順にチェーンされて実行される。一般的に推奨される Processor の順序は以下の通りだ。

memory_limiter -> k8sattributes -> resourcedetection -> attributes -> filter -> tail_sampling -> batch

Memory Limiter Processor

Memory Limiter は Collector の OOM (Out of Memory) を防ぐ安全装置だ。必ず Processor チェーンの最初に配置する必要があり、メモリ使用量がしきい値に達するとデータを拒否してプロセスを保護する。

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 1800 # ハードリミット (コンテナ limit の 80%)
    spike_limit_mib: 500 # 突発的なスパイクの許容量
    # limit_percentage: 80     # または比率ベースの設定 (cgroup 認識)
    # spike_limit_percentage: 25

limit_mib はコンテナのメモリ limit のおよそ 80% 程度に設定する。例えばコンテナの limit が 2Gi なら limit_mib を 1600-1800 に設定する。残りの 20% は Go ランタイムとその他の内部バッファのための余裕である。spike_limit_mib は瞬間的なトラフィックバーストを許容しつつ limit_mib を超えないようにする緩衝の役割を果たす。

Batch Processor

Batch Processor は個々のテレメトリレコードをまとめてバッチ単位で Exporter に渡す。このバッチングはネットワークリクエスト数を減らし圧縮効率を高めるため、パイプライン全体のスループットを大きく向上させる。

processors:
  batch:
    timeout: 5s # 最大待機時間
    send_batch_size: 8192 # バッチサイズ (レコード数)
    send_batch_max_size: 16384 # 最大バッチサイズ (このサイズを超えると分割)
  # Gateway ではより大きなバッチ
  batch/gateway:
    timeout: 10s
    send_batch_size: 16384
    send_batch_max_size: 32768

timeout に先に到達するか send_batch_size に先に到達するか、いずれかの条件を満たした時点でバッチが送信される。トラフィックが少ない環境では timeout が、トラフィックが多い環境では send_batch_size が主に働く。

Attributes Processor

Attributes Processor はテレメトリデータの属性 (Attribute) を追加、修正、削除する。機微情報の除去、環境情報のタグ付け、ラベルの正規化などに使う。

processors:
  attributes/security:
    actions:
      # 機微な HTTP ヘッダーを削除
      - key: http.request.header.authorization
        action: delete
      - key: http.request.header.cookie
        action: delete
      # DB クエリのハッシュ化 (機微データの保護)
      - key: db.statement
        action: hash
      # 環境タグを追加
      - key: deployment.environment
        action: upsert
        value: production
      # IP アドレスのマスキング
      - key: net.peer.ip
        action: extract
        pattern: '^(?P<subnet>\d+\.\d+\.\d+)\.\d+$'
      - key: net.peer.ip
        action: delete
      - key: net.peer.subnet
        from_attribute: subnet
        action: upsert

Tail Sampling Processor

Tail Sampling はトレース全体の Span がすべて収集されたあとにサンプリングの判断を下す方式だ。Head Sampling と違い、エラーが発生したトレースやレスポンスが遅いトレースを漏れなく保存できるため、プロダクション環境でデバッグ能力とコスト削減を同時に達成できる。

processors:
  tail_sampling:
    decision_wait: 30s # トレース完了の待機時間
    num_traces: 200000 # メモリに保持する最大トレース数
    expected_new_traces_per_sec: 5000
    policies:
      # ポリシー 1: エラーを含むトレースは 100% 保存
      - name: error-traces
        type: status_code
        status_code:
          status_codes: [ERROR]
      # ポリシー 2: 2 秒以上かかったトレースは 100% 保存
      - name: high-latency
        type: latency
        latency:
          threshold_ms: 2000
          upper_threshold_ms: 0 # 0 なら上限なし
      # ポリシー 3: 重要サービスは 50% 保存
      - name: critical-services
        type: and
        and:
          and_sub_policy:
            - name: service-match
              type: string_attribute
              string_attribute:
                key: service.name
                values:
                  - payment-service
                  - auth-service
                  - order-service
            - name: sample-half
              type: probabilistic
              probabilistic:
                sampling_percentage: 50
      # ポリシー 4: 特定の HTTP パスを除外 (health check など)
      - name: drop-health-checks
        type: string_attribute
        string_attribute:
          key: http.route
          values:
            - /healthz
            - /readyz
            - /livez
          invert_match: true
      # ポリシー 5: 残りのトラフィックは 5% のみサンプリング
      - name: default-sampling
        type: probabilistic
        probabilistic:
          sampling_percentage: 5

Tail Sampling の最も重要な注意点は、同一 TraceID のすべての Span が同一の Collector インスタンスに到達しなければならないことだ。Gateway が複数台ある場合は、必ず TraceID ベースの一貫性ハッシュ (ロードバランサーの consistent hashing) が必要になる。

Processor タイプ比較表

Processor役割必須かどうか推奨する配置位置
memory_limiterOOM 防止必須最優先 (最初)
k8sattributesK8s メタデータの注入推奨memory_limiter の次
resourcedetectionクラウド/ホスト情報の注入推奨k8sattributes の次
attributes属性の追加/修正/削除任意中間
filter不要データのドロップ任意サンプリングの前
tail_samplingトレース単位のサンプリング任意 (traces)batch の前
transformOTTL ベースの変換任意状況に応じて
batchバッチ処理必須最後

Exporter の設定

Exporter は加工済みのテレメトリデータを最終的な宛先へ送信する役割を担う。同じ Exporter タイプを名前を変えて複数のバックエンドへ同時に送信でき、retry と queue の設定で送信の安定性を確保する。

OTLP Exporter

OTLP Exporter は別の Collector (Gateway) や OTLP をネイティブにサポートするバックエンド (Tempo、Jaeger、SigNoz など) へデータを送信する。

exporters:
  # Traces -> Grafana Tempo
  otlp/tempo:
    endpoint: tempo-distributor.observability.svc:4317
    tls:
      insecure: true # クラスタ内部通信
    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
    timeout: 30s

  # Traces -> Jaeger (OTLP ネイティブ対応)
  otlp/jaeger:
    endpoint: jaeger-collector.observability.svc:4317
    tls:
      cert_file: /certs/client.crt
      key_file: /certs/client.key
      ca_file: /certs/ca.crt

Prometheus Remote Write Exporter

Prometheus Remote Write Exporter はメトリクスを Prometheus 互換のバックエンド (Mimir、Thanos、Cortex、VictoriaMetrics) へ送信する。

exporters:
  prometheusremotewrite/mimir:
    endpoint: https://mimir.observability.svc:9009/api/v1/push
    tls:
      insecure: false
      cert_file: /certs/client.crt
      key_file: /certs/client.key
    headers:
      X-Scope-OrgID: 'tenant-production'
    resource_to_telemetry_conversion:
      enabled: true # Resource 属性をメトリックラベルに変換
    external_labels:
      cluster: 'prod-ap-northeast-2'
      region: 'ap-northeast-2'
    retry_on_failure:
      enabled: true
      initial_interval: 5s
      max_interval: 60s
    sending_queue:
      enabled: true
      num_consumers: 5
      queue_size: 10000

Loki Exporter

Loki Exporter はログデータを Grafana Loki へ送信する。ラベルマッピングの設定が重要で、過度なラベルカーディナリティは Loki の性能を低下させるため注意が必要だ。

exporters:
  loki:
    endpoint: https://loki-gateway.observability.svc:3100/loki/api/v1/push
    headers:
      X-Scope-OrgID: 'tenant-production'
    default_labels_enabled:
      exporter: false
      job: true
      instance: true
      level: true
    retry_on_failure:
      enabled: true
      initial_interval: 5s
      max_interval: 30s
    sending_queue:
      enabled: true
      num_consumers: 5
      queue_size: 5000

主要バックエンド比較表

バックエンドシグナルプロトコル主な特徴Exporter タイプ
Grafana TempoTracesOTLP gRPCオブジェクトストレージ、コスト効率otlp
Grafana MimirMetricsPrometheus Remote WritePrometheus 互換、マルチテナントprometheusremotewrite
Grafana LokiLogsHTTP Pushラベルベースのインデックス、低コストloki
JaegerTracesOTLP gRPCトレース専用、UI 内蔵otlp
ElasticsearchLogsHTTP全文検索に強いelasticsearch
SigNozAllOTLP gRPCオールインワン、ClickHouse ベースotlp
DatadogAllHTTPSaaS、豊富な統合datadog

Agent と Gateway のデプロイパターン

OpenTelemetry Collector をデプロイする方式は、大きく Agent パターン、Gateway パターン、そして両者を組み合わせたハイブリッドパターンに分かれる。プロダクション環境では Agent + Gateway の組み合わせが最も広く使われており、それぞれのパターンの長所と短所を理解したうえで、トラフィック規模に合わせて選択する必要がある。

パターン別比較

特性Agent (DaemonSet)Gateway (Deployment)Agent + Gateway
デプロイ方式各ノードに 1 つクラスタ内の独立サービス二層の組み合わせ
収集範囲ローカルノードクラスタ全体ローカル収集 + 中央処理
Tail Sampling不可 (トレースが分散)可能 (中央集中)Gateway で実施
リソース使用ノード数だけ分散集中分散 + 集中
障害の影響範囲該当ノードのみパイプライン全体隔離可能
スケーリングノード追加時に自動HPA で水平拡張独立したスケーリング
複雑度低い中間高い
推奨トラフィック小規模中規模中~大規模

Agent + Gateway ハイブリッドアーキテクチャ

[Node 1]                    [Node 2]                    [Node N]
+----------+               +----------+               +----------+
| App Pods |               | App Pods |               | App Pods |
+----+-----+               +----+-----+               +----+-----+
     |                          |                          |
+----+-----+               +----+-----+               +----+-----+
| OTel     |               | OTel     |               | OTel     |
| Agent    |               | Agent    |               | Agent    |
| (DaemonSet)              | (DaemonSet)              | (DaemonSet)
+----+-----+               +----+-----+               +----+-----+
     |                          |                          |
     +------------+-------------+-----------+--------------+
                  |                         |
           +------+------+          +------+------+
           | OTel Gateway |          | OTel Gateway |
           | (Deployment) |          | (Deployment) |
           +------+------+          +------+------+
                  |                         |
     +------------+-------------------------+
     |             |              |
+----+----+  +----+----+  +-----+-----+
|  Tempo  |  |  Mimir  |  |   Loki    |
+---------+  +---------+  +-----------+

Agent では軽量な処理 (メモリ制限、基本的なバッチング、K8s メタデータの注入) だけを行い、Gateway が Tail Sampling、高度なフィルタリング、最終的なバックエンドへのルーティングを担当する。この分離によって Agent のリソース使用量を最小限に抑えつつ、Gateway で緻密なデータ処理を行える。

Kubernetes 環境へのデプロイ

Agent DaemonSet マニフェスト

apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
  name: otel-agent
  namespace: observability
spec:
  mode: daemonset
  image: otel/opentelemetry-collector-contrib:0.120.0
  serviceAccount: otel-collector-agent
  env:
    - name: K8S_NODE_NAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName
    - name: K8S_POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP
  resources:
    requests:
      cpu: 200m
      memory: 256Mi
    limits:
      cpu: 500m
      memory: 512Mi
  volumeMounts:
    - name: varlogpods
      mountPath: /var/log/pods
      readOnly: true
    - name: varlibdockercontainers
      mountPath: /var/lib/docker/containers
      readOnly: true
  volumes:
    - name: varlogpods
      hostPath:
        path: /var/log/pods
    - name: varlibdockercontainers
      hostPath:
        path: /var/lib/docker/containers
  tolerations:
    - operator: Exists
  config:
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318
      filelog:
        include:
          - /var/log/pods/*/*/*.log
        exclude:
          - /var/log/pods/observability_otel-*/*/*.log
        start_at: end
        include_file_path: true
        operators:
          - type: regex_parser
            id: parser-cri
            regex: '^(?P<time>[^ Z]+Z) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) ?(?P<log>.*)$'
            timestamp:
              parse_from: attributes.time
              layout: '%Y-%m-%dT%H:%M:%S.%fZ'
      hostmetrics:
        collection_interval: 30s
        scrapers:
          cpu: {}
          memory: {}
          disk: {}
          network: {}
          load: {}
          filesystem:
            exclude_mount_points:
              mount_points: ['/dev/*', '/proc/*', '/sys/*']
              match_type: regexp

    processors:
      memory_limiter:
        check_interval: 1s
        limit_mib: 400
        spike_limit_mib: 100
      k8sattributes:
        auth_type: serviceAccount
        passthrough: false
        extract:
          metadata:
            - k8s.namespace.name
            - k8s.deployment.name
            - k8s.statefulset.name
            - k8s.daemonset.name
            - k8s.pod.name
            - k8s.pod.uid
            - k8s.node.name
            - k8s.container.name
          labels:
            - tag_name: app.label.team
              key: team
              from: pod
        pod_association:
          - sources:
              - from: resource_attribute
                name: k8s.pod.ip
          - sources:
              - from: connection
      batch:
        timeout: 5s
        send_batch_size: 4096

    exporters:
      otlp/gateway:
        endpoint: otel-gateway.observability.svc.cluster.local:4317
        tls:
          insecure: true
        retry_on_failure:
          enabled: true
          initial_interval: 5s
          max_interval: 30s
        sending_queue:
          enabled: true
          queue_size: 2000

    extensions:
      health_check:
        endpoint: 0.0.0.0:13133

    service:
      extensions: [health_check]
      pipelines:
        traces:
          receivers: [otlp]
          processors: [memory_limiter, k8sattributes, batch]
          exporters: [otlp/gateway]
        metrics:
          receivers: [otlp, hostmetrics]
          processors: [memory_limiter, k8sattributes, batch]
          exporters: [otlp/gateway]
        logs:
          receivers: [otlp, filelog]
          processors: [memory_limiter, k8sattributes, batch]
          exporters: [otlp/gateway]
      telemetry:
        logs:
          level: warn
        metrics:
          address: 0.0.0.0:8888

Gateway Deployment マニフェスト

apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
  name: otel-gateway
  namespace: observability
spec:
  mode: deployment
  replicas: 3
  image: otel/opentelemetry-collector-contrib:0.120.0
  serviceAccount: otel-collector-gateway
  resources:
    requests:
      cpu: '1'
      memory: 2Gi
    limits:
      cpu: '2'
      memory: 4Gi
  autoscaler:
    minReplicas: 3
    maxReplicas: 10
    targetCPUUtilization: 70
    targetMemoryUtilization: 80
  podDisruptionBudget:
    minAvailable: 2
  config:
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
            max_recv_msg_size_mib: 16

    processors:
      memory_limiter:
        check_interval: 1s
        limit_mib: 3200
        spike_limit_mib: 800
      resourcedetection:
        detectors: [env, system, gcp, aws, azure]
        timeout: 5s
        override: false
      attributes/security:
        actions:
          - key: http.request.header.authorization
            action: delete
          - key: db.statement
            action: hash
      filter/metrics:
        metrics:
          exclude:
            match_type: regexp
            metric_names:
              - 'go_.*'
              - 'process_.*'
              - 'promhttp_.*'
      tail_sampling:
        decision_wait: 30s
        num_traces: 200000
        expected_new_traces_per_sec: 5000
        policies:
          - name: error-traces
            type: status_code
            status_code:
              status_codes: [ERROR]
          - name: high-latency
            type: latency
            latency:
              threshold_ms: 2000
          - name: default-sampling
            type: probabilistic
            probabilistic:
              sampling_percentage: 10
      batch:
        timeout: 10s
        send_batch_size: 16384
        send_batch_max_size: 32768

    exporters:
      otlp/tempo:
        endpoint: tempo-distributor.observability.svc:4317
        tls:
          insecure: true
        sending_queue:
          enabled: true
          num_consumers: 10
          queue_size: 10000
      prometheusremotewrite/mimir:
        endpoint: http://mimir-distributor.observability.svc:8080/api/v1/push
        headers:
          X-Scope-OrgID: 'production'
        resource_to_telemetry_conversion:
          enabled: true
        sending_queue:
          enabled: true
          num_consumers: 5
          queue_size: 10000
      loki:
        endpoint: http://loki-gateway.observability.svc:3100/loki/api/v1/push
        headers:
          X-Scope-OrgID: 'production'
        sending_queue:
          enabled: true
          queue_size: 5000

    extensions:
      health_check:
        endpoint: 0.0.0.0:13133
      zpages:
        endpoint: 0.0.0.0:55679
      pprof:
        endpoint: 0.0.0.0:1777

    service:
      extensions: [health_check, zpages, pprof]
      pipelines:
        traces:
          receivers: [otlp]
          processors: [memory_limiter, resourcedetection, attributes/security, tail_sampling, batch]
          exporters: [otlp/tempo]
        metrics:
          receivers: [otlp]
          processors: [memory_limiter, resourcedetection, filter/metrics, batch]
          exporters: [prometheusremotewrite/mimir]
        logs:
          receivers: [otlp]
          processors: [memory_limiter, resourcedetection, attributes/security, batch]
          exporters: [loki]
      telemetry:
        logs:
          level: info
        metrics:
          address: 0.0.0.0:8888

Gateway のロードバランシングと TraceID ベースのルーティング

Tail Sampling を使う Gateway が複数台ある場合、同一 TraceID の Span が異なる Gateway インスタンスへ分散すると、サンプリングの判断が不完全になる。この問題を解決するため、Agent から Gateway へ送信する際に loadbalancing Exporter を使う。

# Agent 側の Exporter 設定 (Gateway へ送信する場合)
exporters:
  loadbalancing:
    protocol:
      otlp:
        tls:
          insecure: true
        timeout: 10s
    resolver:
      dns:
        hostname: otel-gateway-headless.observability.svc.cluster.local
        port: 4317
      # または Kubernetes resolver を使用
      # k8s:
      #   service: otel-gateway
      #   ports:
      #     - 4317
    routing_key: traceID # TraceID ベースの一貫性ハッシュ

この設定を使えば、同一 TraceID を持つすべての Span が同じ Gateway Pod へルーティングされ、Tail Sampling の正確性が保証される。

メモリ管理とバックプレッシャー

OpenTelemetry Collector の運用で最も頻繁に発生する問題はメモリ関連の問題だ。テレメトリトラフィックの急増、Tail Sampling の待機バッファ、sending queue の蓄積などが複合的にメモリ使用量を押し上げる。

メモリ使用量の算定式

Collector のメモリ使用量を見積もる際は、次の要素を考慮する必要がある。

総メモリ = Go ランタイム基本 (約 50MB)
          + Receiver バッファ (接続数 x メッセージサイズ)
          + Processor バッファ
            - Batch: send_batch_max_size x レコード平均サイズ
            - Tail Sampling: num_traces x トレース平均サイズ
          + Exporter キュー: queue_size x バッチサイズ x レコード平均サイズ
          + 内部オーバーヘッド (10-20%)

Tail Sampling が最大のメモリ消費要因だ。decision_wait の時間だけトレースをメモリに保持するため、decision_wait が 30 秒で毎秒 5,000 件の新しいトレースが流入すると、およそ 150,000 件のトレースが同時にメモリ上に存在することになる。

バックプレッシャーのメカニズム

Collector のバックプレッシャー (Backpressure) は三段階で動作する。

第 1 段階では、Exporter の sending queue がいっぱいになると Exporter が Processor に圧力を伝える。第 2 段階では、memory_limiter がメモリ使用量が limit_mib に達したことを検知し、Receiver にデータ拒否のシグナルを送る。第 3 段階では、Receiver がデータを拒否するとクライアント (SDK または Agent) にエラーを返し、クライアントの retry ロジックが動作する。

このバックプレッシャーの連鎖が正しく動作するには、memory_limiter が必ず Processor チェーンの最初に位置していなければならない。そうでなければ、メモリ制限が働く前に別の Processor がメモリを使い果たし、OOM が発生しうる。

GOGC チューニング

Go ランタイムのガベージコレクタのチューニングもメモリ管理では重要だ。GOGC のデフォルト値は 100 で、これはヒープサイズが前回の GC サイクル比で 100% 増加すると GC をトリガーすることを意味する。メモリの余裕が少ない環境では GOGC を下げて、より頻繁な GC を促すことができる。

env:
  - name: GOGC
    value: '80' # デフォルト値 100 から 80 に下げる
  - name: GOMEMLIMIT
    value: '3600MiB' # ソフトメモリ上限 (limit の 90%)

トラブルシューティングガイド

自己テレメトリメトリクスによる診断

Collector は自身のテレメトリメトリクスをデフォルトポート 8888 で提供する。このメトリクスを Prometheus でスクレイピングし、Grafana ダッシュボードで監視することが必須だ。

# 受信メトリクス
otelcol_receiver_accepted_spans          # Receiver が受け入れた Spanotelcol_receiver_refused_spans           # Receiver が拒否した Span  (バックプレッシャー)
otelcol_receiver_accepted_metric_points  # 受け入れたメトリックポイント数
otelcol_receiver_accepted_log_records    # 受け入れたログレコード数

# Processor メトリクス
otelcol_processor_dropped_spans          # Processor でドロップされた Spanotelcol_processor_batch_batch_send_size  # 実際に送信されたバッチサイズ

# Exporter メトリクス
otelcol_exporter_sent_spans              # Exporter が送信に成功した Spanotelcol_exporter_send_failed_spans       # 送信に失敗した Spanotelcol_exporter_queue_size              # 現在キューで待機中の項目数
otelcol_exporter_queue_capacity          # キューの最大容量

主要なアラートの式は以下の通りだ。

zPages を活用したリアルタイムデバッグ

zPages 拡張を有効にすると、ブラウザから Collector の内部状態をリアルタイムで確認できる。

よくある問題と解決法

問題 1: Collector が OOM で再起動を繰り返す

原因の多くは、Tail Sampling の num_traces が大きすぎるか decision_wait が長すぎて、メモリに過剰なトレースが蓄積されるケースだ。num_traces を減らすか decision_wait を 10-15 秒に短縮し、memory_limiter の limit_mib をコンテナ limit の 75% まで下げる。

問題 2: Exporter で "context deadline exceeded" エラー

バックエンドの応答が timeout 内に返ってこない場合に発生する。Exporter の timeout 値を増やすか、sending_queue の num_consumers を増やして同時送信数を上げる。根本的にはバックエンドの処理能力をスケールアップする必要がある。

問題 3: トレースで Span が欠落する

Tail Sampling の Gateway が複数台あるのに TraceID ベースのルーティングが設定されていない場合に発生する。Agent で loadbalancing Exporter を使い、TraceID ベースの一貫性ハッシュを適用する。

問題 4: Prometheus Receiver で "context canceled" エラー

scrape_timeout が scrape_interval 以上の場合に発生する。scrape_timeout を scrape_interval の 50-80% 程度に設定する。

運用チェックリスト

プロダクション環境で OpenTelemetry Collector を安定して運用するためのチェックリストだ。

デプロイ前チェックリスト

モニタリング設定

スケーリング戦略

セキュリティチェック

障害事例と復旧

事例 1: Tail Sampling のメモリ暴走による連鎖障害

状況: Gateway 3 台で Tail Sampling を運用中、ブラックフライデーのトラフィック急増でトレースの流入量が平常時の 5 倍に増えた。decision_wait が 30 秒、num_traces が 500,000 に設定されていたが、実際にメモリへ保持されたトレース数が num_traces を超え、メモリ使用量が急騰した。

症状: Gateway Pod が順番に OOM Kill され、再起動を繰り返した。再起動した Pod にトラフィックが集中し、連鎖的に OOM が発生するドミノ現象が起きた。

復旧手順:

  1. 緊急対応として Tail Sampling を無効化し、probabilistic head sampling (10%) へ切り替えてトレースの流入量を即座に減らした。
  2. Gateway のメモリ limit を 4Gi から 8Gi へ増設し、replicas を 3 から 6 へ拡張した。
  3. decision_wait を 30 秒から 15 秒へ短縮し、num_traces を 200,000 に調整したうえで Tail Sampling を再有効化した。

教訓: Tail Sampling のメモリ使用量はトラフィックに線形に比例する。最大トラフィックのシナリオに対する負荷テストを必ず実施し、num_traces と decision_wait は保守的に設定する必要がある。

事例 2: Exporter キューの飽和によるデータ欠損

状況: Tempo バックエンドの Ingester がディスクフルによって応答が遅くなった。Collector の otlp/tempo Exporter で timeout エラーが発生し、sending queue が急速に埋まった。

症状: Exporter queue がいっぱいになり、新しいトレースデータがドロップされ始めた。otelcol_exporter_send_failed_spans メトリクスが急騰し、otelcol_exporter_queue_size が queue_capacity に達した。

復旧手順:

  1. Tempo Ingester のディスクを拡張し、問題となった Ingester Pod を再起動した。
  2. Collector の sending_queue サイズを 5,000 から 20,000 へ一時的に増やし、バッファリングの余裕を確保した。
  3. retry_on_failure の max_elapsed_time を 600 秒へ延ばし、バックエンドの復旧時間の間リトライを維持した。

教訓: sending_queue はバックエンドの一時的な障害に対する緩衝の役割しか果たさない。長時間のバックエンド障害ではキューが必ず飽和するため、バックエンドの監視と迅速な障害対応が根本的な解決策になる。重要なデータについては、Kafka を中間バッファとして使い永続性を保証するアーキテクチャも検討すべきだ。

事例 3: K8s Attributes Processor の権限不足で Pod メタデータが注入されない

状況: k8sattributes Processor を設定したが、namespace や pod name などのメタデータがテレメトリに注入されなかった。

症状: トレースとログで k8s.namespace.name、k8s.pod.name などの属性が空だった。Collector のログに "error": "forbidden" というメッセージが出力されていた。

復旧: Collector の ServiceAccount に、Pods、Namespaces、ReplicaSets に対する get、list、watch 権限を持つ ClusterRole をバインドして解決した。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: otel-collector
rules:
  - apiGroups: ['']
    resources: ['pods', 'namespaces', 'nodes']
    verbs: ['get', 'list', 'watch']
  - apiGroups: ['apps']
    resources: ['replicasets', 'deployments', 'statefulsets', 'daemonsets']
    verbs: ['get', 'list', 'watch']
  - apiGroups: ['batch']
    resources: ['jobs', 'cronjobs']
    verbs: ['get', 'list', 'watch']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: otel-collector
subjects:
  - kind: ServiceAccount
    name: otel-collector-agent
    namespace: observability
roleRef:
  kind: ClusterRole
  name: otel-collector
  apiGroup: rbac.authorization.k8s.io

設定検証とテスト

プロダクションへデプロイする前に Collector 設定ファイルの妥当性を検証することが重要だ。設定ミスによる Collector の起動失敗は、テレメトリパイプライン全体の停止につながる。

# 設定ファイルの構文検証
otelcol validate --config=config.yaml

# ドライランモードでの起動テスト
otelcol --config=config.yaml --dry-run

# Docker を使ったローカルテスト
docker run --rm \
  -v $(pwd)/config.yaml:/etc/otelcol/config.yaml \
  otel/opentelemetry-collector-contrib:0.120.0 \
  validate --config=/etc/otelcol/config.yaml

CI/CD パイプラインに設定検証のステップを組み込めば、誤った設定がプロダクションへデプロイされることを事前に防げる。Helm Chart を使う場合は、helm template でレンダリングしたあとに生成される ConfigMap の設定ファイルに対して validate を実行する。

高度な運用テクニック

マルチテナント環境でのルーティング

複数のチームが共有する Collector で、テナントごとにデータを分離して異なるバックエンドへ送信する必要がある場合は、routing Connector を活用する。

connectors:
  routing:
    table:
      - statement: route() where attributes["team"] == "platform"
        pipelines: [traces/platform]
      - statement: route() where attributes["team"] == "payments"
        pipelines: [traces/payments]
    default_pipelines: [traces/default]

service:
  pipelines:
    traces/ingress:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [routing]
    traces/platform:
      receivers: [routing]
      processors: [batch]
      exporters: [otlp/tempo-platform]
    traces/payments:
      receivers: [routing]
      processors: [batch]
      exporters: [otlp/tempo-payments]
    traces/default:
      receivers: [routing]
      processors: [batch]
      exporters: [otlp/tempo-default]

Collector 自体の監視ダッシュボードで押さえるべきパネル

運用向けの Grafana ダッシュボードに必ず含めるべき主要パネルの一覧だ。

参考資料

コメント

まだコメントはありません。

ログインするとコメントできます