1. 项目概述:给 CentOS 7 补上 eBPF 动态追踪能力
如果你常年跟服务器性能问题打交道,bcc(BPF Compiler Collection)和 bpftrace 这两个名字应该不陌生。bcc 是一整套基于 eBPF 的工具集,里面预置了几十个现成工具,覆盖 CPU、内存、IO、网络、文件系统等各个维度的观测场景;bpftrace 则是更轻量级的追踪语言前端,可以把一段追踪逻辑压缩成一行脚本,临时排查问题特别顺手。这两者搭配起来,基本上能从内核层面解释大部分"系统突然变慢""某个进程卡住""IO 毛刺从哪来"的疑难杂症。
问题在于,CentOS 7 这个存量极大的系统,要跑起这一套东西并不像 Ubuntu 上apt install那样简单。它有两个天生的坎:默认内核停留在 3.10,太老;官方软件源里根本没有 bcc 和 bpftrace 的包。很多人在这上面折腾到一半就放弃了,其实倒不是工具本身复杂,而是被"内核版本不满足 + 依赖找不到 + 仓库没配好"这三座大山压住了。
这篇文章就是把我在 CentOS 7 上从零搭建 bcc 和 bpftrace 的完整过程写出来,包括内核升级、依赖匹配、包来源选择、常见报错和排查思路。适合两类人看:一类是生产环境还是 CentOS 7、想把动态追踪工具补齐的运维同学;另一类是刚接触 eBPF、手头只有 CentOS 7 机器、想拿现成工具练手的开发者。流程照着走基本能通,但里面有几个坑如果不知道,会走不少弯路。
2. 关键决策:内核升不升,包从哪来
2.1 内核升级是绕不开的一步
先说结论:要正经用 bcc 和 bpftrace,CentOS 7 自带的 3.10 内核撑不住。
bcc 官方文档里写得很直白,绝大多数工具要求内核 4.1 以上,部分新特性要求 4.9 甚至 4.14。CentOS 7 的 3.10 内核虽然被红帽回移了一部分 eBPF 能力,但那是残缺的——很多 helper 函数、map 类型、kprobe/tracepoint 相关的设施没有完整移植。实际表现就是,部分简单工具能启动一两个,但稍微复杂一点的工具在加载 BPF 程序时直接报invalid argument或者找不到事件源。
bpftrace 对内核的要求更敏感。它底层依赖的 attach 方式、map 操作、perf event 输出,在内核 4.4 之前都有不少限制。所以哪怕你只是想在 CentOS 7 上跑个一行脚本,内核这一步就得认真对待。
升级内核走 ELRepo 是 CentOS 7 生态里非常成熟的做法,生产环境用得很普遍。ELRepo 提供两个主流内核系列,选择逻辑很清晰:
| 内核系列 | 版本特征 | 适合场景 | 对 bcc/bpftrace 的友好度 |
|---|---|---|---|
| kernel-ml | 主线最新内核,迭代快 | 需要完整新特性,比如 BTF、全新 tracepoint | 最推荐,功能全,踩坑少 |
| kernel-lt | 长期维护分支(4.4 系列) | 追求极致稳定、业务对内核版本敏感 | 能用,但 bpftrace 部分功能受限 |
我在测试机上用的是 kernel-ml,实际跑起来 bcc 和 bpftrace 都很顺畅。kernel-lt 我也试过一次,bcc 大部分工具没问题,但 bpftrace 有几个 map 相关的功能会报错,排查起来又要多花时间。所以如果目标很明确就是要跑 bpftrace,直接 kernel-ml 是个明智的选择。
2.2 包来源怎么选
CentOS 7 上装 bcc 有两条常见路径:EPEL 仓库和 iovisor 官方仓库。
EPEL 里确实有 bcc 的包,但版本比较老,Python 绑定很多时候还是 python2 的。现在很多辅助脚本、工具链都在向 python3 迁移,用老绑定会处处别扭。iovisor 官方仓库是 bcc 项目维护方在用的发布渠道,包更新更及时,python3-bcc 这类绑定也更完整。bpftrace 在 CentOS 7 上则基本只能靠 iovisor 仓库,EPEL 不一定有。
我的建议很直接:EPEL 和 iovisor 都配上。EPEL 不只是用来装 bcc 的一个选项,它还能顺手解决 elfutils-libelf-devel、bison、flex 这类底层依赖,后面不管是装包还是真走到源码编译那一步,都能少折腾一些。
3. 环境准备:内核升级与版本匹配
3.1 先确认当前内核状态
一切操作之前,先看一眼内核版本:
uname -r假设输出是3.10.0-1160.el7.x86_64,那就说明还停留在老内核。记下这个版本号,后面选内核、装 kernel-devel 都要用它做对照。
顺手再看一眼内核编译选项里 BPF 相关的开关,这一步能让你提前知道内核支持情况:
grep -E 'CONFIG_BPF|CONFIG_KPROBES|CONFIG_UPROBES|CONFIG_TRACEPOINTS' /boot/config-$(uname -r)如果这些配置项大部分是=y或=m,说明基础能力齐全。3.10 上这些项也存在,只是实际功能回移不完整,所以别被表面配置骗了,最终还是得升内核。
3.2 导入 ELRepo 并安装新内核
先导入 ELRepo 的 GPG 密钥和仓库配置:
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org rpm -Uvh https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm然后更新缓存,安装 kernel-ml:
yum --enablerepo=elrepo-kernel install kernel-ml这里要特别注意,elrepo-kernel 仓库默认是禁用的,必须带--enablerepo=elrepo-kernel,少写了 yum 就直接报找不到包。我见过不止一个同事在这步以为仓库没装对,来回折腾,其实就是参数没带够。
安装完成后,先确认内核包确实进来了:
rpm -qa | grep kernel-ml看到输出里有kernel-ml-5.x.x之类的包名,再重启:
reboot重启后如果发现还在旧内核,说明 grub 默认项没有指向新内核。这时手动进入开机菜单选一次新内核,等系统起来后执行:
grub2-set-default 0 grub2-mkconfig -o /boot/grub2/grub.cfg把第一项(通常是新内核)设为默认。之后再uname -r确认已经切到 5.x。这里有个安全习惯:旧内核先别删,保留在 grub 菜单里,万一新内核在你机器上有兼容性问题,还能随时回滚。
3.3 kernel-devel 和 kernel-headers 必须匹配
这一步是整个搭建过程里最容易翻车的地方,也是环境起不来的头号原因。
bcc 在运行时会现场编译 BPF 程序,它需要当前内核的开发环境和头文件;bpftrace 加载追踪程序时同样依赖这些。具体来说,工具会去找/lib/modules/$(uname -r)/build这个目录,而这个目录就是 kernel-devel 包提供的。版本对不上,编译阶段直接报错。
切换到新内核后,执行精确安装:
yum install kernel-devel-$(uname -r) kernel-headers-$(uname -r)注意这里的$(uname -r)会自动展开成当前内核版本号,比如5.18.19-1.el7.elrepo.x86_64。这种方式比手动输入版本号更保险,手动敲数字很容易因为手误装错版本。
完后检查一下符号链接:
ls -l /lib/modules/$(uname -r)/build如果能看到类似build -> /usr/src/kernels/5.18.19-1.el7.elrepo.x86_64的输出,说明 kernel-devel 装对了。如果提示目录不存在,那就是版本不匹配,回头重新装。
另外提一个进阶项:如果你希望 bcc 分析工具在输出内核栈时能显示完整的函数名而不是一堆十六进制地址,可以顺手装内核的 debuginfo 包:
yum --enablerepo=elrepo-kernel install kernel-ml-debuginfo这个包比较大,几十 MB 起步,不装不影响基本使用,但装了之后栈回溯的可用性会高一个档次。
3.4 基础依赖一次配齐
为了避免后续装包时缺这个缺那个,我习惯把常用依赖一次装到位:
yum install -y epel-release elfutils-libelf-devel bison flexepel-release 前面说过,主要给后续来源做铺垫;elfutils-libelf-devel 是很多 BPF 相关库的基础依赖;bison 和 flex 在走源码编译 bpftrace 时会用到,提前装上没坏处。
4. 安装 bcc 与 bpftrace
4.1 配置 iovisor 仓库
iovisor 仓库的配置方式很简单,直接写一个 repo 文件:
rpm --import https://repo.iovisor.org/GPG-KEY-Iovisor cat > /etc/yum.repos.d/iovisor.repo << 'EOF' [repo.iovisor.org] name=repo.iovisor.org baseurl=https://repo.iovisor.org/yum/centos/7/x86_64/ enabled=1 gpgcheck=0 EOF我把gpgcheck直接设为 0,是因为这个仓库的 key 管理有时候会给你找麻烦,测试环境和内网机器这样省心。如果是在严格的生产环境,还是建议把 key 导好、开启 gpgcheck,安全第一。
配好后重建一下缓存:
yum clean all && yum makecache这样能避免 yum 缓存了旧的仓库数据导致安装时报 404。
4.2 安装 bcc
执行安装:
yum install -y bcc bcc-tools python3-bcc装完之后,工具集位于/usr/share/bcc/tools目录下。这个目录里有几十个现成工具,比如 execsnoop、opensnoop、biolatency、tcplife 等等,后面我会挑几个演示。
这里有个小细节:/usr/share/bcc/tools下的工具,它们的解释器取决于你装的是 python2 的 bcc 绑定还是 python3 的绑定。装了 python3-bcc 之后,这些脚本会用 python3 跑。如果你只装了 python2-bcc,遇到某些依赖 python3 的脚本会直接报No module named bcc。所以我个人建议直接装 python3-bcc,省事。
4.3 安装 bpftrace
yum install -y bpftrace装完验证版本:
bpftrace -V能正常输出版本号,说明安装成功。如果 yum 提示找不到包,先检查刚才写的 iovisor.repo 里的 baseurl 路径是不是/centos/7/x86_64/,拼错一个斜杠都会直接 404。再不行就再执行一次yum clean all && yum makecache,让 yum 重新拉取仓库元数据。
iovisor 仓库里的 bpftrace 版本可能不是最新的,但对日常排查来说完全够用,没必要在这上面追新。
5. 快速上手:几个值得收藏的实操用例
5.1 bcc 工具:execsnoop 和 biolatency
先跑一个最简单的,监控系统里的新进程创建:
/usr/share/bcc/tools/execsnoop这个工具通过 tracepoint 捕获 execve 系统调用,屏幕上会实时滚动当前系统里新启动的进程。我第一次在测试机上跑它,就发现了一个每隔几秒就悄悄冒出来的计划任务脚本,这在平时用 top、ps 根本看不出来。这种"看见看不见的东西"的能力,就是 eBPF 工具跟传统监控手段最大的区别。
再来看磁盘 IO 延迟分析,用 biolatency:
/usr/share/bcc/tools/biolatency它会统计块设备上 IO 请求的延迟分布,直接输出直方图。如果你想判断系统里是不是有 IO 毛刺,这个工具比 iostat 直观得多——iostat 只能给你平均值,biolatency 能让你看到延迟的完整分布形状。
这些工具跑起来之后,Ctrl+C 退出,它会把已经采集到的统计结果打印出来,非常适合抓现场。
5.2 bpftrace 一行脚本
bpftrace 最爽的用法是直接丢一行脚本上去。统计每个进程发起的 execve 系统调用次数:
bpftrace -e 'tracepoint:syscalls:sys_enter_execve { @[comm] = count(); }'运行一段时间后 Ctrl+C,bpftrace 会自动把统计表打出来,按调用次数排序,一眼就能看出谁在频繁创建进程。
再看一个追踪函数调用时延的例子。比如你想看 vfs_read 这个文件读取路径上的耗时:
bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ { @usec[tid] = (nsecs - @start[tid]) / 1000; delete(@start[tid]); }'这段的意思是:进入 vfs_read 时记录当前线程的时间戳,返回时用当前时间减去起始时间得到耗时,存进 map,退出时清掉记录。/@start[tid]/这个部分是过滤条件,确保所有线程都能正确配对上。这是 bpftrace 里非常经典的一类写法,理解了这个模式,后面写什么延迟分析都顺手。
bpftrace 的语言上手成本比 bcc 低很多,不需要写完整 C 代码,这也是它适合快速排查的原因。
6. 常见问题与排查实录
6.1 bpf: Operation not permitted
跑任何 bcc 或 bpftrace 命令都需要 root 权限,因为加载 BPF 程序是内核特权操作。普通用户执行会直接看到bpf: Operation not permitted。这个最简单,sudo 或切 root 即可。
但如果确认是 root 还报这个,问题就复杂一些——大概率是运行环境层面的限制。比如某些容器环境里 seccomp 配置把 bpf 系统调用拦截了,或者在受限的 selinux 策略下,BPF 相关能力被压住了。这种情况就得先解决环境限制,再谈工具本身。
6.2 invalid argument 或 unknown kprobe
这类报错大概率出现在还在 3.10 内核上硬跑的场合。3.10 的 eBPF 支持是回移不完整的,很多 helper 函数、map 类型、kprobe 事件都缺,bcc 生成的 BPF 字节码里用到了内核不认的特性,内核直接拒收。我在 3.10 上实测过,execsnoop 这类简单工具偶尔能起来,但换个复杂点的工具基本都是秒挂。
解法就是回到第 3 节,把内核升上去。升级后如果还报unknown kprobe,那多半是追踪的内核函数名在新内核里变了,需要先用/boot/config-$(uname -r)或/proc/kallsyms确认函数是否存在。这个点在内核版本跨度大的时候尤其常见。
6.3 linux/bpf.h: No such file or directory
报这种头文件缺失的错误,十有八九是 kernel-devel 没装或者版本不对。bcc 编译 BPF 程序时要引用内核头文件,而内核头文件由 kernel-devel 提供。检查方法前面说过,看/lib/modules/$(uname -r)/build是否存在、是否指向正确的源码目录。
这里提醒一个容易踩的细节:如果你同时装了多个内核版本,yum 在解依赖时有可能会选到旧版本的 kernel-devel。一定要用kernel-devel-$(uname -r)这种精确到版本号的装法,装完再验证一下链接路径。
6.4 bpftrace 安装时 404 或找不到包
iovisor 仓库路径配错是最常见的原因。检查/etc/yum.repos.d/iovisor.repo里的 baseurl,CentOS 7 对应的路径必须是:
https://repo.iovisor.org/yum/centos/7/x86_64/注意是centos,这个大小写、单词都不能错。另外,yum 缓存太久也会导致找不到包,执行yum clean all && yum makecache重建缓存再试。
6.5 BTF 或 CO-RE 相关的报错
如果你用的是比较新的 bcc 工具或较新版本的 bpftrace,它们默认可能尝试 CO-RE(一次编译、到处运行)模式,这依赖内核开启 BTF(BPF Type Format)。ELRepo 的 kernel-ml 内核不一定默认开CONFIG_DEBUG_INFO_BTF,所以遇到相关报错时可以检查:
grep CONFIG_DEBUG_INFO_BTF /boot/config-$(uname -r)如果结果是# CONFIG_DEBUG_INFO_BTF is not set,那就说明内核没开 BTF。这时候的解决思路不是去重编内核(成本太高),而是让工具走传统模式,或者干脆用老一点但功能完整的工具。bcc 大多数经典工具并不强依赖 CO-RE,报错那一两个完全可以避开。
6.6 仓库装不上时的兜底方案:源码编译
最后说说最极端的情况——你所在的环境离线,或者必须指定版本,那只能走源码编译。先把话放前面:这个方案时间成本高、依赖链长,不优先推荐,但作为兜底要会。
bcc 源码编译的路径大致是:
yum install -y cmake gcc-c++ python3-devel git clone https://github.com/iovisor/bcc.git cd bcc && mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/usr .. make -j$(nproc) make installbpftrace 的源码编译更麻烦一点,依赖 bcc 头文件之外还要 llvm。CentOS 7 自带的 llvm 版本偏老,我建议先启用 toolset:
yum install -y llvm-toolset-7 devtoolset-8 scl enable llvm-toolset-7 bash git clone https://github.com/iovisor/bpftrace.git cd bpftrace && mkdir build && cd build cmake .. make -j$(nproc) make install我在这条路上踩过的最大坑是 cmake 找不到 bcc 的头文件路径,解决办法是在 cmake 时显式指定,比如-DBCC_SYMBOLS_PREFIX=/usr。不过说句实话,正常有网环境真的没必要走编译,仓库包又快又稳。
7. 最后想说的经验
整个流程走下来,我最大的感受是:CentOS 7 上配 bcc 和 bpftrace,核心矛盾就一个——内核版本。内核一旦升到 5.x,后面基本就是 yum 一把梭;内核不升,其他操作全是白费力气。
还有一个实际建议:不管你是运维还是开发,装好之后花半个小时把/usr/share/bcc/tools下的工具列表过一遍,记住每个工具大概干什么用的。这半小时的回报很高,因为真实生产出问题的时候,你根本不会有时间现查文档。比如进程突然打满 CPU,你第一时间要知道用profile和cpuwalk,而不是临时翻tools -h。这种"工具箱里有趁手家伙"的底气,是平时积累出来的。
另外,上生产之前,务必先在测试机把整套流程跑通,并且观察新内核在你业务负载下的稳定性。这类追踪工具平时看不到收益,但在最棘手的排查场景里,有没有它们就是半天和十分钟的区别。