本节摘要:bcc 在目标机即时编译,迭代快,部署重。libbpf 把编译前移到构建机,配合 BTF 做一次编译多处运行,是常驻程序的默认路。aya 用 Rust 表达同类模型,换来类型与打包习惯,不换内核契约。选框架就是选编译发生在哪、失败出现在谁面前。
阅读完本节,你应当能够:
内核契约(验证器、Map、Helper)与用哪门语言写无关。框架差在:谁在什么时候把 C 变成字节码,出了错谁看得见。
bcc 的典型形态是 Python 脚本里嵌着 C,目标机器上调用编译器,当场生成并加载。对排障极友好:改一行,跑一下,看 Map。对交付不友好:每台机器要编译器、内核头文件、合适的 LLVM。内核一升级,现场编译可能失败,失败发生在已经出故障的节点上。
把它当成急诊室的便携超声:今天就要看,明天换正式监护仪。我用 bcc 验证“这个跟踪点有没有我要的字段”,验证完就停。不把 bcc 脚本复制进生产镜像当长期守护。
libbpf 路线是:构建机用 clang 生成带 BTF 的对象,运行时库做重定位和加载。目标机不需要编译器,只需要内核 BTF(多数新发行版有)和加载器。失败尽量出现在 CI:验证器在测试内核上先跑一遍。
这才像把监护仪的固件在厂里烧好,病房只开机。第 7 章的 CO-RE 是这条路能跨内核的关键。没有 BTF 的老内核,这条路会痛,那时要么升级,要么退回针对版本构建。
aya 让你用 Rust 写 eBPF 侧和用户态侧。买到的是:更严的类型、更统一的工程布局、不会在脚本里拼接 C 字符串。买不到的是:验证器变宽松、Helper 变多、内核 bug 消失。字节码照样要过同一套闸。
团队已有 Rust 基础设施、希望两边语言一致时,aya 合理。团队内核经验在 C、要跟大量现成示例对齐时,libbpf 摩擦更小。不要因为“Rust 更安全”就认为可以少测——eBPF 的安全边界在验证器,不在源语言。
| 框架 | 编译发生在 | 目标机依赖 | 迭代速度 | 适合 |
|---|---|---|---|---|
| bcc | 目标机 | 编译器、内核头 | 很快 | 探索、一次性排障 |
| libbpf | 构建机 | BTF、加载器 | 中 | 常驻守护、产品化 |
| aya | 构建机 | BTF、Rust 运行时侧 | 中 | Rust 团队的产品化 |
| 纯 syscall 自己封装 | 你自己决定 | 你自己承担 | 慢 | 几乎不建议从零造 |
⚠️ 常见坑:在生产镜像里安装完整编译链“以便随时改探针”。这扩大攻击面,也让节点变成不可复现的工作室。改探针走发布,不走 SSH 编译。
💡 关键直觉:探索和交付用两套工具是成熟,不是浪费。急诊超声和病房监护仪本来就不是同一台机器。
当 bcc 脚本证明假设成立,固化前问:
缺任何一项,都还不是产品。第 8 章会把这些变成灰度步骤。
探索:字段在不在、频率高不高 固化:对象文件进制品库 守护:加载器、指标、卸载接口 平台:挂钩点注册,避免互踩

