当系统出现 CPU 飙高、网络延迟莫名增大、某个进程频繁崩溃时,传统排查思路往往依赖 top、strace、ss 这类工具。它们能用,但都有各自的盲区:strace 对生产环境有不可忽略的性能开销,ss 只能看连接状态,看不到内核协议栈里真正发生了什么。eBPF 的出现,把这种“隔靴搔痒”的诊断方式变成了一套可以直接在内核里挂载探针的安全机制。

很多团队第一次了解 eBPF,其实都是被逼的。线上服务某个接口耗时突然从 20ms 涨到 300ms,用 top 看 CPU 不高,用 strace 跟踪系统调用耗损太大,perf 的事件周期又太粗。后来用 BCC 的 tcpconnect 挂在 kprobe 上,才发现出问题的请求到了一个已被安全组禁用的内网地址,每次都卡在连接超时上。这种现场,就是 eBPF 工具链最典型的舞台。
eBPF 在内核里做了什么
eBPF 全称 Extended Berkeley Packet Filter,最早源于 BPF,但现在已经远远超越包过滤的范畴。它允许用户编写一段字节码,通过内核验证器校验后,动态挂载到内核的 kprobe、tracepoint、uprobe 等位置。因为运行在内核态,它可以拿到普通用户态工具拿不到的上下文;又因为是解释执行或 JIT 执行,它相对安全,不会因为探针逻辑写错就直接把内核打崩。
这里有个关键点:eBPF 不是内核模块,不需要自己管理内存、不需要重新编译内核。它更像是一个受限的沙箱,里面可以访问指定的辅助函数和数据结构。这种设计让它在生产环境具备「敢用」的前提。
BCC 和 bpftrace 不是替代关系
一上来就纠结 BCC 和 bpftrace 选哪个,其实把问题问错了。它们位于工具链的不同层级,解决的问题也不同。
BCC 是一个完整的编程框架,提供 Python、Lua 等绑定,适合写几十行甚至上百行的复杂 BPF 程序。比如你要统计每个进程的读写字节数、按容器维度聚合,或者操作 map 做时序计算,BCC 会省很多事。缺点是每次都要经过 Python 运行时的初始化,脚本启动有几毫秒到几十毫秒的延迟,在瞬时故障现场可能来不及。
bpftrace 则是设计给「快速问答」用的。它像 awk 一样用一行脚本描述探针、过滤条件和动作。例如你想知道当前系统里哪些进程在做文件写,只需要这样写:
bpftrace -e 'kprobe:vfs_write { @[comm] = count(); }'
这一行就定义了一个 kprobe,每次 vfs_write 被调用时,将当前进程名作为键加一。整个程序可以立即运行并输出统计结果。没有 Python 环境依赖,也没有编译步骤,适合在故障现场直接跑。
另一个常见场景是定位容器网络重传。用 bpftrace 的 tracepoint 观察 TCP 重传事件,一行命令就能输出重传的源端口、目的端口和次数;如果你想把这个数据再加工成结构化报告,或者跟自己的监控平台打通,那 BCC 的 Python 脚本会更顺手。
BCC 与 bpftrace 的核心区别
| 维度 | BCC | bpftrace |
|---|---|---|
| 表达复杂度 | 高,可自由定义数据结构 | 低,适合单行或少量行 |
| 上手门槛 | 需要 Python 和 BPF 概念 | 类 awk,几分钟可上手 |
| 启动开销 | 较大,需加载 Python 运行时 | 很小,秒级输出 |
| 适合场景 | 长期工具、复杂逻辑、数据聚合 | 现场问答、快速验证 |
| 生产风险 | 可以精细控制探针频率和逻辑 | 单行脚本容易误用高频探针,需要关注 |
这张表不是要告诉你哪个更好,而是让你意识到:如果你只是想快速回答「现在发生了什么」,bpftrace 是你的第一选择;如果你要做成一套持续运行的观测能力,BCC 或者基于 libbpf 的原生工具更合适。
使用 eBPF 前必须想清楚的三个问题
eBPF 不是银弹,我们见过最多的坑往往不在技术本身,而在这三件事上:
- 内核版本兼容性。eBPF 的 helper 函数和探针类型是随内核版本逐渐增加的。比如 kprobe 在 4.1 引入,bpf_iter 到 5.8 才稳定。如果你的生产环境还是 3.10,很多 BCC 工具根本加载不了,或者需要打补丁。建议先确认内核版本,再决定用哪些工具。
- 权限边界。挂载 eBPF 程序通常需要 root 或 CAP_BPF、CAP_SYS_ADMIN。在 Docker 容器里跑 bpftrace,常会遇到的权限错误是容器没挂载 /sys/kernel/debug,或者没有对应 capability。一般需要以特权容器或直接在宿主机上运行。
- 探针频率的代价。虽然 eBPF 比传统内核模块安全,但高频探针仍然会带来额外开销。例如对 do_sys_open 做全局 kprobe,在高 IOPS 文件服务器上可能让系统调用延迟增加几个百分点。线上排查时建议先加采样条件,比如只统计特定 PID。
这三点里,最容易被忽视的是第一点。很多团队把 BCC 的网站演示默认当成新内核环境下运行的结果,部署到老内核上才发现一堆工具不可用。一个务实的方法是先跑一遍 BCC 自带的 kernel_tools 检测脚本,看看当前环境支持哪些探针。
从入门到落地的工具链路径
如果你刚接触 eBPF,我不建议一上来就啃 libbpf 源码。我的建议路径是:
- 先安装 BCC,把 execsnoop、biosnoop、tcpconnect 这些现成工具跑一遍。让它们输出一遍系统里的进程创建、块 IO 和 TCP 连接事件,你就能直观感受 eBPF 能拿到哪些数据。
- 然后用 bpftrace 写一些单行命令,比如追踪 syscall、统计文件写次数,练习探针语法和过滤条件。
- 当你需要把观测逻辑固化成一个工具,且需要跟现有监控系统集成时,再回到 BCC,或者直接用 libbpf + C 开发。
这个路径的核心是:先建立对「可观测性数据」的直觉,再逐步加深对工具细节的理解。你不需要一开始就掌握所有探针类型和 map 结构,大多数排查问题只用得到几个常用探针。
实际项目里,很多团队因为某个故障引入 eBPF 工具链,然后慢慢把它沉淀成一套内部诊断工具集。比如用 BCC 写一个脚本,统计每个容器的 TCP 重传次数和延迟分布,输出到 Prometheus;再用 bpftrace 写几个快捷方式,在登录跳板机时直接可用。
最后提醒一句:不要把所有探针长期无条件开着。建议在需要诊断的窗口内开启,结束后关闭。毕竟 eBPF 再安全,也是有开销的,而且大量探针的输出也会增加数据处理的压力。
写在最后
eBPF 把内核的隐藏行为以一种安全、高效的方式暴露给了开发者,而 BCC 和 bpftrace 是进入这个世界的两条不同路径。没有哪一个是「标准答案」,符合你当前阶段和问题形态的,才是合适的工具。先把这两套工具用熟,至少以后遇到「内核里发生了什么」这类问题时,你多了一个可以直视现场的窗口。
原创文章,作者:fudengji,如若转载,请注明出处:https://fudengji.cn/article/703/