本文介紹如何將 eBPF 與 OpenTelemetry 結合,實現自動化、零代碼的分佈式追蹤和可觀測性系統,瞭解 Odigos、Beyla 和 OpenTelemetry eBPF 等工具的工作原理、適用場景以及如何在生產環境中設置。原文:Using eBPF with OpenTelemetry: Zero-Code Auto-Instrumentation for Any Application
如果能在所有服務間實現完整的分佈式追蹤,而無需添加一行儀表盤代碼,怎麼樣?
傳統 OpenTelemetry 儀表盤需要添加 SDK、配置導出器,並用 span 包裝代碼。雖然功能強大,但需要付出不少努力,尤其是系統中有數十種不同語言的服務時。
eBPF 完全改變了這一模式。通過從 Linux 內核觀察應用,基於 eBPF 的工具可以自動生成兼容 OpenTelemetry 的追蹤、指標和配置文件,而無需觸及應用代碼 。
本文將介紹如何將 eBPF 與 OpenTelemetry 結合,實現強大的零代碼可觀測性。
1. 問題:大規模儀表盤
傳統 OpenTelemetry 儀表盤遵循以下模式:
- 為每個服務添加 OTel SDK
- 配置導出器
- 測量入口點(HTTP 處理程序,gRPC 方法)
- 為重要操作添加 span
- 跨服務邊界傳播上下文
- 每個服務、每種語言都要重複
對於只有少量服務的小團隊來説,還算可以管理。但請考慮:
|場景|挑戰|
|-|-|
| 50+ 微服務|需要數週時間集成跨所有服務的 SDK|
|多語言技術棧|Go、Python、Node.js、Java、Rust 等不同的 SDK……|
|遺留服務|代碼不容易修改,沒人願意碰|
|第三方服務|無法訪問源代碼|
|快速部署|新服務出現的速度比測量它們的速度還快|
結果呢?可觀測性缺口。有些服務有追蹤,有些沒有。未安裝測量的服務中斷上下文傳播。系統一定程度上正在裸跑。
2. eBPF 如何實現自動化測量
eBPF 通過從應用外部 —— 內核層面觀察應用來解決這個問題。
eBPF 能看到什麼
由於 eBPF 會鈎入內核函數和系統調用,可以觀察到:
|層|eBPF 看見|
|-|-|
|網絡|每一次 TCP 連接、HTTP 請求/響應、DNS 查詢|
|系統調用|文件 I/O,進程創建,內存分配|
|用户功能|函數通過 uprobe 進入/退出(如果有符號)|
|語言運行時|Go、Node.js、Python、Java 運行時內部結構|
這些如何成為 OpenTelemetry 數據
基於 eBPF 的自動化測量工作原理:
- 將探針附加到已知入口點(HTTP 庫、gRPC 處理器、數據庫驅動程序)
- 從請求中提取上下文(從頭部提取追蹤 ID、請求元數據)
- 利用內核時間戳測量時序
- 關聯相關請求/響應對
- 導出為標準 OpenTelemetry Protocol(OTLP)數據
3. eBPF + OpenTelemetry 架構
典型生產配置如下:
組件
|組件| 職責|
|-|-|
|eBPF 代理|運行在每個節點上,連接 eBPF 程序,生成遙測數據|
|OTel 收集器|接收、處理 OTLP 數據,並導出到後端|
|後端|存儲和可視化追蹤/指標(OneUptime、Jaeger、Tempo)|
部署模式
模式 1:DaemonSet(Kubernetes)
# 運行在每個 node 上的 eBPF 代理
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ebpf-auto-instrumenter
spec:
selector:
matchLabels:
app: ebpf-agent
template:
spec:
hostPID: true # eBPF 需要
hostNetwork: true # 網絡追蹤需要
containers:
- name: agent
securityContext:
privileged: true # eBPF 需要
模式 2:Sidecar(每個 Pod 一個)
# eBPF 代理作為 sidecar (更為隔離,更多開銷)
spec:
containers:
- name: my-app
image: my-app:latest
- name: ebpf-sidecar
image: ebpf-agent:latest
securityContext:
privileged: true
模式 3:獨立(非 Kubernetes)
# 直接在主機上運行
sudo ./beyla --config config.yaml
4. 工具比較:Odigos vs Beyla vs Pixie
下面比較幾種將 eBPF 與 OpenTelemetry 結合起來的工具。
Grafana Beyla
|特色|詳情|
|-|-|
|重點|HTTP/gRPC 自動監測|
|語言|Go, Python, Node.js, Java, Rust, .NET, Ruby|
|輸出|OTLP (追蹤 + 指標)|
|部署|獨立二進制或 Kubernetes|
|許可證|Apache 2.0|
|最佳實踐|簡單部署,Grafana 技術棧用户|
Odigos
|特色|詳情|
|-|-|
|重點|帶上下文傳播的全分佈式追蹤|
|語言|Go, Python, Node.js, Java, .NET|
|輸出|OTLP(追蹤)|
|部署|Kubernetes 原生(operator)|
|許可證|Apache 2.0|
|最佳實踐|Kubernetes 環境,分佈式追蹤|
Pixie
|特色|詳情|
|-|-|
|重點|全棧可觀測性,帶集羣內存儲|
|語言|Go, C/C++, Python, Node.js, Java, Rust|
|輸出|Pixie 格式(可導出為 OTel)|
|部署|僅限 Kubernetes|
|許可證|Apache 2.0|
|最佳實踐|調試、臨時查詢、全面可視化|
快速決策指南
5. 設置 Beyla(Grafana 的 eBPF 自動化測量)
Beyla 是入門基於 eBPF 的 OpenTelemetry 測量的最簡單方式。
前置條件
- Linux 內核 5.8+(支持 BTF)
- Root/privileged 訪問
- 目標應用程序正在運行
安裝
# 下載最新版本
curl -LO https://github.com/grafana/beyla/releases/latest/download/beyla-linux-amd64.tar.gz
tar xzf beyla-linux-amd64.tar.gz
sudo mv beyla /usr/local/bin/
配置
創建 beyla-config.yaml:
# beyla-config.yaml
open_port: 8080 # 測量進程監聽端口
# 或目標的可執行名稱
# executable_name: "my-service"
# 或進程 ID
# pid: 12345
# OTLP 導出配置
otel_traces_export:
endpoint: http://localhost:4317 # OTel 收集器
otel_metrics_export:
endpoint: http://localhost:4317
# 可選: 添加資源參數
attributes:
kubernetes:
enable: true # 自動檢測 K8s 元數據
# 採樣 (可選)
traces:
sampler:
name: parentbased_traceidratio
arg: "0.1" # 10% 採樣
運行 Beyla
# 通過配置文件執行
sudo beyla --config beyla-config.yaml
# 或者通過環境變量
sudo BEYLA_OPEN_PORT=8080 \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
beyla
Kubernetes 部署
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: beyla
namespace: observability
spec:
selector:
matchLabels:
app: beyla
template:
metadata:
labels:
app: beyla
spec:
hostPID: true
serviceAccountName: beyla
containers:
- name: beyla
image: grafana/beyla:latest
securityContext:
privileged: true
runAsUser: 0
env:
- name: BEYLA_OPEN_PORT
value: "8080,3000,9090" # 測量端口
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector.observability:4317"
- name: BEYLA_KUBE_METADATA_ENABLE
value: "true"
volumeMounts:
- name: sys-kernel
mountPath: /sys/kernel
readOnly: true
volumes:
- name: sys-kernel
hostPath:
path: /sys/kernel
Beyla 採樣數據
運行後,Beyla 會自動生成:
追蹤:
- HTTP 服務器 span(方法、路徑、狀態、時長)
- HTTP 客户端 span(外出請求)
- gRPC span(方法,狀態)
- SQL 查詢 span(如果使用支持的驅動)
指標:
http.server.request.duration(直方圖)http.server.request.body.sizehttp.client.request.durationrpc.server.durationrpc.client.duration
6. 為 Kubernetes 設置 Odigos
Odigos 提供了更全面的分佈式追蹤,並實現了自動上下文傳播。
安裝
# 安裝 Odigos CLI
brew install odigos-io/homebrew-odigos-cli/odigos
# 或者直接下載
curl -LO https://github.com/odigos-io/odigos/releases/latest/download/odigos-cli-linux-amd64
chmod +x odigos-cli-linux-amd64
sudo mv odigos-cli-linux-amd64 /usr/local/bin/odigos
部署到 Kubernetes
# 在集羣裏安裝 Odigos
odigos install
# 創建:
# - odigos-system namespace
# - Odigos operator
# - Instrumentor DaemonSet
# - OTel Collector (可選)
配置目的地
# 添加可觀測性後端
odigos ui
# 或通過 CLI
odigos destination add oneuptime \
--endpoint https://otlp.oneuptime.com \
--api-key YOUR_API_KEY
測量命名空間
# 測量命名空間中的所有工作負載
odigos instrument namespace my-app-namespace
# 或指定工作負載
odigos instrument deployment my-service -n my-namespace
Odigos 運作方式
Odigos 比簡單的 eBPF 追蹤更智能:
- 語言檢測:自動檢測運行時(Go、Java、Python 等)
- 合適的測量方式:Go 使用 eBPF,Java/Python 注入代理
- 上下文傳播:確保跨越服務邊界追蹤上下文
- 無代碼更改:所有注入均發生在運行時
7. 自動獲取的內容
以下是基於 eBPF 工具能自動獲取或者不能獲取的內容:
自動獲取
|信號|詳情|
|-|-|
|HTTP 服務器請求|方法、路徑、狀態碼、時長、消息頭|
|HTTP 客户端請求|發送請求,含目的地和時間|
|gRPC 調用|方法、狀態、時長(包括服務器和客户端)|
|數據庫查詢|查詢文本、時長、數據庫類型(因工具而異)|
|DNS 查詢|域、查詢時間、結果|
|TCP 連接|源、目的、傳輸字節數|
|TLS 握手|證書信息,握手時間|
部分獲取(因工具/語言而異)
|信號|侷限性|
|-|-|
|消息隊列|Kafka/RabbitMQ 的支持各不相同,可能需要手動設置|
|自定義協議|需要特定工具的支持|
|內部函數調用|只有在符號信息可用的情況下|
|業務邏輯上下文|無法推斷用户 ID、訂單 ID 等信息|
無法獲取(需要手動測量)
|信號|為什麼|
|-|-|
|自定義 span 屬性|eBPF 不知道業務域|
|應用錯誤|異常詳情,stack trace(部分)|
|自定義指標|業務關鍵績效指標(KPI),轉化率|
|Baggage/Context|自定義傳播數據|
8. 將 eBPF 數據與手動測量進行關聯
最佳方法通常是混合式:基礎覆蓋用 eBPF,重要細節用手動測量。
策略:分層測量
示例:混合配置
// Go 服務 - eBPF 自動捕獲 HTTP 處理
// 為重要業務邏輯添加手動 span
func (s *OrderService) CreateOrder(ctx context.Context, req *OrderRequest) (*Order, error) {
// eBPF已經獲取:HTTP POST /orders、計時、狀態
// 業務邏輯細節的手動 span
ctx, span := tracer.Start(ctx, "order.validate")
err := s.validateOrder(ctx, req)
span.End()
if err != nil {
// 手動:添加 eBPF 看不到的錯誤細節
span.RecordError(err)
span.SetStatus(codes.Error, "validation failed")
return nil, err
}
// eBPF 自動捕獲數據庫調用
// 手動:添加業務上下文
ctx, span = tracer.Start(ctx, "order.save")
span.SetAttributes(
attribute.String("order.customer_id", req.CustomerID),
attribute.Float64("order.total", req.Total),
attribute.Int("order.items_count", len(req.Items)),
)
order, err := s.repo.Save(ctx, req)
span.End()
return order, err
}
確保相關性有效
為了讓 eBPF span 和手動 span 出現在同一條追蹤中:
- 相同的 Trace ID:eBPF 工具從輸入請求中提取
traceparent - 上下文傳播:手動 span 必須使用相同的上下文
- 一致導出:eBPF 和手動測量都導出到同一個收集器
# OTel Collector 配置合併兩個源
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
# 添加一致的資源屬性
resource:
attributes:
- key: deployment.environment
value: production
action: upsert
exporters:
otlp:
endpoint: https://oneuptime.com/otlp
headers:
x-oneuptime-token: ${ONEUPTIME_TOKEN}
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, resource]
exporters: [otlp]
9. 性能開銷
關鍵問題: 運行基於 eBPF 的自動化測量的成本是多少?
測量額外開銷
|工具|CPU 開銷|內存|時延影響|
|-|-|-|-|
|Beyla|1-3%|~50-100MB|< 1ms|
|Odigos|2-5%|~100-200MB|< 2ms|
|Pixie|2-5%|~500MB-1GB|< 1ms|
注意:實際開銷因工作負載、採樣率和追蹤端點數量而異。
增加額外開銷的因素
| 因素| 影響| 緩解措施|
|-|-|-|
| 高請求量|更多 eBPF 事件待處理| 增加採樣|
| 追蹤太多端點| 連接太多探針| 要有選擇性|
| 全載荷捕獲| 用於複製數據的內存/CPU|禁用或限制|
| 低採樣率| 更多數據需導出| 使用頭部採樣|
降低開銷
# Beyla 示例: 通過採樣降低開銷
traces:
sampler:
name: parentbased_traceidratio
arg: "0.01" # 1% 採樣
# 隔離高數據量、低價值的端點
routes:
ignored:
- /health
- /ready
- /metrics
10. 侷限性及何時使用手動測量
eBPF 自動測量功能強大,但並非魔法,需要知道取捨。
在以下情況下使用 eBPF 自動化測量:
✅ 需要在多個服務中快速實現基線可觀測性
✅ 不能修改應用代碼(遺留版本,第三方代碼)
✅ 需要一致的 HTTP/gRPC/數據庫追蹤,而不是每個服務單獨設置
✅ 需要網絡層面的可視化(連接、DNS)
✅ 身處混合語言的 Kubernetes 環境中
需要使用手動測量:
✅ 需要自定義業務屬性(用户 ID、訂單 ID、功能標誌)。
✅ 需要詳細的錯誤信息和 stack traces
✅ 需要自定義指標(業務關鍵績效指標、特定事件的計數器)
✅ 追蹤沒有 eBPF 支持的非 HTTP 協議
✅ 需要跨服務上下文的 baggage 傳播
✅ 要控制 span 名稱和結構
eBPF 自動化測量的侷限性
|限制|詳情|
|-|-|
|僅限 Linux|不支持沒有 Linux 內核的 Windows、macOS 或容器運行時|
|內核版本|需要 5.x 以上才能獲得最佳效果,部分功能需要 5.8 以上|
|特權訪問|必須提升權限運行(安全考慮)|
|符號可用性|剝離符號的 Go 二進制可執行文件會降低可見度|
|加密流量|TLS 檢查需要額外設置|
|應用上下文|無法從網絡數據推斷業務含義|
11. 最佳生產實踐
安全考量
運行 eBPF 代理需要提升權限。降低風險:
# Kubernetes: 嚴格使用 RBAC
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ebpf-agent-role
rules:
- apiGroups: [""]
resources: ["pods", "nodes"]
verbs: ["get", "list", "watch"]
# 避免授予不必要的權限
# 儘可能使用 seccomp 配置文件
securityContext:
seccompProfile:
type: RuntimeDefault
限制資源
containers:
- name: ebpf-agent
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
過濾與抽樣
# 不要追蹤每件事 —— 專注於重要的事情
routes:
patterns:
- /api/* # Trace API calls
- /graphql # Trace GraphQL
ignored:
- /health # Skip health checks
- /metrics # Skip metrics endpoint
- /favicon.ico # Skip static assets
# 通過採樣控制數據量
traces:
sampler:
name: parentbased_traceidratio
arg: "0.1" # 10% in production
逐步推廣
# 從非生產環境開始
odigos instrument namespace staging
# 驗證開銷和數據質量
# 然後擴展到生產環境
odigos instrument namespace production
監控
# 導出 eBPF 代理指標
prometheus:
port: 9090
path: /metrics
# 代理有問題時告警
# - 高 CPU 使用率
# - 事件丟失
# - 導出失敗
12. 結論
基於 eBPF 的自動化測量代表了可觀測性的範式轉變。通過將測量遷移到內核級,我們可以:
- 消除測量負擔:不再按服務集成 SDK
- 實現全覆蓋:觀察任何應用,任何語言
- 減少盲點:發現那些被忽視的服務
- 加快上線速度:新服務可立即被觀測到
但並不能完全取代傳統測量。最佳可觀測性策略結合了:
- eBPF 用於基線基礎設施層級可視化
- 針對特定框架上下文的自動化測量庫
- 為業務關鍵範圍和自定義屬性提供手動測量
像 Beyla 和 Odigo 這樣的工具讓入門變得前所未有的簡單。如果應用運行在 Kubernetes 和 Linux 上,只需要幾分鐘就可以實現整個分佈式追蹤技術棧。
要點
- eBPF 通過從內核觀測應用實現零代碼儀表化
- OpenTelemetry 兼容性意味着 eBPF 數據會流入現有可觀測棧
- 選擇合適的工具:Beyla 簡化應用,Odigos 支持 Kubernetes 分佈式追蹤,Pixie 負責調試
- 混合方法效果最佳:eBPF 用於覆蓋,手動測量用於業務環境
- 開銷低(1-5% CPU),但要監控並使用採樣
- 安全問題:eBPF 需要特權,授權範圍要適當
- 從小處開始:先從非生產環境開始,再擴展到生產環境
延伸閲讀
What is eBPF and How Does It Work? —— 深入探討 eBPF 基礎知識
Traces and Spans in OpenTelemetry —— 理解分佈式追蹤
What are Metrics in OpenTelemetry? —— 指標基礎
Logs, Metrics & Traces: The Three Pillars —— 完整的可觀察性概述
Basics of Profiling —— 需要更深入的性能洞察時
Hi,我是俞凡,一名兼具技術深度與管理視野的技術管理者。曾就職於 Motorola,現任職於 Mavenir,多年帶領技術團隊,聚焦後端架構與雲原生,持續關注 AI 等前沿方向,也關注人的成長,篤信持續學習的力量。在這裏,我會分享技術實踐與思考。歡迎關注公眾號「DeepNoMind」,星標不迷路。也歡迎訪問獨立站 www.DeepNoMind.com,一起交流成長。
本文由mdnice多平台發佈