eBPF:内核的可编程边界
eBPF 被大多数人认识的方式是通过 Cilium、Pixie、BCC 这类工具:一种无侵入的 tracing 技术,能在不修改代码的情况下观测进程行为。这个认知框架是准确的,但不完整。
把 eBPF 理解为”更好的 strace”,会错过它更根本的含义:Linux 内核从一个固定功能的黑盒,变成了一个可在运行时扩展的可编程平台。
架构:为什么 eBPF 是安全的
Linux 内核允许加载内核模块(kernel module),但内核模块可以直接访问任意内存,一个写坏的模块能把整个系统搞崩溃,生产环境没人敢乱用。
eBPF 的核心创新是在可编程性和安全性之间找到了一个平衡点。
用户编写 eBPF 程序(通常是 C 的受限子集,通过 Clang 编译),内核里有一个 Verifier 会在程序加载时做静态分析:所有内存访问是否有边界检查?程序是否一定会终止(无循环或循环有界)?是否会访问未初始化的栈变量?
Verifier 通过后,程序通过 JIT 编译转换为本地机器码直接执行,性能接近原生内核代码。
这让 eBPF 程序可以安全地挂载到内核的数百个 hook 点(kprobe、uprobe、tracepoint、XDP、cgroup hook 等),在对应事件触发时运行。
三个不同的用途
eBPF 的应用场景沿着内核的三个核心子系统展开:
观测(Observability)
这是 eBPF 最被熟知的用途。挂载到系统调用、内核函数入口、网络事件,可以实现几乎零开销的性能分析和行为追踪。
关键词是”几乎零开销”。传统 strace 因为每次系统调用都要把进程暂停并陷入内核两次,开销可以高达 100x。eBPF 在内核态直接处理,数据通过 lock-free 的 ring buffer 传递给用户态,生产环境可以常态开启,不用在”要不要 trace”和”性能损失”之间做取舍。
网络(Networking)
XDP(eXpress Data Path)把处理逻辑挂在网卡驱动之后、内核网络栈之前。数据包在进入 Linux 网络协议栈之前,eBPF 程序就能决定:转发、丢弃、修改、还是继续走正常路径。
这个位置让 XDP 在 DDoS 防御、负载均衡、高性能数据包处理上有独特优势——内核协议栈的每一层(skb 分配、路由查找、iptables 规则匹配)都被绕过了。Cloudflare 的 DDoS 防御层有相当一部分构建在 XDP 上;Meta 的 Katran 负载均衡器同样如此。
安全(Security)
Seccomp-BPF 让系统调用过滤变得可编程:不是”禁止这个系统调用”,而是”在某些条件下禁止”。容器运行时用 seccomp profile 限制容器能调用的系统调用集合,是容器安全的重要组成。
Tetragon 走得更远:在内核函数级别实施访问控制,可以做到”禁止这个进程访问这个文件描述符”这样细粒度的策略,且策略可以在运行时动态修改,不需要重启进程或容器。
无 Sidecar 服务网格:Cilium 的工程逻辑
服务网格的经典实现是 Sidecar 模式:在每个 Pod 旁边注入一个 Envoy 代理,所有流量经过代理处理,mTLS、重试、熔断、可观测性在这一层实现。
这个模式的问题是资源开销和延迟。每个 Pod 额外消耗几十到几百 MB 内存,每个请求多两次 loopback 的延迟。在有几千个 Pod 的集群里,这个开销是显著的。
Cilium 提供了另一种路径:把 mTLS 和可观测性的逻辑放到 eBPF 程序里,在内核网络层直接处理,绕过 Sidecar。
服务间通信走 TCP/UDP,在 socket 层或 XDP 层拦截所有流量,做身份识别(基于 Kubernetes Service Account)、加密(通过 WireGuard 或 IPSec 内核模块)、策略执行。这些操作在内核态完成,不需要 userspace 的 Envoy 参与。
代价是:调试难度大幅上升。Sidecar 里的 Envoy 有丰富的 admin interface 和 access log,出问题可以直接看日志。eBPF 的问题藏在内核态,需要专门的工具才能观察。这不是一个简单的 tradeoff,对运维体系的要求更高。
CO-RE:可移植性的工程解法
eBPF 程序在不同内核版本之间的可移植性,一度是最大的工程痛点。
内核数据结构(比如 task_struct)在不同版本之间字段位置不同,一个为 5.15 编译的 eBPF 程序,到了 6.1 可能访问到错误的字段偏移量,读出来的数据是脏的。
BTF(BPF Type Format)和 CO-RE(Compile Once - Run Everywhere)解决了这个问题。BTF 是内核在编译时嵌入的类型信息,描述了所有数据结构的字段布局。CO-RE 在 eBPF 程序加载时,用目标内核的 BTF 信息重写字段访问的偏移量,实现一次编译、在不同内核版本上正确运行。
Linux 5.4 以后的内核基本都支持 BTF。这意味着你不需要为每个内核版本单独编译 eBPF 程序,可以分发预编译的二进制——这是 eBPF 工具走向”可分发生产工具”而不是”只能在本地编译运行”的关键一步。
eBPF 的边界在哪里
eBPF 不是万能的,几个场景里它不是正确答案:
调试窗口期短的问题。eBPF 的调试链路复杂。如果问题是偶发的、转瞬即逝的,eBPF 程序在定位问题前可能需要几轮迭代,不如直接加日志或者用更简单的工具快。
需要修改内核语义的逻辑。eBPF 可以观测和过滤,但不能修改内核的核心调度或内存管理逻辑(LSM BPF 有一定扩展,但仍有限制)。如果你需要的是”改变内核行为”而不是”在内核行为发生时做点什么”,eBPF 能做的有限。
极端吞吐量的 L2/L3 转发场景。在高吞吐量、对延迟极度敏感的纯转发场景,DPDK 这类完全绕过内核的方案有时能比 XDP 做得更彻底——因为 DPDK 连内核调度都绕开了,而 XDP 毕竟还在内核里运行。两者的选择取决于你的流量特征和运维成本的权衡。
eBPF 的意义,在于它把内核的可编程性从”需要写内核模块”的门槛,降到了”可以在用户态开发、安全加载”的门槛。
这个门槛的降低,正在改变基础设施工具的开发方式:Tetragon、Cilium、Parca、Coroot,都在这个可编程性上构建。基础设施的可观测性和可编程性,正在从”需要在内核里打补丁”变成”可以以服务的形式部署和迭代”。
这个生态还在早期。很多可能性——比如 eBPF 在 ML 推理链路的网络加速上——还没有被充分开发。但方向是清楚的:内核不再是黑盒,是可以被工程师以安全方式编程的基础设施层。