OpenTelemetry完全ガイド2026:分散トレーシング・メトリクス・ログの統合可観測性をOSSで実現
オープンソースラボ編集部 ・ 2026年6月13日
OpenTelemetry完全ガイド2026:分散トレーシング・メトリクス・ログの統合可観測性をOSSで実現
マイクロサービスが増えると「本番でエラーが起きたが、どのサービスで遅延が発生したか分からない」「APIのレイテンシーが突然上がったが原因が追えない」問題が深刻になります。OpenTelemetry(OTel)はCNCF(Cloud Native Computing Foundation)のプロジェクトで、分散トレーシング・メトリクス・ログを統一的に収集してDatadog・Grafana・Jaeger・OpenSearchに送る業界標準OSSです。
OpenTelemetryとは
OpenTelemetryはクラウドネイティブアプリケーションの「可観測性(Observability)」を実現するためのSDK・API・コレクター(収集エージェント)のOSSです。Traces・Metrics・Logsの3つのシグナルを統一的に扱い、特定のベンダーに依存しない形で監視データを収集できます。
3つのシグナル:
- Traces: リクエスト1件がどのサービス・関数を通り、どこで何秒かかったかの追跡(分散トレーシング)
- Metrics: CPU・メモリ・リクエスト数・エラー率等の数値時系列データ
- Logs: アプリケーションが出力するテキストの記録(構造化ログ)
OpenTelemetryを使う理由
- ベンダーロックイン解消: Datadog・New Relic・Grafana等をバックエンドとして自由に切り替えられる
- 自動計装: Node.js・Python・Java等のSDKが一般的なライブラリ(Express・Django・Spring等)のトレースを自動生成
- 標準化: CNCF卒業プロジェクト。AWS・Google Cloud・Azure・Grafana・Datadogが公式サポート
- コスト最適化: オープンなプロトコルで複数のバックエンドに送れるため、安いバックエンドに切り替え可能
- OSSスタック: Jaeger(トレース)+ Prometheus(メトリクス)+ Loki(ログ)と組み合わせてフルOSSの可観測性スタックが構築できる
Node.js / Next.jsでのOpenTelemetry実装
// Next.jsにOpenTelemetryを統合する(自動計装)
// npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
// npm install @opentelemetry/exporter-otlp-grpc @opentelemetry/resources
// npm install @opentelemetry/semantic-conventions
// instrumentation.ts(Next.js 13+ App Routerのinstrumentation hook)
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';
import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
export function register() {
const sdk = new NodeSDK({
resource: new Resource({
[SEMRESATTRS_SERVICE_NAME]: 'my-nextjs-app',
[SEMRESATTRS_SERVICE_VERSION]: process.env.npm_package_version,
}),
// OpenTelemetry Collector(またはTempoやJaeger)にトレースを送信
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://otel-collector:4318/v1/traces',
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://otel-collector:4318/v1/metrics',
}),
exportIntervalMillis: 30000, // 30秒ごとにメトリクスをエクスポート
}),
// Node.js標準ライブラリ・Express・HTTPクライアント・DBを自動計装
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': { enabled: true },
'@opentelemetry/instrumentation-dns': { enabled: true },
'@opentelemetry/instrumentation-pg': { enabled: true }, // PostgreSQL
'@opentelemetry/instrumentation-redis': { enabled: true },
'@opentelemetry/instrumentation-fetch': { enabled: true },
}),
],
});
sdk.start();
process.on('SIGTERM', () => sdk.shutdown());
}
// カスタムスパン・メトリクス・ログを手動で計装する
import { trace, metrics, context, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('my-app', '1.0.0');
const meter = metrics.getMeter('my-app', '1.0.0');
// カスタムメトリクス(ビジネス指標)
const orderCounter = meter.createCounter('orders.created', {
description: '作成された注文数',
unit: '{orders}',
});
const paymentHistogram = meter.createHistogram('payment.duration', {
description: '決済処理の所要時間',
unit: 'ms',
});
// ビジネスロジックのトレース+メトリクス計測例
async function processOrder(orderId: string, items: OrderItem[]) {
return tracer.startActiveSpan('process-order', async (span) => {
span.setAttribute('order.id', orderId);
span.setAttribute('order.item_count', items.length);
try {
// 在庫確認(子スパンとして自動ネスト)
const inventoryOk = await tracer.startActiveSpan('check-inventory', async (childSpan) => {
const result = await checkInventory(items);
childSpan.setAttribute('inventory.available', result.available);
childSpan.end();
return result.available;
});
if (!inventoryOk) {
span.setStatus({ code: SpanStatusCode.ERROR, message: '在庫不足' });
span.end();
return { success: false, reason: 'out_of_stock' };
}
// 決済処理の時間計測
const paymentStart = Date.now();
await processPayment(orderId);
paymentHistogram.record(Date.now() - paymentStart, { 'payment.method': 'credit_card' });
// 成功メトリクスを記録
orderCounter.add(1, { 'order.status': 'success', 'order.region': 'jp' });
span.setStatus({ code: SpanStatusCode.OK });
return { success: true };
} catch (error) {
span.recordException(error as Error);
span.setStatus({ code: SpanStatusCode.ERROR });
orderCounter.add(1, { 'order.status': 'error' });
throw error;
} finally {
span.end();
}
});
}
OpenTelemetry Collectorのセットアップ
# otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 10s
send_batch_size: 1024
# 機密情報をマスク
attributes:
actions:
- key: http.request.header.authorization
action: delete
- key: user.email
action: hash
exporters:
# Grafana Tempoにトレースを送信(無料の自己ホスト版)
otlp/tempo:
endpoint: http://tempo:4317
tls:
insecure: true
# Prometheusにメトリクスをエクスポート
prometheus:
endpoint: "0.0.0.0:8889"
# Lokiにログを送信
loki:
endpoint: http://loki:3100/loki/api/v1/push
# デバッグ用(コンソール出力)
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, attributes]
exporters: [otlp/tempo]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus]
logs:
receivers: [otlp]
processors: [batch]
exporters: [loki]
# docker-compose.yml(OpenTelemetry + Grafana Observabilityスタック)
version: "3"
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
restart: always
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
ports:
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
- "8889:8889" # Prometheus metrics export
# 分散トレーシング(Grafana Tempo)
tempo:
image: grafana/tempo:latest
restart: always
command: ["-config.file=/etc/tempo.yaml"]
volumes:
- ./tempo.yaml:/etc/tempo.yaml
- tempo_data:/var/tempo
# ログ管理(Grafana Loki)
loki:
image: grafana/loki:latest
restart: always
command: -config.file=/etc/loki/local-config.yaml
volumes:
- loki_data:/loki
# メトリクス(Prometheus)
prometheus:
image: prom/prometheus:latest
restart: always
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prom_data:/prometheus
# 可視化(Grafana)
grafana:
image: grafana/grafana:latest
restart: always
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
GF_FEATURE_TOGGLES_ENABLE: traceqlEditor
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
volumes:
tempo_data:
loki_data:
prom_data:
grafana_data:
Pythonでの実装
# FastAPI + OpenTelemetry(Python)
# pip install opentelemetry-sdk opentelemetry-instrumentation-fastapi
# pip install opentelemetry-exporter-otlp-proto-http
from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
from fastapi import FastAPI
import time
# トレーサープロバイダーの設定
provider = TracerProvider()
provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://otel-collector:4318/v1/traces"))
)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
app = FastAPI()
FastAPIInstrumentor.instrument_app(app) # FastAPIを自動計装
SQLAlchemyInstrumentor().instrument() # SQLAlchemyを自動計装
# カスタムスパンの例
@app.get("/orders/{order_id}")
async def get_order(order_id: str):
with tracer.start_as_current_span("get-order") as span:
span.set_attribute("order.id", order_id)
start = time.time()
order = await db.fetch_order(order_id)
span.set_attribute("db.query_time_ms", (time.time() - start) * 1000)
if not order:
span.set_status(trace.StatusCode.ERROR, "Order not found")
return {"error": "not found"}
return order
バックエンド選定比較表
| バックエンド | トレース | メトリクス | ログ | 料金 |
|---|---|---|---|---|
| Grafana Cloud(OSS) | ✅ Tempo | ✅ Prometheus | ✅ Loki | 無料枠あり |
| Jaeger(セルフホスト) | ✅ | ❌ | ❌ | 無料 |
| Datadog | ✅ | ✅ | ✅ | $15/ホスト〜 |
| New Relic | ✅ | ✅ | ✅ | 100GB/月無料 |
| OpenSearch | △ | ❌ | ✅ | 無料(セルフホスト) |
OpenTelemetryの設定はDevOpsカテゴリ/categories/devopsのKubernetesモニタリングと組み合わせると効果的です。LLMのトレーシングはLangfuseをOTelバックエンドとして使うパターンをLLMツールカテゴリ/categories/llm-toolsにまとめています。
FAQ
Q. OpenTelemetryとOpenTracingの違いは何ですか?どちらを使うべきですか?
A. OpenTelemetry(OTel)が新標準です。OpenTracing(2016年〜)はトレーシングのみを標準化した先行プロジェクトで、現在はArchived(メンテナンス終了)です。OpenTelemetryは2019年にOpenTracing + OpenCensusの後継として誕生し、Traces・Metrics・Logsの3シグナルを統一的に扱います。既存コードでJaegerClient・Zipkin等のOpenTracingライブラリを使っている場合、opentelemetry-shim-opentracingパッケージで段階的に移行できます。新規プロジェクトでは迷わずOpenTelemetryを使ってください。
Q. Next.jsのEdge RuntimeでOpenTelemetryは動作しますか?
A. Edge Runtimeは制限されたWebAssembly環境のため、Node.jsのOpenTelemetry SDKは直接動作しません。対応策: ①App Routerのinstrumentation.tsをNode.jsランタイムに限定する: Edge Runtimeを使うルートはトレースの対象外にする②Vercel OTel対応の@vercel/otelパッケージを使う: Vercelはエッジ向けの軽量OTel実装を提供しており、Vercel環境ではEdge RuntimeでもBasicなトレーシングが可能③フロントエンド側はBrowser SDK使用: @opentelemetry/sdk-trace-webでブラウザのリアルユーザーモニタリング(RUM)を別途実装。現時点ではEdge RuntimeのOTelサポートは発展途上で、完全なサポートは2026〜2027年の見込みです。
Q. Kubernetes環境でOpenTelemetryを全サービスに適用する最も効率的な方法は?
A. OpenTelemetry Operator for Kubernetesを使うのが最も効率的です。
# OpenTelemetry Operatorをインストール
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm install opentelemetry-operator open-telemetry/opentelemetry-operator -n opentelemetry-operator-system --create-namespace --set "manager.collectorImage.repository=otel/opentelemetry-collector-contrib"
# Instrumentationリソースを定義(自動計装)
kubectl apply -f - <<EOF
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: my-instrumentation
namespace: default
spec:
exporter:
endpoint: http://otel-collector:4317
propagators:
- tracecontext
- baggage
sampler:
type: parentbased_traceidratio
argument: "0.1" # 10%サンプリング(本番負荷軽減)
nodejs:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latest
python:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest
java:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
EOF
# Podに自動計装を注入(アノテーション追加のみ)
kubectl patch deployment my-nodejs-app -p '{
"spec": {"template": {"metadata": {"annotations": {
"instrumentation.opentelemetry.io/inject-nodejs": "true"
}}}}
}'
Podが再起動すると自動的にOTel SDKがサイドカーとして注入され、コードを一行も変更せずにトレーシングが開始されます。
Q. OpenTelemetryのサンプリング戦略はどうするべきですか?
A. 本番環境では全トレースを記録するとストレージコストが高くなるため、サンプリングが重要です。推奨戦略:
// Head-based sampling(リクエスト開始時に決定)
// 1%サンプリング(高トラフィックAPI向け)
import { TraceIdRatioBasedSampler, ParentBasedSampler } from '@opentelemetry/sdk-trace-node';
const sdk = new NodeSDK({
sampler: new ParentBasedSampler({
// 親スパンがある場合は親の判断に従い、なければ1%サンプリング
root: new TraceIdRatioBasedSampler(0.01),
}),
// ...
});
// Tail-based sampling(OpenTelemetry Collectorで実装)
// エラー・高レイテンシーのトレースは100%保持、正常は1%
# otel-collector-config.yaml
processors:
tail_sampling:
decision_wait: 10s
num_traces: 100
expected_new_traces_per_sec: 10
policies:
- name: errors-policy
type: status_code
status_code: {status_codes: [ERROR]}
- name: slow-traces-policy
type: latency
latency: {threshold_ms: 1000}
- name: random-sampling-policy
type: probabilistic
probabilistic: {sampling_percentage: 1}
エラートレースと1秒超のスロートレースは100%保持、正常リクエストは1%のみ保持する設定が、コストと可視性のバランスが良い構成です。
まとめ
| ユースケース | 推奨構成 |
|---|---|
| フルOSSスタック | OTel + Tempo + Prometheus + Loki + Grafana |
| 最小コスト | OTel + Jaeger(トレースのみ) |
| エンタープライズ | OTel → Datadog/New Relic |
| Kubernetes自動計装 | OTel Operator |