能跑,不该作为架构。镜像膨胀、构建不可复现、安全扫描痛苦。例外是应急工具箱,与常驻守护分开,用完卸载。
可以,用户态语言不限。只要最终调用的是同一套加载协议,内核不在乎。不要每种语言再发明一遍重定位。
现实很少从零开始。往往已经有人用 bcc 救过火,有人用 libbpf 写了守护,还有人想推 Rust。允许三套并存可以,但要规定流向:bcc 的产出是结论和规格,不是长期进程;新的常驻只准走 libbpf 或 aya 其中一条;选定之后,另一条只用于实验。双向都当一等公民,CI 矩阵会加倍,挂钩清单会乱。
选择 libbpf 还是 aya,用“谁维护加载器”决定,不用语言偏好决定。加载器要处理 pin、能力、指标、回滚。哪边能把这些写成你们已经会运维的服务,就选哪边。语言安全帮不了你忘记 unpin。若两边能力接近,选能招到人、能看懂内核示例的那条。目前大量内核示例仍是 C 风格的 libbpf,培训成本是真实成本。
依赖冻结要写进仓库。clang 版本、libbpf 版本、aya 版本,都会改变字节码形状。突然升级编译器导致验证失败,看起来像内核变了。把工具链版本和内核矩阵一起锁。升级工具链等于一次发布,要跑完整加载测试。
许可证字符串、段名、程序名称这些“像仪式”的字段,其实是加载契约。框架封装了它们,不等于你可以乱改。多人协作时把段名约定写下来,避免两个程序抢同一个段名导致加载器挂错对象。这种低级错误在框架层不会消失。
应急工具箱可以继续放 bcc 和 bpftrace,但镜像与常驻镜像分开,权限分开,网络策略分开。能在故障夜用工具箱的人,不一定有权改常驻制品。把两种身份拆开,现场编译才不会慢慢渗进生产。
为了方便改探针,常驻镜像带了完整编译链。安全扫描每次报警,镜像体积让分发变慢,节点上还能编译出和 CI 不同的字节码。某次现场热修把未测试程序挂上,验证过了,逻辑写错,指标全歪。后来常驻镜像禁止编译器,热修走紧急发布轨,仍然要签名和短灰度。快来自轨道,不来自把厂搬进病房。
bcc 脚本进了定时任务,内核升级后现场编译失败,定时任务打满日志。探索工具过了寿命就会变成假服务。寿命到期要么固化到 libbpf,要么删除。删除也是一种交付。
工具链升级没锁版本,clang 小版本变化导致验证失败,看起来像内核背锅。锁版本之后,升级工具链等于一次正式发布。发布就要跑矩阵。矩阵不是只给内核用的,给编译器同样用。
bcc 进定时任务,内核升级后现场编译失败,日志打满。探索工具过期会变成假服务。假服务比没有更糟,因为它占用挂钩和注意力。删除也是交付。常驻镜像禁止编译器,热修走紧急发布轨,仍要签名和短灰度。快来自轨道。工具链版本与内核矩阵一起锁,升级 clang 等于发布。段名约定写下来,防止加载器挂错对象。应急工具箱与常驻镜像分开、权限分开。能用工具箱的人未必能改常驻制品。身份拆开,现场编译才不会渗进生产。渗进生产的编译器,会在某一夜编译出和 CI 不同的字节码。不同的字节码可能仍通过验证,然后逻辑错。验证不管逻辑。逻辑错要靠发布轨上的评审,评审在现场编译里不存在。不存在的评审,事故会补上。
框架选择会被语言偏好绑架。绑架的结果是两套常驻加载器,两套 pin 语义,两套回滚剧本。故障夜要问这套是哪边的。问不出来就两套都不敢动。不敢动的程序是最糟的常驻。所以先选能把加载器运维起来的那条路,再谈喜欢。喜欢可以留在实验。实验有终点。终点到了,要么并入选定的路,要么删除。删除需要把删除写进规范,否则没人敢删别人喜欢的语言。规范比喜欢硬。硬一次,后面的矩阵只跑一套。一套矩阵才跑得完。跑不完的矩阵,等于没有矩阵。
\n\n## 课堂补充\n\n寿命定框架,偏好不定框架。镜像不带编译器。Rust不让验证器放假。脚本过期要删。clang升级等于发布。加载器四件套决定胜负。段名要约定。工具箱和常驻拆身份。移交缺一项就还不是产品。双轨永久并存是欠债。重定位不要每种语言发明一遍。现场编译会绕过评审。\n\n\n\n## 生产验收条\n\n1. 围绕「开发框架bcc libbpf aya怎么选」,生产验收只认能关掉、能计数、能对账,不认口头保证。\n2. 围绕「开发框架bcc libbpf aya怎么选」,把所有者、版本、卸载方式写成清单三件套,缺一视为幽灵。\n3. 围绕「开发框架bcc libbpf aya怎么选」,对照实验必须能回答开关前后业务指标动了没有。\n4. 围绕「开发框架bcc libbpf aya怎么选」,失败要分类到验证、额度、挂载、未触发、丢失,禁止只丢一句笼统错误。\n5. 围绕「开发框架bcc libbpf aya怎么选」,热路径默认克制,阈值之后才出栈,出栈之前先证明钩子活着。\n6. 围绕「开发框架bcc libbpf aya怎么选」,和平台已有挂钩点冲突时先登记再加载,禁止手工抢挂。\n7. 围绕「开发框架bcc libbpf aya怎么选」,内核版本矩阵没跑绿就不能把功能写成必成功路径。\n8. 围绕「开发框架bcc libbpf aya怎么选」,回滚必须碰到内核对象,心跳停止才算撤回成功。\n\n## 本节速览
下一节看不必自己写程序时的生态:bpftrace 提问,Cilium 当数据平面,观测栈如何接 Map 里的数。