mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1367 字
4 分钟
eBPF 可观测性
2026-04-16

凌晨 3 点,服务延迟飙升。你需要知道:哪个函数耗时最长?哪个系统调用最频繁?哪个进程在疯狂分配内存?传统的 APM 方案需要修改代码、部署 Agent,而 eBPF 提供了一种全新的可观测性范式——零侵入、低开销、内核级的追踪能力。

Brendan Gregg 将 eBPF 称为”Linux 可观测性的未来”——它不仅替代了 strace/perf 的部分功能,更提供了传统工具无法实现的内核级深度可观测性。

一、eBPF 可观测性的优势#

1.1 与传统方案对比#

维度传统 APMeBPF 可观测性
侵入性需修改代码/部署 Agent零侵入
开销5-15%<1-3%
覆盖范围应用层内核 + 应用层
部署需要应用配合即插即用
语言依赖需 SDK语言无关
动态性需重启动态加载/卸载

1.2 eBPF 可观测性工具链#

flowchart TB subgraph 高级工具["高级工具(开箱即用)"] BEYLA["Beyla<br/>零侵入 APM"] HUBBLE["Hubble<br/>网络可观测性"] PIXIE["Pixie<br/>K8s 自动可观测性"] end subgraph 中级工具["中级工具(脚本化)"] BCC["BCC<br/>Python + 丰富工具"] BPFTRACE["bpftrace<br/>一行命令追踪"] end subgraph 低级工具["低级工具(编程)"] LIBBPF["libbpf<br/>C 开发库"] GOEBPF["cilium/ebpf<br/>Go 开发库"] end 高级工具 --> 中级工具 中级工具 --> 低级工具 style 高级工具 fill:#c8e6c9,stroke:#2e7d32 style 中级工具 fill:#bbdefb,stroke:#1565c0 style 低级工具 fill:#fff9c4,stroke:#f9a825

二、bpftrace:一行命令追踪内核#

2.1 bpftrace 语法#

bpftrace 使用类似 DTrace/awk 的高级语法:

probe[,probe...] [if (filter)] { action }
语法元素说明示例
probe探针定义kprobe:do_sys_open
filter过滤条件/pid == 1234/
action动作块{ printf(...); }
@mapMap 变量@count[comm] = count()
builtins内置变量pid, comm, tid, args
functions内置函数printf(), count(), sum()

2.2 常用内置变量#

变量类型说明
pidu64进程 ID
tidu64线程 ID
uidu64用户 ID
commstring进程名
nsecsu64纳秒时间戳
elapsedu64程序运行时间
cpuu32CPU 编号
curtasku64当前 task_struct 指针
argsstructtracepoint 参数

2.3 常用内置函数#

函数说明
printf(fmt, …)格式化输出
count()计数
sum(val)求和
avg(val)平均值
min(val)最小值
max(val)最大值
hist(val)直方图(log2)
lhist(val, min, max, step)线性直方图
stack()内核调用栈
ustack()用户态调用栈
str(ptr)读取字符串
exit()退出 bpftrace

2.4 bpftrace 实战示例#

# 统计每个进程的系统调用次数
sudo bpftrace -e '
tracepoint:raw_syscalls:sys_enter {
@syscalls[comm] = count();
}'
# 追踪文件打开,按进程统计
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_openat {
@opens[comm] = count();
}'
# I/O 延迟直方图
sudo bpftrace -e '
kprobe:vfs_read { @start[tid] = nsecs; }
kretprobe:vfs_read /@start[tid]/ {
@latency = hist(nsecs - @start[tid]);
delete(@start[tid]);
}'
# 追踪 TCP 连接延迟
sudo bpftrace -e '
kprobe:tcp_v4_connect { @start[tid] = nsecs; }
kretprobe:tcp_v4_connect /@start[tid]/ {
@connect_ns = hist(nsecs - @start[tid]);
delete(@start[tid]);
}'
# 火焰图数据采集
sudo bpftrace -e '
profile:hz:99 {
@[ustack()] = count();
}'

2.5 bpftrace 的 Map 操作#

# 定期打印并清空 Map
sudo bpftrace -e '
tracepoint:raw_syscalls:sys_enter {
@syscalls[comm] = count();
}
interval:s:5 {
print(@syscalls);
clear(@syscalls);
}'
# 打印后不清空(累计值)
sudo bpftrace -e '
kprobe:vfs_read {
@bytes[comm] = sum(retval);
}
interval:s:10 {
print(@bytes);
}'

