OpenTelemetry 2025 - 統合オブザーバビリティの標準

2026.01.12

OpenTelemetryとは

OpenTelemetry(OTel)は、オブザーバビリティ(可観測性)のためのオープンソースフレームワークです。分散システムからテレメトリデータ(トレース、メトリクス、ログ)を収集・処理・エクスポートするためのベンダー中立な標準を提供します。

Cloud Native Computing Foundation(CNCF)のプロジェクトとして、KubernetesやPrometheusに次ぐ活発なプロジェクトに成長しています。

2025年の主要な進化

GA(安定版)リリースの完了

シグナルステータス説明
TracesGA分散トレーシング
MetricsGAメトリクス収集
LogsGAログ収集・相関
ProfilingBetaパフォーマンスプロファイリング

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とのシームレスな統合により、分散システムの可観測性がかつてないほど容易になりました。ベンダー中立なアプローチにより、ロックインのリスクなく最適な監視バックエンドを選択できます。

参考: OpenTelemetry公式ドキュメント | OpenTelemetry 2025ブログ

この技術を体系的に学びたいですか?

未来学では東証プライム上場企業のITエンジニアが24時間サポート。月額24,800円から、退会金0円のオンラインIT塾です。

LINEで無料相談する
← 一覧に戻る