OpenTelemetry

メトリクス、ログ、トレースを統一的に収集するオブザーバビリティのオープン標準

オブザーバビリティ基礎
OpenTelemetry」の技術書を見る →

OpenTelemetry とは

OpenTelemetry (OTel) は、CNCF がホストするオブザーバビリティのオープン標準で、メトリクス、ログ、トレースを統一的な API・SDK・プロトコルで収集する。計装コード (何を span にするか、どの属性を付けるか) はアプリ側に残したまま送信先だけを設定で差し替えられる構造になっているため、バックエンド (X-Ray, Datadog, Grafana) を変えてもアプリのコードは書き換えずに済む。

前身は OpenTracing と OpenCensus という 2 つの別プロジェクトで、「計装の標準がない」という同じ問題に別々に取り組んでいたものを統合したのが OTel である。CNCF には 2019 年 5 月に受け入れられ、2021 年 8 月に Incubating、2026 年 5 月 11 日に Graduated (卒業) へ移行した。2026 年 8 月時点では仕様としては 3 シグナル (トレース・メトリクス・ログ) が揃っているが、言語 SDK ごとの成熟度は揃っていない。公式のステータス表では Java と .NET はログまで Stable、JavaScriptPython のログは Development、Go のログは Beta である。「OTel を入れればログも同じ経路で送れる」と決めた後に言語側の未成熟が判明する、という手戻りが起きやすい。

アーキテクチャ

構成は「計装 (アプリ内) → OTLP → Collector → バックエンド」の 3 段に分かれる。アプリがバックエンドの API を直接叩かず、いったん OTLP という共通形式で Collector に渡すのが要点で、送信先の追加・変更・多重送信は Collector の設定だけで完結する。

アプリケーション (OTel SDK)
  ↓ OTLP (OpenTelemetry Protocol)
OTel Collector
  ↓ エクスポーター
CloudWatch / X-Ray / Datadog / Grafana
コンポーネント役割
API計装のインターフェース
SDKAPI の実装 (言語ごと)
Collectorテレメトリの受信・処理・送信
OTLP標準プロトコル (gRPC は 4317 番、HTTP は 4318 番が既定)
セマンティック規約属性名・メトリクス名の共通語彙 (http.request.method 等)

AWS では ADOT (AWS Distro for OpenTelemetry) の Lambda Layer が用意されているが、Layer を関連付けるだけでは何も起きない。自動計装はランタイムを包むラッパーとして差し込まれる仕組みなので、環境変数 AWS_LAMBDA_EXEC_WRAPPER に /opt/otel-instrument を設定して初めて有効になる (送信先に応じた IAM 権限も別途必要)。設定を忘れるとデプロイは成功しトレースだけが届かない、という気づきにくい失敗になる。

手動計装

自動計装は HTTP クライアントや DB ドライバーといったライブラリの境界を捉えるだけなので、「注文処理 1 件」のような業務上の単位は手動で span を作る。

import { trace, SpanStatusCode } from '@opentelemetry/api';

const tracer = trace.getTracer('order-service');

async function processOrder(orderId: string) {
  const span = tracer.startSpan('processOrder');
  span.setAttribute('order.id', orderId);
  try {
    const result = await db.getItem(orderId);
    span.setStatus({ code: SpanStatusCode.OK });
    return result;
  } catch (e) {
    span.recordException(e as Error);
    span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
    throw e;
  } finally {
    span.end();
  }
}

startSpan で作った span は「現在の span」にはならない点が最大の落とし穴である。この関数の中でさらに startSpan を呼んでも親子関係は結ばれず、別のトレース ID を持つ独立した span に分かれてしまう。入れ子にしたいなら startActiveSpan を使うか、context.with(trace.setSpan(context.active(), span), ...) の内側で子を作る。例外は recordException で記録して初めてイベントとして残る (setStatus が持つのはステータスコードとメッセージだけ)。

OTel vs ベンダー固有 SDK

ベンダー固有 SDK は自社バックエンドの機能へ最短で届く代わりに、乗り換えのコストが計装コード全体に乗る。OTel はその依存を Collector の設定 1 箇所に押し込む取引だと考えるとよい。

観点OpenTelemetryベンダー固有
ロックインなしあり
バックエンド切り替えCollector の設定変更のみコード変更が必要
機能の豊富さ標準的ベンダー独自機能
コミュニティCNCF (大規模)ベンダー依存

Collector の設定

Collector の設定は receiver (受け取る)・processor (加工する)・exporter (送り出す) と、シグナル間を橋渡しする connector の 4 分類からなる。定義しただけでは動かず、service.pipelines に列挙したものだけが有効になる。使われない定義が残っていても黙って無視されるため、設定ミスが表に出にくい。

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
processors:
  batch:
    timeout: 5s
exporters:
  awsxray: {}
  awsemf: {}
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [awsxray]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [awsemf]

batch を挟むのは体裁ではなく実利で、span 1 件ごとに送信 API を叩く形を避けて呼び出し回数を落とす。配置は各ホストや各 Pod に相乗りさせる agent パターンと、集約用に独立させる gateway パターンの 2 系統があり、後述のテールサンプリングのように「1 トレース分の span が同じインスタンスに揃っている」ことを前提にする処理は gateway 側に置く。

AWS での OTel

AWS 側は OTel の受け皿を既存サービスに割り当てる形になっている (トレースは X-Ray、メトリクスとログは CloudWatch)。

サービス連携
ADOT CollectorECS, EKS, Lambda で動作
X-Rayトレースのバックエンド
CloudWatchメトリクス・ログのバックエンド
Managed Grafanaダッシュボード

運用で最初に効いてくるのはサンプリングの設計である。全リクエストの span を送ればバックエンドの取り込み量はリクエスト数に比例して増えるので、SDK 側で確率的に間引く head sampling か、Collector 側でトレース全体を見てからエラーや遅い経路だけを残す tail sampling を選ぶ。後者は判断のために 1 トレース分の span を溜める必要があるため、Collector を複数台に並べるならトレース ID で振り分ける段を手前に置かないと、同じトレースの span が別インスタンスへ散って判定できなくなる。

この記事は役に立ちましたか?

関連用語

関連する記事