Skip to content
Published on

オブザーバビリティ:OTel eBPF SLO 運用モデル 2026

Authors
  • Name
    Twitter
オブザーバビリティ:OTel eBPF SLO 運用モデル 2026

2026年オブザーバビリティスタックの変曲点

2025-2026年にオブザーバビリティ領域で3つの技術が交差し、運用モデル自体が変わりつつある。

OpenTelemetry(OTel) は CNCF graduated project として事実上のオブザーバビリティ標準となった。Metrics、Logs、Traces を1つの SDK とプロトコル(OTLP)で統合し、vendor lock-in なしにバックエンドを交換できる。

eBPF ベースの自動計測(OBI:OpenTelemetry eBPF Instrumentation) は 2025年5月に Grafana Labs が Beyla プロジェクトを OTel に寄贈したことで本格化した。コード変更なしにカーネルレベルで HTTP、gRPC、SQL の呼び出しを自動キャプチャする。2026年には安定版 1.0 リリースを目指している(opentelemetry.io/blog/2026/obi-goals)。

SLO(Service Level Objective) は単なるダッシュボードの数字から脱却し、error budget policy を通じてリリース意思決定とオンコール優先順位を制御する運用フレームワークへと進化している。

本記事ではこの3つを組み合わせた 2026年の運用モデルを設計する。

eBPF 自動計測が変えるもの

eBPF 計測 vs 従来の SDK 計測比較

項目従来の OTel SDK 計測eBPF 自動計測(OBI)
コード変更必要(SDK 追加、instrumentation コード)不要(カーネルレベル自動キャプチャ)
サポート言語Java、Python、Go、.NET、JS 等言語無関係(バイナリレベル)
オーバーヘッド1-3% CPU1% 未満 CPU(カーネル空間実行)
キャプチャ深度ビジネスロジックまで詳細L7 プロトコルレベル(HTTP、gRPC、SQL)
Context propagation完全サポートHTTP/gRPC でサポート、一部プロトコルで制限あり
カスタム属性自由に追加可能制限的(プロトコルから抽出可能なもののみ)
デプロイ方式アプリケーションと共にDaemonSet またはサイドカー
運用負担サービスごとに個別適用クラスタ全体一括適用

eBPF 自動計測デプロイ(Kubernetes)

# otel-ebpf-instrumentation.yaml
# OBI(OpenTelemetry eBPF Instrumentation)を DaemonSet でデプロイ
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-ebpf-instrumentation
  namespace: observability
spec:
  selector:
    matchLabels:
      app: obi
  template:
    metadata:
      labels:
        app: obi
    spec:
      hostPID: true # eBPF プローブ接続に必要
      hostNetwork: false
      serviceAccountName: obi
      containers:
        - name: obi
          image: ghcr.io/open-telemetry/opentelemetry-ebpf-instrumentation:v0.9.0
          securityContext:
            privileged: true # eBPF プログラムロードに必要
            runAsUser: 0
          env:
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: 'http://otel-collector:4318'
            - name: OTEL_SERVICE_NAME
              value: 'auto-detected' # プロセス名から自動抽出
            - name: OTEL_EBPF_TRACK_REQUEST_HEADERS
              value: 'true'
            # 監視対象ネームスペースフィルター
            - name: OTEL_EBPF_KUBE_NAMESPACE
              value: 'production,staging'
            # キャプチャするプロトコル
            - name: OTEL_EBPF_PROTOCOLS
              value: 'HTTP,GRPC,SQL,REDIS'
          volumeMounts:
            - name: sys-kernel
              mountPath: /sys/kernel
              readOnly: true
            - name: bpf-maps
              mountPath: /sys/fs/bpf
          resources:
            limits:
              cpu: 500m
              memory: 512Mi
            requests:
              cpu: 100m
              memory: 128Mi
      volumes:
        - name: sys-kernel
          hostPath:
            path: /sys/kernel
        - name: bpf-maps
          hostPath:
            path: /sys/fs/bpf

eBPF と SDK のハイブリッド戦略

すべてのサービスを eBPF だけで計測することはできない。eBPF は L7 プロトコルレベルのメトリクスとトレースを自動提供するが、ビジネスロジックレベルのカスタム span やメトリクスは提供できない。

