ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

eBPF命令行工具实战:bpftool、bcc-tools与bpftrace排障指南

eBPF命令行工具实战:bpftool、bcc-tools与bpftrace排障指南 前几天帮一个朋友处理线上问题服务每到凌晨就出现一波明显的磁盘IO毛刺htop里看进程状态一切正常strace挂上去又担心影响生产。我干脆打开终端用 eBPF 命令行工具从内核视角查了一层几分钟就定位到是某个定时任务在反复打开同一个配置目录下的大量小文件。类似这种场景我遇到不是一次两次了。很多人一听到 eBPF就以为是内核大神才能碰的东西其实现在围绕它已经长出了一套非常成熟的命令行工具生态bpftool 负责管理内核里的 BPF 对象bcc-tools 提供大量开箱即用的运维脚本bpftrace 则让你像用 awk 一样写动态追踪脚本。这篇文章不打算讲枯燥的内核理论知识直接按我平时排障的习惯把这几件好东西逐个拆开给你讲清楚它们各自能干什么、怎么用、以及有哪些坑。1. 先在脑子里装一张工具链地图从探针点到三个命令行工具想用好这些命令行工具第一步不是背命令而是搞清楚数据是怎么从内核流到你屏幕上的。eBPF 本身不是单个命令而是一个运行在内核里的安全虚拟机。它允许你把一小段字节码加载进操作系统挂到某个事件点去执行事件触发时内核会采集数据并回传给用户态。这里面的关键概念有三个探针点、事件数据和加载器。1.1 事件驱动模型到底在驱动什么传统排查工具大多靠轮询或者被抓包比如 top 每隔几秒刷一次数据strace 则通过 ptrace 系统调用接管目标进程。eBPF 走的是完全不同的路线事件驱动。你定义了 kprobe 或者 tracepoint 这类探针点当内核执行到对应位置时与探针绑定的 BPF 程序会同步被触发采集当时的参数、进程号、函数调用栈等信息。它不轮询也不暂停进程所以在大部分场景下开销远小于 strace这也是它适合在生产环境做排查的根本原因。我得强调一个容易误解的点eBPF 命令行工具并不是 eBPF 本身它们只是前端。真正干活的是内核里的 BPF 虚拟机、BPF 辅助函数和 map 数据结构。命令行工具负责把人类可读的请求比如统计每秒新创建的进程编译成字节码加载进内核再把内核送回来的数据翻译成表格或直方图。1.2 四件套的分工bpftool、bcc、bpftrace、libbpf这四样东西常被混为一谈实际职责差异很大我习惯用下面这张对照表来理解它们。工具定位典型命令适合谁bpftoolBPF 对象管理器和内核诊断器bpftool prog show、bpftool map dump想看内核里到底加载了哪些 BPF 程序的人bcc-tools多功能的运维脚本全家桶execsnoop、opensnoop、biolatency不想写代码、想直接出结果的人bpftrace一门专为动态追踪设计的解释型脚本语言bpftrace -e kprobe:vfs_read { [comm] count(); }习惯写一行式 awk、需要自定义探针的人libbpfC 库用于构建原生 eBPF 应用配合 clang 编译.bpf.o文件做平台开发、二次集成的人排障现场用前三者的频率最高。libbpf 更多属于开发者的范畴本文只做简单介绍重点还是放在命令行工具上面。我见过很多同事一上来就用 bpftrace 写复杂脚本结果卡在语法上调半天。其实很多时候bcc 里现成的工具已经覆盖了 80% 的场景而 bpftool 则可以帮你验证工具跑的到底是不是你想的那样。真正高效的顺序是先看看有没有现成工具再用 bpftrace 定制最后才考虑自己写 libbpf 程序。2. 环境检查清单跑第一个命令之前先确认四件事我见过太多人卡在 permission denied 或者 failed to create kprobe 这类报错上浪费大量时间。其实只要在上机前花两分钟过一遍下面的检查大部分问题都能绕开。2.1 内核版本与 BTF 符号表绝大多数现代 eBPF 工具对内核版本是有要求的。我的经验是内核低于 4.9 的基本可以放弃大部分 bcc 工具低于 5.4 的建议只用老牌 bcc 工具避免碰那些依赖 CO-RECompile Once - Run Everywhere的 libbpf-tools。想看内核是否支持关键特性两条命令就够了uname -r cat /sys/kernel/btf/vmlinux | wc -c第二条命令的输出如果是一堆字节数通常几十 MB说明内核开启了 BTF 信息新版 bpftrace 和 bcc 里的 libbpf-tools 可以直接跨内核版本跑。如果文件不存在说明内核编译时没有开CONFIG_DEBUG_INFO_BTF这时要么换内核要么降低工具版本使用传统方式加载探针。BTF 缺失是新手最容易遇到的隐性坑因为 bcc 老工具不依赖它bpftrace 还能用但部分 libbpf 工具就会莫名其妙地报 failed to load program。2.2 权限、挂载点和用户态依赖eBPF 程序属于高权限操作非特权用户默认加载不了 BPF 程序。内核 5.8 之后引入了专门的CAP_BPF和CAP_PERFMONcapability很多发行版默认允许特权用户操作。我建议排查问题时统一用sudo否则会看到大量含糊其辞的权限报错。另外如果你是在容器里跑 eBPF 工具情况会更麻烦一些容器默认缺内核 capability常见的做法是起容器时带上--privileged并挂载宿主机的/sys/fs/bpf目录否则你看到的 BPF 文件系统是空的工具无法正常工作。BPF 文件系统挂载点也是检查项之一执行下面命令确认sudo mount -t bpf bpf /sys/fs/bpf ls /sys/fs/bpf还有一类依赖容易被忽略bpftrace 和 bcc 依赖 LLVM 来做即时编译如果你用的发行版 LLVM 版本过旧部分新语法会编译失败。检查方式很简单llvm-config --version bpftrace --info | grep -i version我的个人建议是如果你准备系统性地把 eBPF 命令行工具当排障武器用优先选内核 5.15 以上的较新发行版能省掉大量环境兼容性的烦恼。3. bpftool 实战从对象管理到网络路径诊断bpftool 是内核自带 BPF 子系统最直接的管理入口。它不像 bcc 那样面向业务场景而是面向 BPF 对象本身。很多时候你在系统里跑到一半想确认有几个探针在干活、有没有残留程序、map 里到底存了什么数据bpftool 是唯一的答案来源。3.1 用 prog show 看清系统里跑着什么 BPF 程序执行sudo bpftool prog show你会看到类似这样的输出46: kprobe name do_sys_open tag b6ec59bdf146afe8 gpl loaded_at 2025-06-10T10:23:450800 uid 0 xlated 112B jited 89B memlock 4096B btf_id 221 pids execsnoop(5896)这里最值得关注的是type程序类型、tag程序指纹和pids当前被哪个进程使用。pids字段很有用它能直接告诉你这个 eBPF 程序是被哪个用户态进程拉起来的。如果你发现某个 BPF 程序没有对应的存活进程说明拉它的程序可能已经崩溃留下了悬挂的探针。此时可以用sudo bpftool prog detach或者直接重启相关服务来清理。另一个高频用法是配合--json输出格式化成结构化数据后方便脚本解析sudo bpftool prog show --json | jq .[] | {id, name, type, tag}3.2 用 net 和 map 打通网络路径与数据流bpftool 的net子命令查看的是 XDP、tc 这类网络挂钩点。有一次我在排查一个网络延迟问题一上来就执行了sudo bpftool net show发现某个网络接口的 tc ingress 上挂了一个老版本的 BPF 程序而这个程序来自一个早已废弃的服务。它在每个数据包上都做了一次额外匹配延迟自然高。这种问题用传统 netstat、ss 完全看不出来因为数据已经进了内核协议栈只是被 BPF 程序截流了。bpftool map系列命令用来操作 BPF map。BPF 程序通过 map 在用户态和内核态之间交换数据很多时候 map 里存的就是程序的关键状态比如流量统计、过滤规则。查看 map 列表sudo bpftool map show sudo bpftool map dump id 7map 的 dump 输出往往是哈希值或二进制格式直接看可能不太友好。一个实操技巧是先通过bpftool map lookup id 7 key 0x...精确查某个 key再配合bpftool map update修改数据。这里要特别提醒不要在非测试环境随便 update map 数据你改的可能就是内核里正在用的过滤规则一次误操作可能直接中断线上流量。3.3 用 btf 和 feature probe 快速给内核做体检遇到 这个工具为什么跑不起来 的问题时我最先执行的其实是bpftool feature probe它会扫描当前内核支持的 BPF 特性包括辅助函数、程序类型、map 类型是否开启。输出示例$ sudo bpftool feature probe Scanning system configuration... - JIT compiler is enabled - JIT hardening is enabled - JIT kallsyms are enabled - kallsyms export is enabled for kernel ...如果你发现工具依赖的某个辅助函数没有出现在列表中问题就定位到了不是命令写错了是内核编译选项没开。bpftool btf dump则用于导出内核的 BTF 格式类型信息开发阶段排查类型问题非常有用sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c | head -n 504. bcc-tools开箱即用的运维工具箱bccBPF Compiler Collection是 eBPF 工具链里历史最久、覆盖面最广的一套。它的特点是你不需要自己写 BPF 程序只需要调用现成的 Python 脚本背后会自动完成字节码生成、加载、数据收集和可视化。所以它的定位很明确拿来就能用。4.1 安装与工具目录不同的发行版包名不一样Debian/Ubuntu 上是bpfcc-toolsRHEL 系是bcc-tools。装完后工具通常位于/usr/share/bcc/tools命令名末尾可能会带bpfcc后缀比如opensnoop-bpfcc。如果找不到先用which确认一下再进脚本避免手滑执行了系统里其他同名命令。装完后我通常是这么挑工具的ls /usr/share/bcc/tools | sort | less这个目录列表本身就是一本按场景组织的排障手册。文件系统相关的有opensnoop、filetop、syncsnoop网络相关的有tcplife、tcpconnect、tcpretrans调度性能相关的有runqlat、offcputime进程相关的有execsnoop、exitsnoop。4.2 六个高频工具及真实场景举几个我实际用过的例子。execsnoop跟踪系统中所有新进程的创建输出进程名、PID、父进程和参数。排查为什么突然冒出这么多进程时它比其他工具都直观sudo execsnoopopensnoop跟踪 open 系统调用能看到哪个进程访问了哪个文件。前面提到的凌晨磁盘 IO 问题我就是靠它确认了某个守护进程疯狂打开配置目录里的小文件sudo opensnoop -n nginx -d 30biolatency输出块设备 IO 延迟的直方图在定位数据库磁盘慢查询时很直观输出直接就是分桶分布的 ASCII 图。tcplife则是网络排查的利器能够显示 TCP 连接从建立到关闭的完整生命周期包括连接时长、收发字节数。某次业务反馈连接偶尔特别慢我用它筛出了大量短连接再去应用层定位到连接池配置问题sudo tcplife -L --csvfunccount可以统计某个内核函数在一段时间内被调用的次数最适合回答这个函数是不是太热。比如sudo funccount vfs_readtrace是 bcc 里最接近手写探针的工具支持一行式 Python 语法。比如查看所有对/tmp目录的 open 操作并打印调用者sudo trace do_sys_openat2 %s arg2 comm nginx这里面我踩过一个小坑老版本的open探针名和新的 openat2 探针名不一样如果脚本报找不到符号先用perf probe -l或者直接查一下内核符号表再换探针名称。4.3 bcc 与 libbpf-tools 的迭代问题新版本 bcc 里有一部分工具已经被重写为 libbpf-tools它们依赖 BTF启动更快输出也更稳定。如果某些旧工具在新内核上跑不稳不妨尝试同名的*-bpfcc替代路径。但如果你是嵌入式或者云上老内核环境那套 libbpf-tools 可能因为 BTB 缺失直接起不来此时兜底方案还是老 bcc 工具。5. bpftrace一行脚本完成动态追踪如果说 bcc-tools 是点外卖bpftrace 就是拿着菜谱自己炒。它是一门专门为动态追踪设计的语言语法风格和 awk 很像但追踪对象不是文本行而是内核事件。它特别适合那种现成工具没有覆盖到、但你就想知道一件事的场景。5.1 一句话理解 bpftrace 语法bpftrace 程序的基本单元是探针 动作类似 awk 的匹配 动作。比如下面这行代码统计各进程执行的open系统调用次数sudo bpftrace -e tracepoint:syscalls:sys_enter_openat { [comm] count(); }拆开看tracepoint:syscalls:sys_enter_openat是探针点{} 里面是动作开头的变量就是 BPF map 的封装count()是内置计数函数。comm是内置变量表示当前进程名。bpftrace 还支持条件过滤。用/加条件过滤可以只关注特定进程sudo bpftrace -e tracepoint:syscalls:sys_enter_openat / comm nginx / { printf(%s\n, str(args.filename)); }这里args.filename是 tracepoint 的参数str()负责把内核指针转换成字符串。带条件过滤的写法在处理大规模高频事件时特别有用能把采集噪声降到最低。5.2 三个高频模板计数、直方图、采样模板一延迟直方图。追踪某个内核函数的耗时分布比单看平均值可靠得多sudo bpftrace -e kprobe:vfs_read { start[pid] nsecs; } kretprobe:vfs_read / start[pid] / { us hist((nsecs - start[pid]) / 1000); delete(start[pid]); }这段代码的思路是入口探针记录每个进程的开始时间出口探针计算时间差并放入hist()直方图然后删除临时记录。hist()输出的是 2 为底的分桶分布能立刻看出延迟是否呈现双峰形态。双峰往往是缓存命中与未命中的混合场景这是平均数完全掩盖的信息。模板二CPU 调用栈采样。理想情况下你应该看到运行热点出现在业务代码里但有时候你看到的是大量内核路径这本身就是异常信号sudo bpftrace -e profile:hz:99 { [kstack] count(); }这里profile探针是一个定时探针以 99Hz 的频率对所有 CPU 采样每次记录当前内核调用栈。跑 30 秒后 CtrlCbpftrace 会打印调用栈出现频次排序。模板三进程退出路径观测。想知道进程为什么退出可以追踪sys_enter_exit或者sys_enter_exit_groupsudo bpftrace -e tracepoint:sched:sched_process_exit { printf(%s %d\n, comm, pid); }5.3 调试参数、输出控制与常见报错bpftrace 有成体系的-d调试参数排查脚本问题很有帮助sudo bpftrace -d -e ... sudo bpftrace -dd -e ...-d显示生成的 LLVM IR-dd则进一步显示最终生成的 BPF 字节码。我遇到探针明明写对了但没输出的情况时会先看字节码是否生成了判断是编译阶段挂掉还是加载阶段挂掉。另一个很实用的参数是-c指定跟随某个命令运行只在那个进程生命周期内追踪sudo bpftrace -e tracepoint:syscalls:sys_enter_openat { printf(%s %s\n, comm, str(args.filename)); } -c nginx -t常见报错有几个一是kprobe not found通常是内核函数名写错或者被__nokprobe屏蔽了可以先用bpftrace -l kprobe:*vfs_read*列一下二是wrong number of args这是探针参数个数不匹配需要对照 tracepoint 定义查三是段错误多半是 bpftrace 自身版本 bug升级到最新版大概率解决。6. 实战复盘一次 CPU 毛刺的定位路径工具讲完了最怕的还是不知道怎么串起来用。下面用一个我实际处理过的 CPU 毛刺问题演示一下工具组合拳的打法。这个案例不涉及具体业务细节但排查路径很典型。6.1 第一轮profile 采样锁定调用栈业务反馈某组容器每天下午会出现 CPU 毛刺持续几分钟后恢复。我先用htop确认毛刺时段确实有 CPU 占满的进程然后又确认了这不是 ksoftirqd 或者内核线程的问题。接下来我没有直接抓进程线程栈而是用 bpftrace 对全系统做调用栈采样sudo bpftrace -e profile:hz:99 { [comm, kstack] count(); } stack_20250610.txt大约采集了 120 秒等毛刺结束把输出按计数排序。结果没有立刻指向业务代码而是出现了大量do_sys_openat2和__x64_sys_openat的内核路径且频次最高的进程是日志采集器。换句话说这次毛刺不是计算密集而是一个进程在短时间内发起了海量文件打开操作。6.2 第二轮funccount 确认热点函数调用栈毕竟是采样存在一定随机性。为了确认我直接用 funccount 对vfs_open、do_sys_openat2等函数做精确计数对比毛刺时段和正常时段的数量级差异。结果显示毛刺时段do_sys_openat2的调用次数是正常时段的 20 倍以上基本锁定问题出在文件打开路径上。6.3 第三轮opensnoop 与 tcplife 交叉验证再回到事件层用opensnoop跟踪到底是谁在打开什么文件。输出显示日志采集器在同时轮询多个目录每个目录下有大量累积未清理的小文件导致每次扫描都要发起成百上千次 open。与此同时我用tcplife查看了对应时段的外发连接发现日志上传队列出现拥塞进一步加剧了文件读写的频率。至此根因清晰文件清理策略失效导致存量文件过多采集器扫描开销被放大。解决方案是调整清理策略、手动压缩归档旧日志问题就不再出现了。这套流程的关键在于先用 profile 做粗筛再用 funccount 做量化最后用 bcc 工具回到业务语义层确认。单靠某一个工具很难一步到位。7. 管理、清理与边界感把 eBPF 工具用稳工具再好如果跑起来没纪律也会给自己和团队挖坑。最后这部分聊聊生产环境中必须养成的几个习惯。7.1 生命周期与残留处理BPF 程序的生命周期通常和拉起的用户态进程绑定bpftrace 退出时对应探针和 map 会被自动销毁。但如果进程是异常 kill 掉的或者你把 BPF 程序 pin 到了/sys/fs/bpf目录程序就可能在内核里残留。检查残留的看家本领还是 bpftoolsudo bpftool prog show | grep -E pids|name如果发现某个程序没有对应 pids、且不是系统服务挂载的果断 detach 或清理 pin 文件sudo rm -f /sys/fs/bpf/xxx这里要注意先看bpftool prog show确认 pin 路径不要乱删系统级 BPF 对象。7.2 性能开销与生产使用纪律eBPF 探针本身开销很小但不代表可以滥用。一个高频探针挂在繁忙路径上比如对每个网络包计数在万兆流量下会让吞吐量明显下降。我的经验是两条能用采样不用全量计数能加过滤条件就加过滤条件。比如 bpftrace 的 profile 采样默认是 99Hz全量事件则必须配合/条件过滤只在特定进程或者特定参数命中时动作。长时间挂探针还会大量占用系统内存因为 BPF map 的内存是常驻的跑完记得退出工具并确认是否有残留。另外如果生产环境开了很多探针建议设置一个观测脚本定期记录bpftool prog show和bpftool map show的摘要避免哪天排查问题时发现环境里不知道飘着多少历史遗留探针。7.3 权限边界与安全提醒eBPF 是有能力修改内核行为的不只是观测。虽然普通 CLI 工具的绝大多数命令都是只读观测但 map 的 update、prog 的 attach 操作都会直接影响内核运行状态。所以在生产环境要遵循两条原则权限最小化别让普通开发账号拥有 CAP_BPF操作可回滚任何涉及 detach、update 的命令先确认当前对象归属和业务影响。内核版本较新的系统里非特权用户默认被禁止加载 BPF 程序这是安全机制的一部分不建议为了图方便去调整kernel.unprivileged_bpf_disabled。我自己的习惯是平时排障优先用 bpftrace 的只读统计类和 bcc 的观测脚本动 map 数据的命令一律走变更评审流程。工具本身只是放大器能不能安全地用取决于使用者的边界感。回到最初的场景eBPF 命令行工具给我的最大感受是它把内核从一个糊着的黑盒子变成了一块可以随时掀开盖板看的仪表盘。你不需要成为内核源码专家只要理解探针、事件、map 这套基本模型再把 bpftool、bcc-tools、bpftrace 按职责排好队很多困扰很久的疑难问题都能快速找到下手点。如果有机会我建议你也从bpftrace -l开始一个探针一个探针地玩那感觉比看任何文档都直观。
返回列表