三、BCC:Python 前端 + 丰富工具集#

3.1 BCC 工具一览#

工具用途替代传统工具
execsnoop追踪进程执行strace -e execve
opensnoop追踪文件打开strace -e openat
biolatencyI/O 延迟直方图iostat
biosnoopI/O 事件追踪iotop
biotopI/O Topiotop
tcplifeTCP 连接生命周期ss
tcptracerTCP 连接事件ss
tcpconnectTCP 主动连接netstat
tcpacceptTCP 被动连接netstat
tcpretransTCP 重传
sockstatSocket 统计ss
slabratetopSlab 分配速率slabtop
memleak内存泄漏检测valgrind
offcputime离 CPU 时间perf
profileCPU 性能分析perf top
stackcount调用栈计数perf
syncsnoop同步原语追踪
vfsstatVFS 统计
writeback页写回追踪

3.2 BCC Python 前端#

#!/usr/bin/env python3
"""追踪进程执行,过滤指定父进程"""
from bcc import BPF
import argparse
# eBPF C 代码
bpf_text = """
#include <uapi/linux/ptrace.h>
struct event {
u32 pid;
u32 ppid;
char comm[16];
char filename[256];
};
BPF_RINGBUF(events);
TRACEPOINT_PROBE(sched, sched_process_exec) {
struct event *e = events.ringbuf_reserve(sizeof(*e));
if (!e) return 0;
e->pid = bpf_get_current_pid_tgid() >> 32;
e->ppid = PPID_FILTER; // 运行时替换
bpf_get_current_comm(&e->comm, sizeof(e->comm));
bpf_probe_read_kernel_str(&e->filename, sizeof(e->filename),
args->filename);
events.ringbuf_submit(e, 0);
return 0;
}
"""
# 解析参数
parser = argparse.ArgumentParser()
parser.add_argument("--ppid", type=int, default=0)
args = parser.parse_args()
# 替换过滤器
bpf_text = bpf_text.replace("PPID_FILTER", str(args.ppid))
# 加载 eBPF 程序
b = BPF(text=bpf_text)
# 事件回调
def handle_event(cpu, data, size):
event = b["events"].event(data)
print(f"PID={event.pid} PPID={event.ppid} "
f"COMM={event.comm.decode()} FILE={event.filename.decode()}")
b["events"].open_ring_buffer(handle_event)
print("Tracing process exec... Hit Ctrl-C to end")
while True:
try:
b.ring_buffer_consume()
except KeyboardInterrupt:
break

3.3 BCC 工具使用示例#

# 追踪所有进程执行
sudo /usr/share/bcc/tools/execsnoop
# 追踪文件打开
sudo /usr/share/bcc/tools/opensnoop
# I/O 延迟直方图
sudo /usr/share/bcc/tools/biolatency
# TCP 连接生命周期
sudo /usr/share/bcc/tools/tcplife
# 内存泄漏检测(追踪 malloc/free 不匹配)
sudo /usr/share/bcc/tools/memleak -p 1234
# CPU 火焰图
sudo /usr/share/bcc/tools/profile -F 99 -f > out.stacks

四、Beyla:零侵入应用性能监控#

4.1 Beyla 架构#

Beyla 是 Grafana 开源的零侵入 APM 工具,基于 eBPF 自动发现和监控应用:

flowchart TB APP["应用程序<br/>(无需修改)"] -->|"eBPF uprobe"| BEYLA["Beyla Agent"] BEYLA -->|"自动发现"| DISCOVER["应用发现<br/>HTTP/gRPC/SQL"] BEYLA -->|"自动追踪"| TRACE["分布式追踪<br/>OpenTelemetry"] BEYLA -->|"自动指标"| METRIC["应用指标<br/>RED 指标"] TRACE --> OTEL["OpenTelemetry Collector"] METRIC --> OTEL OTEL --> GRAFANA["Grafana"] style BEYLA fill:#c8e6c9,stroke:#2e7d32

4.2 Beyla 的核心能力#

能力说明
自动发现基于进程/端口自动发现 HTTP/gRPC 服务
零侵入无需修改应用代码或部署 SDK
分布式追踪自动生成 OpenTelemetry Span
RED 指标Rate/Errors/Duration 自动采集
语言无关支持 Go/Java/Node.js/Python/Rust 等
K8s 原生自动关联 K8s 元数据

4.3 Beyla 部署#

