Linux eBPF in Action
背景
作为 Linux 开发者经常需要知道系统内部各个子系统的使用情况,可以使用 top、iostat、vmstat 等对各个子系统进行监控的工具,也有 sar、perf 等进行更细粒度采集分析的工具。但往往在解决系统实际问题的过程中,需要将多个工具组合分析,缺少针对于特定业务场景、具体细节的精准分析工具。eBPF 的出现,使根据业务灵活、安全地定制监控工具成为可能,但同时也带来了代码碎片化,重复的问题。
eBPF 在 2014 年引入内核后,逐渐演进成为一个通用执行引擎,可基于此开发性能分析工具,网络过滤器等诸多场景。并扩展到用户空间,使得应用不再局限于网络栈。eBPF 程序架构强调安全性和稳定性,看上去像内核模块,但却并不需要重新编译内核。而且在 eBPF 程序运行中,不会因为程序问题造成系统的崩溃。因此,eBPF 在网络、观测、安全和系统调优等领域发挥了重要作用。相比于现有工具的组合,针对业务特点的 eBPF 监控工具能够更有效的反应系统使用状况和暴露潜在问题。
eBPF 是什么
eBPF (Extended Berkeley Packet Filter) 扩展的伯克利数据包过滤器
BCC (BPF Compiler Collection)
eBPF
是一种数据包过滤技术,是从 BPF
(Berkeley Packet Filter) 技术扩展而来的。顾名思义 BPF
来源于伯克利大学,最早应用于网络数据包过滤器,它比当时最先进的抓包技术快 20 倍,其主要得利于它的两个设计:
- 内核态引入一个新的虚拟机,所有指令都在内核虚拟机中运行。
- 用户态使用
BPF
字节码来定义过滤表达式,然后传递给内核,由内核虚拟机解释执行。
BPF
提供了一种在内核事件和用户程序事件发生时安全注入代码的机制,这就让非内核开发人员也可以对内核进行控制。
eBPF
则是对对BPF
的一些扩展,将原本单一的数据包过滤事件逐步扩展到了内核态函数、用户态函数、跟踪点、性能事件(perf_events
)以及安全控制等。eBPF
不仅扩展了寄存器的数量,引入了全新的BPF
映射存储。eBPF
程序架构强调安全性和稳定性,看上去更像内核模块,但与内核模块不同,eBPF
程序不需要重新编译内核,并且可以确保eBPF
程序运行完成,而不会造成系统的崩溃。eBPF
程序并不像常规的线程那样,启动后就一直运行在那里,它需要事件触发后才会执行。这些事件包括系统调用、内核跟踪点、内核函数和用户态函数的调用退出、网络事件,等等。借助于强大的内核态插桩(kprobe
)和用户态插桩(uprobe
),eBPF
程序几乎可以在内核和应用的任意位置进行插桩。
eBPF 架构
eBPF
分为用户空间程序和内核程序两部分:
- 用户空间程序负责加载
BPF
字节码至内核,如需要也会负责读取内核回传的统计信息或者事件详情。 - 内核中的
BPF
字节码负责在内核中执行特定事件,如需要也会将执行的结果通过maps
或者perf-event
事件发送至用户空间。 - 其中用户空间程序与内核
BPF
字节码程序可以使用map
结构实现双向通信,这为内核中运行的BPF
字节码程序提供了更加灵活的控制
通常借助 LLVM
把编写的 eBPF
程序转换为 BPF
字节码,然后再通过 bpf
系统调用提交给内核执行。内核在接受 BPF
字节码之前,会首先通过验证器对字节码进行校验(不能包含无限循环、不能导致内核崩溃、必须在有限时间内完成),只有校验通过的 BPF
字节码才会提交到即时编译器执行。BPF
程序可以利用 BPF
映射(map
)进行存储,而用户程序通常也需要通过 BPF
映射同运行在内核中的 BPF
程序进行交互。如下图所示,在性能观测中,BPF
程序收集内核运行状态存储在映射中,用户程序再从映射中读出这些状态。
eBPF 优势
- 速度和性能。
eBPF
可以将数据包处理这项工作从内核空间转移到用户空间。同时,eBPF
还支持即时(JIT
) 编译器。在字节码被(JIT
)编译完成后,会直接调用eBPF
,而不是对每个方法的字节码进行新的解释。 - 低侵入性。 当作为调试器时,
eBPF
不需要停止程序来观察其状态。 - 安全。 程序被有效地沙箱化了,这意味着内核源代码仍然受到保护并保持不变。
eBPF
程序的验证步骤确保资源不会被运行无限循环的程序阻塞。 - 方便。 创建
hook
内核函数的代码比构建和维护内核模块的工作要少。 - 统一追踪。
eBPF
提供了一个单一、强大且易于访问的流程跟踪框架,这增加了可见性和安全性。 - 可编程性。 使用
eBPF
有助于在不添加额外层的情况下增加环境的功能丰富性。由于代码直接在内核中运行,因此可以在eBPF
事件之间存储数据,而不是像其他跟踪器那样转储数据。 - 表现力。
eBPF
具有丰富的表现力,能够执行通常只能在高级语言中才能找到的功能。
eBPF 相关工具
BCC
BCC
是 BPF 的编译工具集合,前端提供 Python/Lua API,本身通过 C/C++ 语言实现,集成 LLVM/Clang 对 BPF 程序进行重写、编译和加载等功能,提供一些更人性化的函数给用户使用。BCC通常用在开发复杂的 eBPF 程序中,其内置的各种小工具也是目前应用最为广泛的 eBPF 小程序。
bpftrace
bpftrace
在 eBPF
和 BCC
之上构建了一个简化的跟踪语言,通过简单的几行脚本,就可以实现复杂的跟踪功能。因此,在编写简单的 eBPF 程序,特别是编写的 eBPF 程序用于临时的调试和排错时,可以考虑直接使用 bpftrace
,而不需要用 C 或 Python 去开发一个复杂的程序。
libbpf
libbpf
是从内核中抽离出来的标准库,用它开发的 eBPF
程序可以直接分发执行,这样就不需要每台机器都安装 LLVM 和内核头文件了。不过,它要求内核开启 BTF 特性,需要非常新的发行版才会默认开启(如 RHEL 8.2+ 和 Ubuntu 20.10+ 等)。