前言
三年前第一次听说eBPF,觉得这玩意离我太远------内核开发?那是神仙干的事。
直到前段时间排查一个诡异的网络延迟问题,传统工具都定位不到根因,同事甩给我一个bpftrace脚本,几秒钟就抓到了问题。那一刻我才意识到,eBPF已经不是什么高深莫测的东西,而是实打实能解决问题的生产力工具。
这篇文章是我学习和使用eBPF的一些记录,不讲太多底层原理,主要聊聊怎么用它解决实际问题。
eBPF是什么
简单说,eBPF(extended Berkeley Packet Filter)让你可以在内核里安全地运行自定义程序,而不需要修改内核源码或加载内核模块。
传统的做法要观测内核行为,要么改内核重新编译,要么写个内核模块。两个方案都很重,风险也大。eBPF相当于在内核里开了个"沙盒",你的程序在里面跑,既能访问内核数据,又不会把系统搞崩。
能干什么
- 网络:高性能负载均衡、DDoS防护、流量过滤
- 安全:系统调用审计、入侵检测、容器安全
- 观测:性能分析、延迟追踪、资源监控
- 调试:内核函数追踪、用户态程序分析
Cloudflare用eBPF扛DDoS,Facebook用它做负载均衡,Cilium用它搞容器网络。这东西已经在生产环境大规模使用了。
环境准备
eBPF需要内核版本支持,最低4.x,建议5.x以上。Ubuntu 20.04/22.04都没问题。
bash
# 检查内核版本
uname -r
# 5.15.0-91-generic
# 安装BCC工具集(最常用的eBPF工具集)
apt update
apt install -y bpfcc-tools linux-headers-$(uname -r)
# 安装bpftrace(高级追踪语言)
apt install -y bpftrace
# 验证安装
bpftrace --version
# bpftrace v0.14.0
实战一:追踪系统调用延迟
前几天遇到一个问题:某个Java服务响应变慢,但CPU、内存、IO看着都正常。
用bpftrace追踪一下read系统调用的延迟分布:
bash
# 追踪read调用延迟(按进程名过滤)
bpftrace -e '
tracepoint:syscalls:sys_enter_read /comm == "java"/ {
@start[tid] = nsecs;
}
tracepoint:syscalls:sys_exit_read /comm == "java" && @start[tid]/ {
@usecs = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}
'
输出:
less
@usecs:
[0] 156 |@@@@@@@@@@@@@@@ |
[1] 489 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[2, 4) 234 |@@@@@@@@@@@@@@@@@@@@@@@ |
[4, 8) 67 |@@@@@@ |
[8, 16) 23 |@@ |
[16, 32) 12 |@ |
[32, 64) 8 | |
[64, 128) 3 | |
[128, 256) 156 |@@@@@@@@@@@@@@@ | <-- 异常
128-256微秒这个区间的调用数量异常偏多,正常应该是单峰分布。进一步追踪发现是读取某个配置文件时,文件系统有锁竞争。
这种问题用传统工具(strace会拖慢进程太多)很难快速定位,eBPF几乎零开销。
实战二:网络延迟分析
生产环境有台机器TCP延迟偶发飙高,网络组说线路没问题。用tcpretrans追踪重传:
bash
# BCC工具:追踪TCP重传
/usr/share/bcc/tools/tcpretrans
# 输出
TIME PID IP LADDR:LPORT T> RADDR:RPORT STATE
14:23:15 0 4 10.0.1.5:443 R> 10.0.2.8:52341 ESTABLISHED
14:23:15 0 4 10.0.1.5:443 R> 10.0.2.8:52341 ESTABLISHED
14:23:16 0 4 10.0.1.5:443 R> 10.0.2.8:52341 ESTABLISHED
同一个连接连续重传,问题缩小到10.0.2.x这个网段。最后查出来是那个机房的交换机有问题。
更进一步,看TCP连接延迟分布:
bash
# 追踪TCP连接建立延迟
/usr/share/bcc/tools/tcpconnlat
# 输出
PID COMM IP SADDR DADDR DPORT LAT(ms)
1892 curl 4 10.0.1.5 10.0.2.8 443 245.12
1893 curl 4 10.0.1.5 10.0.2.8 443 312.45
1894 curl 4 10.0.1.5 10.0.3.9 443 1.23
对比很明显,连10.0.2网段延迟高了两个数量级。
实战三:进程级资源监控
有个容器CPU用量一直很高,但top里看不出哪个函数在消耗。用profile工具:
bash
# CPU采样火焰图数据
/usr/share/bcc/tools/profile -p $(pgrep -f myapp) -f 30 > profile.out
# 生成火焰图(需要安装FlameGraph)
git clone https://github.com/brendangregg/FlameGraph
./FlameGraph/flamegraph.pl profile.out > cpu.svg
火焰图一目了然,发现某个JSON解析函数占了40%的CPU。原来是每次请求都在重复解析同一个大配置文件,加个缓存解决。
实战四:自定义追踪点
有时候需要追踪特定的内核函数。比如想知道文件打开操作的分布:
bash
bpftrace -e '
kprobe:do_sys_openat2 {
@files[str(arg1)] = count();
}
interval:s:5 {
print(@files);
clear(@files);
}
'
输出每5秒打印一次文件打开统计:
less
@files[/etc/ld.so.cache]: 234
@files[/lib/x86_64-linux-gnu/libc.so.6]: 156
@files[/proc/self/status]: 89
@files[/app/config.json]: 67
...
这种方式对排查"到底谁在频繁读写某个文件"特别有用。
写个简单的eBPF程序
BCC提供Python接口,写起来比较方便。追踪所有的execve调用(新进程启动):
python
#!/usr/bin/env python3
from bcc import BPF
# eBPF程序(C语言)
prog = """
#include <linux/sched.h>
struct data_t {
u32 pid;
char comm[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(events);
int trace_execve(struct pt_regs *ctx) {
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid() >> 32;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
"""
b = BPF(text=prog)
b.attach_kprobe(event="__x64_sys_execve", fn_name="trace_execve")
print("追踪新进程启动,Ctrl+C退出...")
def print_event(cpu, data, size):
event = b["events"].event(data)
print(f"PID: {event.pid}, COMM: {event.comm.decode()}")
b["events"].open_perf_buffer(print_event)
while True:
b.perf_buffer_poll()
运行效果:
yaml
追踪新进程启动,Ctrl+C退出...
PID: 12345, COMM: bash
PID: 12346, COMM: ls
PID: 12347, COMM: grep
这对安全审计很有价值------谁在服务器上执行了什么命令,一清二楚。
生产环境使用建议
性能开销
eBPF的开销很低,但不是零。几个原则:
- 过滤要前置:在eBPF程序里做过滤,而不是全量采集再在用户态过滤
- 采样而非全量:profile用采样,不要每个事件都追踪
- 控制输出频率:用interval聚合,不要每个事件都输出
常用工具速查
bash
# 性能分析
profile # CPU火焰图
offcputime # Off-CPU时间分析
runqlat # 运行队列延迟
# 网络
tcplife # TCP连接生命周期
tcpretrans # TCP重传追踪
tcpconnlat # TCP连接延迟
# 磁盘IO
biolatency # 块设备IO延迟
biosnoop # 块设备IO追踪
ext4slower # ext4慢操作
# 内存
memleak # 内存泄漏检测
cachestat # 缓存命中率
# 系统调用
execsnoop # 进程启动追踪
opensnoop # 文件打开追踪
多机器批量排查
遇到跨多台服务器的问题时,需要同时在多个节点运行eBPF工具做对比分析。这种场景下我会用星空组网先把各个网段的机器串起来,统一管理后再批量跑诊断脚本,比逐台SSH效率高很多。
进阶学习资源
- 《BPF Performance Tools》 by Brendan Gregg - 这本书是圣经级别的
- bcc官方仓库:github.com/iovisor/bcc - 大量现成工具和示例
- bpftrace参考:github.com/iovisor/bpftrace - 追踪语言文档
- Cilium eBPF教程:docs.cilium.io - 网络方向的最佳实践
总结
eBPF不再是内核黑客的专属玩具。对于运维和后端开发来说,它是一个威力巨大的问题排查工具:
- 低开销:生产环境可用
- 高精度:内核级别的观测能力
- 灵活:可以自定义追踪逻辑
- 安全:内核保证程序不会搞崩系统
从BCC工具集入手,遇到问题先试试现成工具,熟练后再尝试写自定义追踪程序。这个学习路径比较平滑。
说实话,用惯了eBPF再回头看传统的排查手段,会觉得效率差太多。推荐每个做服务端的同学都了解一下。