# Beyla 部署配置
apiVersion: v1
kind: ConfigMap
metadata:
name: beyla-config
data:
beyla-config.yml: |
open_telemetry:
traces:
endpoint: "http://otel-collector:4318/v1/traces"
metrics:
endpoint: "http://otel-collector:4318/v1/metrics"
discovery:
services:
- name: myapp
namespace: default
instrumentations: [http]
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: beyla
spec:
selector:
matchLabels:
app: beyla
template:
spec:
containers:
- name: beyla
image: grafana/beyla:latest
securityContext:
capabilities:
add: ["CAP_BPF", "CAP_PERFMON", "CAP_SYS_RESOURCE"]
volumeMounts:
- name: config
mountPath: /etc/beyla
volumes:
- name: config
configMap:
name: beyla-config

五、性能分析方法论#

5.1 USE 方法#

Brendan Gregg 提出的 USE 方法(Utilization / Saturation / Errors):

指标说明bpftrace/BCC 工具
Utilization资源使用率profile, biolatency
Saturation资源饱和度(排队)offcputime, runqlat
Errors错误数tcpretrans, biotop

5.2 性能分析工作流#

flowchart LR A["1. 确定问题<br/>延迟?吞吐?错误?"] --> B["2. 量化问题<br/>具体数值"] B --> C["3. 缩小范围<br/>CPU/内存/IO/网络"] C --> D["4. 深入分析<br/>函数级/调用栈"] D --> E["5. 定位根因<br/>代码行级"] E --> F["6. 修复验证<br/>对比前后"] style A fill:#bbdefb,stroke:#1565c0 style D fill:#fff9c4,stroke:#f9a825 style E fill:#ffcdd2,stroke:#c62828 style F fill:#c8e6c9,stroke:#2e7d32

5.3 常用分析命令#

# CPU 分析
sudo bpftrace -e 'profile:hz:99 { @[ustack()] = count(); }' # 火焰图
sudo /usr/share/bcc/tools/offcputime # 离 CPU 时间
# 内存分析
sudo /usr/share/bcc/tools/memleak -p <pid> # 内存泄漏
sudo bpftrace -e 'kprobe:kmalloc { @size[comm] = hist(arg1); }' # 分配大小
# I/O 分析
sudo /usr/share/bcc/tools/biolatency # I/O 延迟
sudo /usr/share/bcc/tools/biosnoop # I/O 事件
# 网络分析
sudo /usr/share/bcc/tools/tcplife # TCP 连接生命周期
sudo /usr/share/bcc/tools/tcpretrans # TCP 重传
sudo bpftrace -e 'kprobe:tcp_drop { printf("drop: %s\n", comm); }' # 丢包

六、动手实践#

6.1 使用 bpftrace 进行 CPU 性能分析#

# 采样 CPU 调用栈(99 Hz)
sudo bpftrace -e 'profile:hz:99 { @[ustack()] = count(); }' > cpu.stacks
# 生成火焰图(使用 FlameGraph 工具)
git clone https://github.com/brendangregg/FlameGraph
sudo bpftrace -e 'profile:hz:99 { @[ustack()] = count(); }' | \
./FlameGraph/stackcollapse-bpftrace.pl | \
./FlameGraph/flamegraph.pl > cpu.svg

6.2 使用 BCC 进行 I/O 分析#

# I/O 延迟直方图
sudo /usr/share/bcc/tools/biolatency
# usecs : count distribution
# 0 -> 1 : 0 | |
# 2 -> 3 : 0 | |
# 4 -> 7 : 0 | |
# 8 -> 15 : 12 |******** |
# 16 -> 31 : 45 |***************************** |
# 32 -> 63 : 89 |************************************************|
# 64 -> 127 : 23 |*************** |
# I/O Top
sudo /usr/share/bcc/tools/biotop

6.3 部署 Beyla 实现零侵入 APM#

# Docker 部署 Beyla
docker run -d --name beyla \
--pid=host \
--cap-add=CAP_BPF \
--cap-add=CAP_PERFMON \
-e BEYLA_OPEN_TELEMETRY_TRACES_ENDPOINT=http://otel:4318/v1/traces \
-e BEYLA_DISCOVERY_SERVICES_0_NAME=myapp \
-e BEYLA_DISCOVERY_SERVICES_0_INSTRUMENTATIONS=http \
grafana/beyla:latest

七、BCC → libbpf-tools 迁移#

7.1 为什么要迁移#