[サービス計測戦略マトリクス]

                   ビジネスロジック観測必要度
                   低い              高い
                +--------------+--------------+
    重要度 高い  |  eBPF only   |  eBPF + SDK  |
                |  (インフラ   |  (コア      |
                |   サービス)  |   ビジネス) |
                +--------------+--------------+
    重要度 低い  |  eBPF only   |  SDK only    |
                |  (レガシー、 |  (データ    |
                |   3rd party) |   パイプライン)|
                +--------------+--------------+

具体的な適用例:

# ハイブリッド戦略:eBPF が L7 トラフィックを自動キャプチャし、
# SDK はビジネスロジックにのみ集中

from opentelemetry import trace

tracer = trace.get_tracer("recommendation-engine", "2.1.0")

async def get_recommendations(user_id: str, context: dict):
    # eBPF が自動でキャプチャするもの:
    # - HTTP リクエスト/レスポンスメトリクス(レイテンシー、ステータスコード)
    # - gRPC 呼び出しメトリクス
    # - SQL クエリ実行時間
    # - Redis コマンド実行時間

    # SDK で追加するもの:ビジネスロジックレベルの詳細情報
    with tracer.start_as_current_span(
        "generate_recommendations",
        attributes={
            "user.segment": context.get("segment", "unknown"),
            "model.version": "v3.2",
            "candidate.count": 1000,
        }
    ) as span:
        # モデル推論 span
        with tracer.start_as_current_span("ml_inference") as ml_span:
            scores = await ml_model.predict(user_id, context)
            ml_span.set_attribute("inference.latency_ms", scores.latency_ms)
            ml_span.set_attribute("inference.model_name", "rec-v3.2-prod")

        # フィルタリング span
        with tracer.start_as_current_span("business_filter") as filter_span:
            filtered = apply_business_rules(scores.items, context)
            filter_span.set_attribute("filter.input_count", len(scores.items))
            filter_span.set_attribute("filter.output_count", len(filtered))
            filter_span.set_attribute("filter.removed_reasons", {
                "out_of_stock": 12,
                "age_restricted": 3,
                "region_blocked": 1,
            })

        span.set_attribute("result.count", len(filtered))
        return filtered

eBPF ベースの SLI 自動収集

eBPF が自動キャプチャするデータから SLI を抽出できる。コード変更なしですべてのサービスの SLI を一括収集することが可能になる。

OBI が自動生成するメトリクス

# OBI が生成する主要メトリクス(Prometheus 形式)

# HTTP サーバーリクエスト持続時間
http_server_request_duration_seconds_bucket{
  http_request_method="GET",
  http_response_status_code="200",
  url_path="/api/v1/orders",
  service_name="order-service",
  le="0.005"
} 1234

# HTTP サーバーリクエスト数
http_server_request_duration_seconds_count{
  http_request_method="GET",
  http_response_status_code="200",
  url_path="/api/v1/orders",
  service_name="order-service",
} 5678

# gRPC サーバーリクエスト持続時間
rpc_server_duration_seconds_bucket{
  rpc_method="GetUser",
  rpc_service="user.UserService",
  rpc_grpc_status_code="OK",
  service_name="user-service",
  le="0.1"
} 9012

# SQL クエリ持続時間
db_client_operation_duration_seconds_bucket{
  db_system="postgresql",
  db_operation="SELECT",
  service_name="order-service",
  le="0.05"
} 3456

eBPF メトリクスから SLI を抽出する Recording Rules

