- Authors
- Name

- 2026年オブザーバビリティスタックの変曲点
- eBPF 自動計測が変えるもの
- eBPF ベースの SLI 自動収集
- 統合運用モデル:OTel + eBPF + SLO
- 自動化された SLO ガバナンス
- 運用サイクル:週次/月次ルーティン
- トラブルシューティング
- 2026年ロードマップに基づく準備事項
- クイズ
- References
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% CPU | 1% 未満 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 VP、SRE チーム、サービスオーナー
1. SLO 目標の妥当性検討
- 過去3ヶ月の実際の SLI 推移に対して SLO 目標は適切か?
- SLO が緩すぎる:不要なリソース浪費の可能性
- SLO がきつすぎる:イノベーション速度の低下
2. eBPF 計測カバレッジ確認
- 新規サービスが自動計測されているか?
- OBI バージョンアップデートの必要性
- 新しいプロトコルサポートの必要性(MQTT、AMQP 等)
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 カバレッジ、コスト推移、アラート品質を点検する戦略的ミーティングだ。