BCC 工具运行时依赖内核头文件编译,在不同内核版本上经常编译失败。libbpf-tools 预编译 eBPF 程序,通过 CO-RE 实现一次编译到处运行:

维度BCClibbpf-tools
编译时机运行时编译预编译
内核头文件必须安装不需要
启动速度慢(编译耗时)快(直接加载)
可移植性好(CO-RE)
依赖clang、llvm、kernel-headers仅 libbpf
Warning

bpftrace 和 BCC 在生产环境中可能因编译失败导致诊断工具不可用。对于关键排障场景,建议使用 libbpf-tools 的预编译二进制,确保工具随时可用。

7.2 追踪上下文传播#

eBPF uprobe 可以拦截用户态函数调用,将内核级追踪数据与 OpenTelemetry Span 关联:

// uprobe 拦截 HTTP 请求处理函数,提取 trace context
SEC("uprobe/http_server_handle_request")
int trace_http(struct pt_regs *ctx)
{
// 从函数参数中提取 trace_id
// 通过 Ring Buffer 发送到用户态
// 用户态 Agent 关联 OTEL Span
return 0;
}

这种关联方式让 eBPF 追踪数据可以与分布式追踪系统(Jaeger/Zipkin)整合,实现从内核到应用的全链路可观测。

Note

Beyla 目前支持 Go、Java、Node.js、.NET、Python 和 Rust 应用的自动仪器化。对于不支持的运行时,可以通过手动注入 OpenTelemetry SDK 配合 eBPF 追踪使用。

flowchart TB KERNEL["内核事件<br/>tracepoint/kprobe"] --> BPF["eBPF 程序<br/>过滤+聚合"] BPF --> RB["Ring Buffer<br/>Perf Buffer"] --> USER["用户态处理"] USER --> BCC["BCC Python"] USER --> BPFTRACE["bpftrace DSL"] USER --> BEYLA["Beyla APM"] style BPF fill:#bbdefb,stroke:#1565c0 style RB fill:#fff9c4,stroke:#f9a825

八、本章小结#

上一章从全景视角介绍了eBPF 安全与 LSM。 本章详解了 eBPF 可观测性的三大工具链:

主题核心要点关键词
bpftrace一行命令追踪内核,适合快速诊断和临时分析bpftrace
BCCPython 前端 + 丰富工具集,适合日常运维和自动化分析BCC
Beyla零侵入 APM,自动发现和监控应用,适合持续可观测性Beyla

选择工具的决策:快速诊断用 bpftrace,日常运维用 BCC,持续监控用 Beyla

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

eBPF 可观测性
https://blog.souloss.com/posts/ebpf/ebpf-observability/
作者
Souloss
发布于
2026-04-16
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

相关文章 智能推荐
1
eBPF Hook 点:kprobe/tracepoint/uprobe
eBPF eBPF 程序的价值在于它能挂载到内核的各种检查点——Hook 点。本章详解三大 Hook 机制——kprobe(动态内核函数追踪)、tracepoint(静态追踪点)、uprobe(用户态函数追踪),以及 USDT 静态用户态追踪点,并通过实战代码展示每种 Hook 的使用方式与适用场景。
2
eBPF 生产部署
eBPF eBPF 生产部署实践——性能开销分析、调试技巧、版本兼容、内核版本要求与升级策略。
3
eBPF 与 WebAssembly
eBPF eBPF 提供内核可编程能力,WebAssembly 提供跨平台可移植性——两者的融合会带来什么?本章详解 Wasm-eBPF 项目、用户态 eBPF 运行时、eBPF 程序的 Wasm 封装,以及 eBPF + Wasm 在边缘计算、插件系统、跨平台可观测性中的应用前景。
4
eBPF 网络全景
eBPF eBPF 正在重新定义 Linux 网络栈——从连接跟踪、NAT 到 kube-proxy 替代,从 Socket Filter 到 Sk_msg 重定向,eBPF 提供了比 iptables/netfilter 更高性能、更灵活的网络方案。本章从宏观视角展示 eBPF 网络的全景,详解连接跟踪、NAT、kube-proxy 替代、Socket 层 eBPF,并对比 eBPF 与传统网络方案的架构差异。
5
eBPF 与内存管理
eBPF eBPF 程序的内存使用是生产部署中的关键考量——Map 占用多少内存?eBPF 程序的栈空间有多大?容器环境中的 eBPF 内存如何计费?本章详解 eBPF 的内存模型、Map 内存开销计算、容器内存追踪、eBPF-mm 子系统,以及内存限制下的优化策略。