# prometheus_rules/ebpf_sli_rules.yaml
groups:
  - name: ebpf_sli_from_obi
    interval: 30s
    rules:
      # 可用性 SLI:5xx を除く HTTP レスポンスの割合
      - record: sli:http_availability:ratio_rate5m
        expr: |
          sum(rate(http_server_request_duration_seconds_count{
            http_response_status_code!~"5.."
          }[5m])) by (service_name)
          /
          sum(rate(http_server_request_duration_seconds_count[5m])) by (service_name)

      # レイテンシー SLI:300ms 以内のレスポンス割合
      - record: sli:http_latency:ratio_rate5m
        expr: |
          sum(rate(http_server_request_duration_seconds_bucket{
            le="0.3"
          }[5m])) by (service_name)
          /
          sum(rate(http_server_request_duration_seconds_count[5m])) by (service_name)

      # gRPC 可用性 SLI
      - record: sli:grpc_availability:ratio_rate5m
        expr: |
          sum(rate(rpc_server_duration_seconds_count{
            rpc_grpc_status_code="OK"
          }[5m])) by (service_name)
          /
          sum(rate(rpc_server_duration_seconds_count[5m])) by (service_name)

      # DB クエリレイテンシー SLI:50ms 以内の割合
      - record: sli:db_latency:ratio_rate5m
        expr: |
          sum(rate(db_client_operation_duration_seconds_bucket{
            le="0.05"
          }[5m])) by (service_name, db_system)
          /
          sum(rate(db_client_operation_duration_seconds_count[5m])) by (service_name, db_system)

統合運用モデル:OTel + eBPF + SLO

3つの技術を組み合わせた運用モデルは以下のフローで動作する。

データフロー

[Application Pods]
     |
     +-- eBPF(OBI DaemonSet)
     |   +-- 自動キャプチャ:HTTP/gRPC/SQL メトリクス + トレース
     |
     +-- OTel SDK(オプショナル)
     |   +-- 手動計測:ビジネス span + カスタムメトリクス
     |
     +-- 両方のデータを OTel Collector に送信
              |
              v
     [OTel Collector Gateway]
     +-- 属性標準化(semantic conventions)
     +-- eBPF データと SDK データのマージ
     +-- Tail-based sampling
     +-- バックエンド別ルーティング
              |
         +----+----+
         v         v
   [Metrics DB]  [Traces DB]
   (Mimir)       (Tempo)
         |         |
         v         v
   [Prometheus Recording Rules]
   +-- SLI 計算(from eBPF metrics)
   +-- Error budget 計算
   +-- Burn rate アラート
              |
              v
   [Error Budget Policy Engine]
   +-- Budget >= 50%:通常リリース
   +-- Budget 20-50%:Canary 必須
   +-- Budget < 20%:リリース凍結
   +-- CI/CD パイプライン連携

統合 Collector 設定

# otel-collector-unified.yaml
# eBPF データと SDK データを両方受信する統合 Gateway
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  # eBPF が生成したサービス名を標準化
  # OBI はプロセス名からサービス名を抽出するが、
  # SDK で設定したサービス名と異なる場合がある
  transform/service_name:
    trace_statements:
      - context: resource
        statements:
          # OBI が自動検出した名前を標準名にマッピング
          - replace_pattern(attributes["service.name"], "^python3?$", "unknown-python-service")
          - replace_pattern(attributes["service.name"], "^java$", "unknown-java-service")
    metric_statements:
      - context: resource
        statements:
          - replace_pattern(attributes["service.name"], "^python3?$", "unknown-python-service")

  # eBPF トレースと SDK トレースを同じ trace_id で接続
  # OBI が HTTP ヘッダーから traceparent を読み取るため、
  # SDK が生成したトレースに eBPF span が自動的に合流
  batch:
    send_batch_size: 2048
    timeout: 10s

  tail_sampling:
    decision_wait: 15s
    num_traces: 200000
    policies:
      - name: errors
        type: status_code
        status_code:
          status_codes: [ERROR]
      - name: slow-requests
        type: latency
        latency:
          threshold_ms: 500
      - name: sample-normal
        type: probabilistic
        probabilistic:
          sampling_percentage: 5

exporters:
  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true
  prometheusremotewrite:
    endpoint: http://mimir:9009/api/v1/push
    resource_to_telemetry_conversion:
      enabled: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [transform/service_name, tail_sampling, batch]
      exporters: [otlp/tempo]
    metrics:
      receivers: [otlp]
      processors: [transform/service_name, batch]
      exporters: [prometheusremotewrite]

自動化された SLO ガバナンス

サービスカタログベースの自動 SLO プロビジョニング

新しいサービスがデプロイされると eBPF が自動でメトリクスを収集し、サービスカタログに登録された SLO ポリシーに従ってアラートが自動生成される仕組みだ。

