OpenTelemetryとは
OpenTelemetry(OTel)は、オブザーバビリティ(可観測性)のためのオープンソースフレームワークです。分散システムからテレメトリデータ(トレース、メトリクス、ログ)を収集・処理・エクスポートするためのベンダー中立な標準を提供します。
Cloud Native Computing Foundation(CNCF)のプロジェクトとして、KubernetesやPrometheusに次ぐ活発なプロジェクトに成長しています。
2025年の主要な進化
GA(安定版)リリースの完了
| シグナル | ステータス | 説明 |
|---|---|---|
| Traces | GA | 分散トレーシング |
| Metrics | GA | メトリクス収集 |
| Logs | GA | ログ収集・相関 |
| Profiling | Beta | パフォーマンスプロファイリング |
2025年の主要アップデート
・全テレメトリシグナルのGA達成
・20以上の言語・フレームワークの自動計装
・eBPF統合による深いシステムレベルの可視性
・宣言的設定(Declarative Configuration)のRC版
・OpAMP(Agent Management Protocol)の進化
・セマンティック規約の標準化強化
OTelの3本柱
1. Traces(トレース)
分散システム内のリクエストの流れを追跡します。
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
# TracerProviderの設定
provider = TracerProvider()
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="localhost:4317"))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
# Tracerの取得と使用
tracer = trace.get_tracer("payment-service", "1.0.0")
def process_payment(order_id: str, amount: float):
with tracer.start_as_current_span("process-payment") as span:
span.set_attribute("order.id", order_id)
span.set_attribute("payment.amount", amount)
# 子スパンの作成
with tracer.start_as_current_span("validate-card") as child_span:
child_span.set_attribute("validation.type", "card")
validate_card()
with tracer.start_as_current_span("charge-payment") as child_span:
child_span.set_attribute("gateway", "stripe")
charge_amount(amount)
span.add_event("payment_completed", {"status": "success"})
return True
2. Metrics(メトリクス)
システムのパフォーマンスと健全性を数値で計測します。
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
# MeterProviderの設定
exporter = OTLPMetricExporter(endpoint="localhost:4317")
reader = PeriodicExportingMetricReader(exporter, export_interval_millis=60000)
provider = MeterProvider(metric_readers=[reader])
metrics.set_meter_provider(provider)
# Meterの取得
meter = metrics.get_meter("order-service", "1.0.0")
# 各種メトリクスの定義
request_counter = meter.create_counter(
name="http_requests_total",
description="Total HTTP requests",
unit="1"
)
request_latency = meter.create_histogram(
name="http_request_duration_seconds",
description="HTTP request latency",
unit="s"
)
active_connections = meter.create_up_down_counter(
name="active_connections",
description="Number of active connections"
)
# メトリクスの記録
def handle_request(method: str, path: str):
import time
start = time.time()
active_connections.add(1, {"service": "api"})
try:
# リクエスト処理
process_request()
request_counter.add(1, {"method": method, "path": path, "status": "200"})
finally:
duration = time.time() - start
request_latency.record(duration, {"method": method, "path": path})
active_connections.add(-1, {"service": "api"})
3. Logs(ログ)
構造化ログとトレースの相関を実現します。
import logging
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
# LoggerProviderの設定
logger_provider = LoggerProvider()
log_exporter = OTLPLogExporter(endpoint="localhost:4317")
logger_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter))
set_logger_provider(logger_provider)
# Python標準ロガーにOTelハンドラーを追加
handler = LoggingHandler(level=logging.INFO, logger_provider=logger_provider)
logging.basicConfig(handlers=[handler], level=logging.INFO)
logger = logging.getLogger(__name__)
# トレースとログの相関
from opentelemetry import trace
tracer = trace.get_tracer("user-service")
def create_user(username: str):
with tracer.start_as_current_span("create-user") as span:
span.set_attribute("user.name", username)
# このログには自動的にSpanIDが付与される
logger.info(f"Creating user: {username}")
try:
user = User.objects.create(username=username)
logger.info(f"User created successfully: {user.id}")
return user
except Exception as e:
logger.error(f"Failed to create user: {e}")
span.record_exception(e)
raise
SDK実装例
Go言語
package main
import (
"context"
"log"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)
func initTracer() (*sdktrace.TracerProvider, error) {
ctx := context.Background()
// OTLPエクスポーターの作成
exporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("localhost:4317"),
otlptracegrpc.WithInsecure(),
)
if err != nil {
return nil, err
}
// リソースの定義
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("order-service"),
semconv.ServiceVersion("1.0.0"),
attribute.String("environment", "production"),
),
)
if err != nil {
return nil, err
}
// TracerProviderの作成
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
sdktrace.WithSampler(sdktrace.AlwaysSample()),
)
otel.SetTracerProvider(tp)
return tp, nil
}
func processOrder(ctx context.Context, orderID string) error {
tracer := otel.Tracer("order-processor")
ctx, span := tracer.Start(ctx, "process-order")
defer span.End()
span.SetAttributes(
attribute.String("order.id", orderID),
attribute.Int("order.items", 3),
)
// 子スパン: 在庫確認
_, childSpan := tracer.Start(ctx, "check-inventory")
time.Sleep(50 * time.Millisecond)
childSpan.End()
// 子スパン: 支払い処理
_, paymentSpan := tracer.Start(ctx, "process-payment")
time.Sleep(100 * time.Millisecond)
paymentSpan.AddEvent("payment_authorized")
paymentSpan.End()
span.AddEvent("order_completed")
return nil
}
func main() {
tp, err := initTracer()
if err != nil {
log.Fatal(err)
}
defer tp.Shutdown(context.Background())
ctx := context.Background()
processOrder(ctx, "ORD-12345")
}
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 { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
// SDKの初期化
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'api-gateway',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
}),
traceExporter: new OTLPTraceExporter({
url: 'http://localhost:4317',
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: 'http://localhost:4317',
}),
exportIntervalMillis: 60000,
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
// 手動計装の例
const { trace, metrics } = require('@opentelemetry/api');
const tracer = trace.getTracer('api-gateway', '1.0.0');
const meter = metrics.getMeter('api-gateway', '1.0.0');
const requestCounter = meter.createCounter('http_requests_total', {
description: 'Total HTTP requests',
});
const requestLatency = meter.createHistogram('http_request_duration_ms', {
description: 'HTTP request latency in milliseconds',
});
async function handleRequest(req, res) {
const startTime = Date.now();
return tracer.startActiveSpan('handle-request', async (span) => {
try {
span.setAttribute('http.method', req.method);
span.setAttribute('http.url', req.url);
const result = await processRequest(req);
requestCounter.add(1, {
method: req.method,
path: req.url,
status: '200'
});
span.setStatus({ code: 1 }); // OK
return result;
} catch (error) {
span.recordException(error);
span.setStatus({ code: 2, message: error.message }); // ERROR
throw error;
} finally {
const duration = Date.now() - startTime;
requestLatency.record(duration, { method: req.method });
span.end();
}
});
}
// グレースフルシャットダウン
process.on('SIGTERM', () => {
sdk.shutdown()
.then(() => console.log('SDK shut down successfully'))
.catch((error) => console.log('Error shutting down SDK', error))
.finally(() => process.exit(0));
});
OpenTelemetry Collector設定
Collectorは、テレメトリデータの受信・処理・エクスポートを行う中央コンポーネントです。
基本設定
# otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
# Prometheusメトリクスのスクレイピング
prometheus:
config:
scrape_configs:
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
processors:
# バッチ処理でパフォーマンス向上
batch:
send_batch_size: 10000
timeout: 10s
# メモリ制限
memory_limiter:
check_interval: 1s
limit_mib: 1000
spike_limit_mib: 200
# リソース属性の追加
resource:
attributes:
- key: environment
value: production
action: upsert
- key: cluster
value: main-cluster
action: insert
# サンプリング(トレース)
probabilistic_sampler:
sampling_percentage: 10
# 属性のフィルタリング
attributes:
actions:
- key: http.request.header.authorization
action: delete
- key: db.statement
action: hash
exporters:
# Jaegerへのトレースエクスポート
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
# Prometheusへのメトリクスエクスポート
prometheus:
endpoint: 0.0.0.0:8889
namespace: otel
# Lokiへのログエクスポート
loki:
endpoint: http://loki:3100/loki/api/v1/push
labels:
attributes:
service.name: "service_name"
severity: "severity"
# デバッグ用
debug:
verbosity: detailed
extensions:
health_check:
endpoint: 0.0.0.0:13133
pprof:
endpoint: 0.0.0.0:1777
zpages:
endpoint: 0.0.0.0:55679
service:
extensions: [health_check, pprof, zpages]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch, resource, probabilistic_sampler]
exporters: [otlp/jaeger, debug]
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, batch, resource]
exporters: [prometheus]
logs:
receivers: [otlp]
processors: [memory_limiter, batch, resource, attributes]
exporters: [loki]
Docker Compose構成
# docker-compose.yaml
version: '3.8'
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
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
- "13133:13133" # Health check
depends_on:
- jaeger
- prometheus
- loki
jaeger:
image: jaegertracing/jaeger:2.0
environment:
- COLLECTOR_OTLP_ENABLED=true
ports:
- "16686:16686" # UI
- "4317" # OTLP gRPC
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yaml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana:11.0.0
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_FEATURE_TOGGLES_ENABLE=traceToMetrics,correlations
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning
ports:
- "3000:3000"
depends_on:
- prometheus
- jaeger
- loki
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
Jaeger/Grafana統合
Grafanaデータソース設定
# grafana/provisioning/datasources/datasources.yaml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
jsonData:
exemplarTraceIdDestinations:
- name: trace_id
datasourceUid: jaeger
urlDisplayLabel: View in Jaeger
- name: Jaeger
type: jaeger
uid: jaeger
access: proxy
url: http://jaeger:16686
jsonData:
tracesToLogsV2:
datasourceUid: loki
spanStartTimeShift: "-1h"
spanEndTimeShift: "1h"
filterByTraceID: true
filterBySpanID: true
tracesToMetrics:
datasourceUid: prometheus
queries:
- name: Request Rate
query: sum(rate(http_requests_total{service="$__span.service.name"}[5m]))
- name: Error Rate
query: sum(rate(http_requests_total{service="$__span.service.name",status=~"5.."}[5m]))
- name: Loki
type: loki
uid: loki
access: proxy
url: http://loki:3100
jsonData:
derivedFields:
- datasourceUid: jaeger
matcherRegex: "trace_id=(\\w+)"
name: TraceID
url: '$${__value.raw}'
Grafanaダッシュボード例
{
"dashboard": {
"title": "OpenTelemetry Overview",
"panels": [
{
"title": "Request Rate by Service",
"type": "timeseries",
"targets": [
{
"expr": "sum by (service_name) (rate(http_requests_total[5m]))",
"legendFormat": "{{service_name}}"
}
]
},
{
"title": "P99 Latency",
"type": "timeseries",
"targets": [
{
"expr": "histogram_quantile(0.99, sum by (le, service_name) (rate(http_request_duration_seconds_bucket[5m])))",
"legendFormat": "{{service_name}}"
}
]
},
{
"title": "Error Rate",
"type": "stat",
"targets": [
{
"expr": "sum(rate(http_requests_total{status=~\"5..\"}[5m])) / sum(rate(http_requests_total[5m])) * 100"
}
]
},
{
"title": "Trace Explorer",
"type": "traces",
"datasource": "jaeger"
}
]
}
}
自動計装
Kubernetes Operator
# OpenTelemetry Operatorのインストール
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: auto-instrumentation
namespace: default
spec:
exporter:
endpoint: http://otel-collector:4317
propagators:
- tracecontext
- baggage
- b3
sampler:
type: parentbased_traceidratio
argument: "0.1"
# Python設定
python:
env:
- name: OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED
value: "true"
# Node.js設定
nodejs:
env:
- name: OTEL_NODE_RESOURCE_DETECTORS
value: "env,host,os"
# Java設定
java:
env:
- name: OTEL_INSTRUMENTATION_COMMON_DEFAULT_ENABLED
value: "true"
# Go設定(eBPF使用)
go:
image: ghcr.io/open-telemetry/opentelemetry-go-instrumentation/autoinstrumentation-go:latest
アプリケーションへのアノテーション
# 自動計装を有効化するDeployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
spec:
template:
metadata:
annotations:
# Python自動計装
instrumentation.opentelemetry.io/inject-python: "true"
# または Node.js
# instrumentation.opentelemetry.io/inject-nodejs: "true"
# または Java
# instrumentation.opentelemetry.io/inject-java: "true"
# または Go (eBPF)
# instrumentation.opentelemetry.io/inject-go: "true"
spec:
containers:
- name: payment-service
image: payment-service:latest
ports:
- containerPort: 8080
Go自動計装(eBPF)
2025年1月にベータ版がリリースされたGo自動計装は、eBPFを使用してコード変更なしでトレースを収集します。
# Go自動計装の有効化(Kubernetes Operator)
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: go-auto-instrumentation
spec:
go:
image: ghcr.io/open-telemetry/opentelemetry-go-instrumentation/autoinstrumentation-go:v0.19.0-alpha
env:
- name: OTEL_GO_AUTO_TARGET_EXE
value: /app/server
サポートされるパッケージ:
net/http: HTTPサーバー・クライアントdatabase/sql: データベースクエリgoogle.golang.org/grpc: gRPCクライアント・サーバーgithub.com/segmentio/kafka-go: Kafkaメッセージング
宣言的設定(Declarative Configuration)
2025年12月にRC版がリリースされた新しい設定方式です。
# otel-config.yaml
file_format: "0.4"
disabled: false
resource:
attributes:
service.name: order-service
service.version: 1.0.0
deployment.environment: production
tracer_provider:
processors:
- batch:
schedule_delay: 5000
export_timeout: 30000
max_queue_size: 2048
max_export_batch_size: 512
exporter:
otlp_grpc:
endpoint: http://collector:4317
timeout: 10000
tls:
insecure: true
meter_provider:
readers:
- periodic:
interval: 60000
timeout: 30000
exporter:
otlp_grpc:
endpoint: http://collector:4317
logger_provider:
processors:
- batch:
exporter:
otlp_grpc:
endpoint: http://collector:4317
環境変数で設定ファイルを指定:
export OTEL_EXPERIMENTAL_CONFIG_FILE=/etc/otel/otel-config.yaml
セマンティック規約
OpenTelemetryは、テレメトリデータの属性に対して標準的な命名規則を定義しています。
HTTP関連
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.semconv.resource import ResourceAttributes
# リソース属性
span.set_attribute(ResourceAttributes.SERVICE_NAME, "api-gateway")
span.set_attribute(ResourceAttributes.SERVICE_VERSION, "1.0.0")
# HTTP属性
span.set_attribute(SpanAttributes.HTTP_METHOD, "POST")
span.set_attribute(SpanAttributes.HTTP_URL, "https://api.example.com/orders")
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, 200)
span.set_attribute(SpanAttributes.HTTP_REQUEST_CONTENT_LENGTH, 1024)
# ネットワーク属性
span.set_attribute(SpanAttributes.NET_PEER_NAME, "api.example.com")
span.set_attribute(SpanAttributes.NET_PEER_PORT, 443)
データベース関連
# データベース属性
span.set_attribute(SpanAttributes.DB_SYSTEM, "postgresql")
span.set_attribute(SpanAttributes.DB_NAME, "orders")
span.set_attribute(SpanAttributes.DB_OPERATION, "SELECT")
span.set_attribute(SpanAttributes.DB_STATEMENT, "SELECT * FROM orders WHERE id = ?")
ベストプラクティス
1. サンプリング戦略
# 本番環境向けサンプリング設定
processors:
# 親ベースのサンプリング
probabilistic_sampler:
sampling_percentage: 10
# テイルベースサンプリング(エラーは100%保持)
tail_sampling:
decision_wait: 10s
policies:
- name: errors-policy
type: status_code
status_code:
status_codes: [ERROR]
- name: latency-policy
type: latency
latency:
threshold_ms: 1000
- name: probabilistic-policy
type: probabilistic
probabilistic:
sampling_percentage: 10
2. コンテキスト伝播
from opentelemetry.propagate import inject, extract
from opentelemetry.propagators.b3 import B3MultiFormat
# HTTPヘッダーにトレースコンテキストを注入
headers = {}
inject(headers)
# 外部リクエストからコンテキストを抽出
context = extract(request.headers)
with tracer.start_as_current_span("process", context=context) as span:
pass
3. リソース検出
from opentelemetry.sdk.resources import Resource, get_aggregated_resources
from opentelemetry.sdk.resources import OTELResourceDetector
from opentelemetry.sdk.resources import ProcessResourceDetector
# 複数のリソース検出器を組み合わせ
resource = get_aggregated_resources([
OTELResourceDetector(),
ProcessResourceDetector(),
Resource.create({
"service.name": "my-service",
"service.version": "1.0.0",
})
])
2025年の動向と今後
Grafana Alloy
Grafanaが発表したOpenTelemetry Collector互換の次世代コレクター。
# alloy-config.alloy
otelcol.receiver.otlp "default" {
grpc {
endpoint = "0.0.0.0:4317"
}
http {
endpoint = "0.0.0.0:4318"
}
}
otelcol.processor.batch "default" {
timeout = "5s"
send_batch_size = 8192
}
otelcol.exporter.otlp "tempo" {
client {
endpoint = "tempo:4317"
}
}
otelcol.exporter.prometheus "metrics" {
forward_to = [prometheus.remote_write.default.receiver]
}
今後の展望
・プロファイリングのGA化
・セキュリティシグナルの追加
・eBPF統合の成熟
・AI/MLオブザーバビリティの標準化
・エッジコンピューティング対応
まとめ
2025年、OpenTelemetryはオブザーバビリティの事実上の標準として確立されました。トレース・メトリクス・ログの3本柱が全てGA(安定版)となり、20以上の言語・フレームワークでの自動計装が利用可能です。
Kubernetes OperatorによるゼロコードでのeBPF計装、宣言的設定の導入、Jaeger v2やGrafana Alloyとのシームレスな統合により、分散システムの可観測性がかつてないほど容易になりました。ベンダー中立なアプローチにより、ロックインのリスクなく最適な監視バックエンドを選択できます。
← 一覧に戻る