# slo_provisioner.py
# サービスカタログから SLO 定義を読み込み、Prometheus アラートルールを生成

import yaml
from pathlib import Path

def generate_slo_alerts(service_catalog_path: str, output_dir: str):
    """サービスカタログから SLO 定義を読み込み Prometheus アラートルールを自動生成"""
    catalog = yaml.safe_load(open(service_catalog_path))

    for service in catalog["services"]:
        name = service["name"]
        tier = service["tier"]
        slo = service["slo"]

        # ティアに基づくデフォルト値を適用
        availability_target = slo.get("availability", TIER_DEFAULTS[tier]["availability"])
        latency_threshold_ms = slo.get("latency_threshold_ms", TIER_DEFAULTS[tier]["latency_ms"])
        latency_target = slo.get("latency_target", TIER_DEFAULTS[tier]["latency_target"])

        # Prometheus アラートルール生成
        alert_rules = generate_burn_rate_alerts(
            service_name=name,
            availability_target=availability_target,
            latency_threshold_ms=latency_threshold_ms,
            latency_target=latency_target,
        )

        output_file = Path(output_dir) / f"slo-{name}.yaml"
        yaml.dump(alert_rules, open(output_file, "w"), default_flow_style=False)
        print(f"Generated SLO alerts for {name}: {output_file}")

TIER_DEFAULTS = {
    "tier1": {"availability": 0.9995, "latency_ms": 200, "latency_target": 0.99},
    "tier2": {"availability": 0.999,  "latency_ms": 500, "latency_target": 0.95},
    "tier3": {"availability": 0.995,  "latency_ms": 2000, "latency_target": 0.90},
}

def generate_burn_rate_alerts(
    service_name: str,
    availability_target: float,
    latency_threshold_ms: int,
    latency_target: float,
) -> dict:
    """Multi-window burn rate アラートルール生成"""
    error_budget = 1.0 - availability_target
    latency_threshold_sec = latency_threshold_ms / 1000.0

    return {
        "groups": [{
            "name": f"slo-{service_name}",
            "rules": [
                # Critical: burn rate 14, 1h/5m window
                {
                    "alert": f"SLO_{service_name}_BurnRate_Critical",
                    "expr": (
                        f'(\n'
                        f'  1 - sli:http_availability:ratio_rate1h{{service_name="{service_name}"}}\n'
                        f') > {14 * error_budget}\n'
                        f'and\n'
                        f'(\n'
                        f'  1 - sli:http_availability:ratio_rate5m{{service_name="{service_name}"}}\n'
                        f') > {14 * error_budget}'
                    ),
                    "for": "1m",
                    "labels": {
                        "severity": "critical",
                        "service": service_name,
                        "burn_rate": "14",
                    },
                    "annotations": {
                        "summary": f"{service_name}: SLO critical burn rate (14x)",
                        "runbook": f"https://wiki.internal/runbook/slo/{service_name}",
                    },
                },
                # Warning: burn rate 6, 6h/30m window
                {
                    "alert": f"SLO_{service_name}_BurnRate_Warning",
                    "expr": (
                        f'(\n'
                        f'  1 - sli:http_availability:ratio_rate6h{{service_name="{service_name}"}}\n'
                        f') > {6 * error_budget}\n'
                        f'and\n'
                        f'(\n'
                        f'  1 - sli:http_availability:ratio_rate30m{{service_name="{service_name}"}}\n'
                        f') > {6 * error_budget}'
                    ),
                    "for": "5m",
                    "labels": {
                        "severity": "warning",
                        "service": service_name,
                        "burn_rate": "6",
                    },
                },
            ],
        }],
    }

サービスカタログ例

# service_catalog.yaml
services:
  - name: payment-api
    tier: tier1
    team: payment
    slo:
      availability: 0.9995
      latency_threshold_ms: 200
      latency_target: 0.99

  - name: recommendation-engine
    tier: tier2
    team: ml-platform
    slo:
      availability: 0.999
      latency_threshold_ms: 500

  - name: notification-service
    tier: tier3
    team: platform
    # tier3 デフォルト値を使用

  - name: internal-admin
    tier: tier3
    team: platform
    slo:
      availability: 0.99
      latency_threshold_ms: 3000

運用サイクル:週次/月次ルーティン

週次 SLO レビュー(30分)

参加者:SRE リード、サービスオーナー、プロダクトマネージャー

1. Error Budget 現状確認(5分)
   - 全サービスの budget 残量ダッシュボードレビュー
   - Yellow/Red ステータスのサービスを特定

2. 先週のインシデント SLO 影響度(10分)
   - 各インシデントが消費した budget 割合
   - 繰り返しパターンの確認

3. リリース計画レビュー(10分)
   - 今週予定されているリリースのリスク評価
   - Budget 状態に基づくリリース戦略決定
     (canary 比率、rollback 基準等)

4. Action Items(5分)
   - 前週の action items 完了状態
   - 新しい action items の割り当て

月次 SLO チューニングレビュー(1時間)

参加者:Engineering VPSRE チーム、サービスオーナー

1. SLO 目標の妥当性検討
   - 過去3ヶ月の実際の SLI 推移に対して SLO 目標は適切か?
   - SLO が緩すぎる:不要なリソース浪費の可能性
   - SLO がきつすぎる:イノベーション速度の低下

2. eBPF 計測カバレッジ確認
   - 新規サービスが自動計測されているか?
   - OBI バージョンアップデートの必要性
   - 新しいプロトコルサポートの必要性(MQTTAMQP 等)

3. コストレビュー
   - オブザーバビリティデータストレージコストの推移
   - サンプリング比率調整の必要性
   - 保持期間ポリシーの確認

4. アラート品質レビュー
   - 先月のアラート発火回数
   - 偽陽性(false positive)率
   - 偽陰性(false negative)事例

トラブルシューティング

1. eBPF プログラムロード失敗

Error: failed to load BPF program: operation not permitted

原因: コンテナに CAP_BPF または CAP_SYS_ADMIN 権限がない

解決策:

# securityContext に必要な権限を追加
securityContext:
  privileged: true
  # または最小権限で:
  capabilities:
    add:
      - BPF
      - SYS_ADMIN
      - NET_ADMIN
      - PERFMON

2. eBPF メトリクスでサービス名が「python3」と表示される

原因: OBI がプロセス名からサービス名を抽出するが、Python サービスではインタープリター名が露出される

解決策:

# 方法 1:OBI 環境変数でマッピング設定
env:
  - name: OTEL_EBPF_SERVICE_NAME_MAP
    value: 'python3.11:/usr/local/bin/gunicorn=order-service'

# 方法 2:Collector の transform processor でマッピング
processors:
  transform/service_name:
    metric_statements:
      - context: resource
        statements:
          - set(attributes["service.name"], "order-service")
            where attributes["k8s.deployment.name"] == "order-service"

3. eBPF トレースと SDK トレースが別々に表示される

症状: 同じリクエストなのに eBPF が生成したトレースと SDK が生成したトレースが異なる trace_id を持つ

原因: OBI が HTTP ヘッダーの既存 traceparent を読み取れない、または SDK が OBI より先にトレースを開始してコンテキストが重複している

解決策:

# OBI で既存コンテキストを尊重するように設定
env:
  - name: OTEL_EBPF_CONTEXT_PROPAGATION
    value: 'true'
  - name: OTEL_EBPF_CONTEXT_PROPAGATION_MODE
    value: 'reuse' # 既存コンテキストがあれば再利用、なければ新規作成

4. SLI recording rule が NaN を返す

原因: 分母(total requests)が 0 の時間帯。トラフィックのない深夜時間帯、または新規デプロイされたサービス。

解決策:

# NaN 防止:分母が 0 なら 1 を返す(エラーなしとみなす)
sli:http_availability:ratio_rate5m = (
  sum(rate(http_server_request_duration_seconds_count{
    http_response_status_code!~"5.."
  }[5m])) by (service_name)
  /
  (sum(rate(http_server_request_duration_seconds_count[5m])) by (service_name) > 0)
) or vector(1)

5. eBPF のオーバーヘッドが想定より高い

症状: OBI DaemonSet の CPU 使用量が 500m を超過

診断と解決策:

# 1. どのプローブが CPU を多く使用しているか確認
kubectl exec -n observability obi-xxx -- /obi debug perf-stats

# 2. 不要なプロトコル検出を無効化
# OTEL_EBPF_PROTOCOLS から使用していないプロトコルを削除
# 例:Redis を使用していなければ REDIS を削除

# 3. 高トラフィック Pod を除外
env:
  - name: OTEL_EBPF_EXCLUDE_NAMESPACES
    value: "kube-system,monitoring"
  - name: OTEL_EBPF_EXCLUDE_PODS
    value: "load-generator-*"  # 負荷テスト Pod を除外

2026年ロードマップに基づく準備事項

OBI の 2026年ロードマップ(opentelemetry.io/blog/2026/obi-goals)によると、以下の機能が追加予定だ。

予定機能現在の状態準備事項
安定版 1.0 リリースAlpha/Betaプロダクションデプロイ前に staging テスト計画策定
.NET 計測サポート初期テスト.NET サービス一覧把握、SDK 代替可能性評価
メッセージングシステム(MQTT、AMQP、NATS)開発中メッセージキューベースサービスの現行計測方式整理
gRPC full context propagation改善中gRPC サービス間のトレース接続状態確認
クラウド SDK 計測(AWS、GCP、Azure)計画クラウド API 呼び出しの観測必要性評価

クイズ

Q1. eBPF 自動計測が SDK 計測を完全に代替できない理由は? 回答: eBPF はカーネルレベルで L7 プロトコル(HTTP、gRPC、SQL)をキャプチャするが、アプリケーションのビジネスロジックレベルのカスタム span やビジネスメトリクス(例:注文金額、推薦スコア)は生成できない。コアビジネスサービスでは eBPF と SDK を併用するハイブリッド戦略が必要だ。

Q2. OBI DaemonSet に privileged 権限が必要な理由は? 回答: eBPF プログラムをカーネルにロードして実行するには CAP_BPF や CAP_SYS_ADMIN などの高レベルの権限が必要だ。eBPF プローブをカーネル関数にアタッチし、ネットワークパケットを検査し、他のプロセスのシステムコールをトレースするためだ。

Q3. eBPF メトリクスから自動的に SLI を抽出する際の利点は? 回答: コード変更なしにクラスタ内のすべてのサービスの可用性とレイテンシー SLI を一括収集できる。新しいサービスがデプロイされると eBPF が自動でメトリクスを生成するため、サービスカタログと連携すれば SLO アラートの自動プロビジョニングまで可能だ。

Q4. eBPF トレースと SDK トレースの trace_id が異なる状況はいつ発生するか? 回答: OBI が HTTP ヘッダーの traceparent を読み取れない場合、または SDK が既にトレースを開始した状態で OBI が別のトレースを生成する場合だ。OTEL_EBPF_CONTEXT_PROPAGATION_MODE を「reuse」に設定すると、既存のコンテキストがある場合に再利用してこの問題を解決できる。

Q5. サービスカタログベースの自動 SLO プロビジョニングの前提条件は? 回答: eBPF 自動計測がクラスタ全体にデプロイされていること、サービス名が標準化(k8s deployment name ベースのマッピング等)されていること、サービスカタログに各サービスのティアと SLO 定義が登録されていること。この3つが揃えば Prometheus recording rules とアラートルールを自動生成できる。

Q6. SLI recording rule で分母が 0 のとき NaN ではなく 1 を返すべき理由は? 回答: トラフィックのない時間帯に NaN が発生すると burn rate アラートが正しく計算されない。リクエストがなければエラーもないため、可用性 1(100%)とみなすのが合理的だ。ただし、長期間トラフィックが 0 の場合は別途「サービス無応答」アラートを設定すべきだ。

Q7. オブザーバビリティ運用モデルにおける週次レビューと月次レビューのフォーカスの違いは?

回答: 週次レビューは現在の error budget 状態と今週のリリース計画に集中する戦術的ミーティングだ。月次レビューは SLO 目標自体の妥当性、eBPF カバレッジ、コスト推移、アラート品質を点検する戦略的ミーティングだ。

References