Cloudflare 用它每秒丢弃上千万个 DDoS 恶意数据包;Meta 用它管理全球数据中心的负载均衡;Netflix 用它监控流媒体服务的每一个性能细节。这三家公司都在用同一项技术——eBPF。
但诡异的是,大多数开发者根本没听过这个名字。
今天把 eBPF 这件事讲透:它到底是什么、怎么工作的、能干什么、想用它需要满足什么条件,以及你的现网服务器到底能不能跑起来。
为什么要在内核里跑程序?
Linux 内核是操作系统的灵魂,跑在最高权限的内核态。你电脑上每一次文件读写、每一次网络请求、每一次进程创建,最终都是内核在调度。
问题来了:内核跑着跑着出了问题,你怎么排查?传统做法要么改内核源码重新编译重启,要么加载一个内核模块(Kernel Module)。两种方式都很硬核——而且风险极高,一个 bug 就可能让整个系统内核崩溃(Kernel Panic),直接宕机。
真实场景:你线上跑着一台关键业务服务器,CPU 突然飙到 100%。你想看看是哪个系统调用在疯狂触发。传统做法要么装个 heavy-weight 的 tracing 工具把系统拖得更慢,要么只能干瞪眼等它自己恢复。
eBPF 彻底改变了这个局面。它让你可以在不修改内核源码、不加载内核模块、不重启系统的前提下,安全地在内核中运行自定义的小程序。每当内核里发生某个事件——收到一个网络包、触发一次系统调用、完成一次磁盘 I/O——你的程序就会被自动执行。
相当于给一辆高速行驶中的汽车,不停车、不拆引擎,直接在发动机里装上各种传感器,实时观测每一个零件的运行状态。
从 1992 到 2026,一个 30 年老技术的进化
eBPF 全称Extended Berkeley Packet Filter(扩展伯克利包过滤器)。名字里带 "Berkeley",就知道年纪不小了。
最早的故事要追溯到1992 年。加州大学伯克利分校搞出了一个叫 BPF 的东西,目的很简单:高效过滤网络数据包。tcpdump这个经典抓包工具,底层用的就是初代 BPF(也叫 cBPF,classic BPF)。
cBPF 能干的事很单一——在内核里跑过滤规则,符合条件的包才往用户态送,省掉不必要的数据拷贝。搁当年这已经是黑科技了,但放到今天看,就是个只能干一件事的流水线工人。
转折点发生在 2014 年。Google 的工程师 Alexei Starovoitov 对 BPF 做了彻头彻尾的改造,扩展成了通用的内核可编程引擎,也就是现在的 eBPF。改造之后,它不再局限于网络包过滤,能挂载到内核的几乎任何位置——网络层、系统调用、函数入口、安全模块,啥都能干。
| 对比项 | cBPF(1992) | eBPF(2014-至今) |
| 用途 | 网络包过滤 | 通用内核可编程引擎 |
| 挂载位置 | 仅网络层 | 网络/追踪/安全/调度/内存管理 |
| 指令集 | 4 位寄存器,16 位指令 | 11 个 64 位寄存器,64 位指令 |
| 数据交互 | 单向输出到用户态 | 双向 Map 通信 |
| 性能 | 解释执行 | JIT 编译,接近原生速度 |
从"单一功能的包过滤器"升级为"内核级可编程引擎"——这一步跨越,直接催生了一个庞大的技术生态。
一个 eBPF 程序的一生
从写出来到跑起来,一个 eBPF 程序要经过五个阶段。
① 编写 C 代码
↓
② LLVM/Clang 编译为 eBPF 字节码
↓
③ 内核验证器(Verifier)安全审查
↓ 审查通过
④ JIT 编译为原生机器指令
↓
⑤ 挂载到内核钩子点,事件触发即执行
这里面最值得聊的是第③步——验证器(Verifier)。
验证器是整个 eBPF 安全模型的基石。程序加载之前,它会对所有可能的执行路径做一遍完整的静态分析,检查的东西包括但不限于:每条指令是不是合法的 BPF 指令(有没有越界跳转)、所有指针访问是不是在合法范围内(不会越界读写内核内存)、程序能不能在有限步骤内结束、有没有读未初始化的变量。
验证器的代码文件kernel/bpf/verifier.c有超过 2 万行,是 Linux 内核中最复杂的源文件之一。很多开发者吐槽验证器太严格,明明逻辑没问题就是不让过。但换个角度想——这玩意跑在内核态,万一出了 bug 就是系统级宕机,严格一点没毛病。
几个你可能关心的技术细节:
• eBPF 虚拟机有 11 个 64 位寄存器,栈空间固定 512 字节——写惯了用户态程序 malloc 几 MB 的人看到这个数字会愣住,但内核里资源金贵,512 字节放几个局部变量够了
• 指令数量限制:早期 4096 条,现在已放宽到 100 万条
• JIT 编译后执行性能与内核原生代码几乎无差异;不启用 JIT 走解释执行的话,性能差 5~10 倍
Maps:内核态和用户态之间的「数据桥梁」
eBPF 程序本身是"无状态"的——每次事件触发就执行一遍,执行完就结束,不保留任何东西。那数据怎么传出来?
靠Maps。Maps 是内核态和用户态之间的共享数据结构。eBPF 程序在内核里往 Map 写数据,用户态程序从 Map 读数据。不用来回拷贝大量数据,效率很高。
Map 支持多种类型:Hash 表、数组、环形缓冲区(Ring Buffer)、LRU 缓存、Per-CPU 变量……你能想到的常用数据结构,基本都有对应实现。
典型工作模型:
内核态(eBPF 程序):在每次系统调用触发时,更新计数器 / 记录延迟到直方图 / 把异常事件写入 Ring Buffer
用户态(采集程序):定时读取 Map 数据,做聚合、可视化、告警、持久化存储
这就是经典的"内核态轻量采集 + 用户态复杂处理"模型。
挂载点决定了 eBPF 能干啥
eBPF 程序挂载在什么位置,决定了它能干什么事。
XDP(eXpress Data Path)挂得最靠前——在网络驱动层,数据包还没进入内核网络栈就已经被处理了。Cloudflare 用它实现每秒千万级的 DDoS 流量丢弃,恶意数据包在进入系统之前就被干掉,后续的协议栈处理完全不会触发。这是目前 Linux 上性能最高的网络过滤方案。
TC(Traffic Control)比 XDP 稍晚介入网络栈,但能力更灵活:不仅能丢包,还能改包。Cilium 用它来做 Kubernetes 的 Pod 间网络策略执行,替代了传统的 iptables 方案。
做内核函数级追踪用Kprobe / Tracepoint。Kprobe 可以挂载到几乎任何内核函数的入口和出口;Tracepoint 是内核中预定义的稳定追踪点。两者用来做系统调用追踪、性能分析、延迟监控。区别在于:Kprobe 灵活但受内核版本影响(函数名可能变更),Tracepoint 稳定但挂载位置固定。生产环境一般优先用 Tracepoint。
想知道用户态程序的函数性能瓶颈?用Uprobe。比如你想知道某个 HTTP 服务里handle_request()函数每次调用耗时多少,Uprobe 就能做到。
还有LSM(Linux Security Module),挂载到内核安全模块钩子上,实现自定义安全策略。比如"禁止某个进程打开网络 socket"、"阻止对 /etc/shadow 的写入"——在内核层面直接拦截,比用户态的安全 Agent 更底层、更难被绕过。
2026 年,哪些场景已经在用了?
eBPF 最成熟的落地方向是可观测性。代表工具是 Parca 和 Grafana Pyroscope。传统的全链路性能分析需要在应用代码里插桩(instrumentation),改了代码才能拿到数据。eBPF 方案完全不需要——Parca 以约1% 的 CPU 开销采集服务器上所有进程的 CPU Profile,自动生成火焰图。对比传统 Agent 动辄35% 的 CPU 开销(Datadog 五年 eBPF 部署经验的数据),差距是数量级的。
云原生网络方向,Cilium用 eBPF 完全替代了 iptables,作为 Kubernetes 的网络方案。实测在大规模集群中延迟降低50%~90%,而且支持 L7 层可见性(HTTP、gRPC、Kafka 协议识别),不需要额外部署 Sidecar 代理。传统的 Service Mesh(如 Istio 老版本)每个 Pod 都要挂一个 Envoy 容器,2 个额外进程、100ms+ 延迟开销。eBPF 模式下,一个节点只需要一个 DaemonSet Pod,所有流量在内核里处理。
安全方向,Tetragon(Cilium 生态)用 eBPF 实现了运行时安全执行:实时检测并阻止权限提升、杀死执行违禁系统调用的进程、追踪文件系统完整性。与传统用户态安全 Agent 相比,CPU 开销降低30%~50%,策略在内核层直接执行,攻击者几乎无法绕过。到 2026 年,Tetragon 已经积累了 4800+ GitHub Stars、33 个版本发布,v1.7.0 标志着生产级别的成熟度。
最新的进展更激进——eBPF 正在尝试进入 Linux 内核最复杂的子系统之一:内存管理。在 2026 年 LSFMM+BPF 峰会上,内核开发者提出了用 eBPF 重新设计 cgroup 内存控制器的构想:支持弹性化的内存策略(如"保持系统级内存利用率在 95% 以下"、"不对持有锁的分配器限流以避免优先级反转"),替代现有的刚性限制方案。不过这个方向还面临不少挑战:ABI 稳定性、热路径性能、故障回退机制等问题仍在讨论中。
想用 eBPF?先看看你的内核答不答应
上面说了这么多 eBPF 的好,该泼点冷水了。eBPF 有个绕不开的现实问题:它是内核特性,不是用户态的普通软件。你的内核版本、配置、权限,直接决定了 eBPF 能干什么、不能干什么。
很多团队在 demo 环境跑得好好的,一上生产就炸——staging 和 production 的内核版本不一样,同一个 eBPF 程序在 staging 能加载,到 production 验证器直接给你拒了。这不是段子,是真实踩过的坑。
🔍 实机验证:一台真实服务器上能看到什么
光说理论没意思,我们直接在一台真实服务器上跑一遍,看看 eBPF 的"底"到底长什么样。
先看内核版本
$ uname -r
6.6.95.bck.1-rc6-amd64
$ uname -a
Linux vefaas-sandbox 6.6.95.bck.1-rc6-amd64 #rc6 SMP Debian 6.6.95.bck.1-rc6
Mon Jun 15 07:21:05 UTC 2026 x86_64 GNU/Linux
Debian 系统,6.6 LTS 内核。6.6 是什么水平?对照前面的版本表,这个版本已经远超 eBPF 的"舒适区"门槛(≥ 5.8)——CO-RE、Ring Buffer、fentry/fexit、BPF LSM 全都有,甚至 bpf_loop(5.17+)和 bpf_kfunc(5.18+)也都支持了。属于生产环境里的第一梯队。
BTF 文件存在吗?
$ ls -lh /sys/kernel/btf/vmlinux
-r--r--r-- 1 root root 4.8M Jul 15 20:22 /sys/kernel/btf/vmlinux
在。4.8MB 的 BTF 文件,记录了内核里所有数据结构的类型信息。这是 CO-RE 能跑起来的硬前提——有了 BTF,eBPF 程序才能"看懂"内核的数据结构,实现"一次编译到处运行"。
再拆开看 BTF 里面到底装了什么:
$ python3 -c "解析 BTF header"
BTF magic: 0xeb9f ← BTF 魔数,确认是合法的 BTF 文件
BTF version: 1 ← 当前标准版本
Type section: 2.9 MB ← 类型定义数据(struct、enum、typedef...)
String section: 2.1 MB ← 类型名称字符串表
2.9MB 的类型定义 + 2.1MB 的名称字符串——这个内核里有几万个数据结构的完整描述信息。写 eBPF 程序时,CO-RE 重定位机制就靠这些信息把编译时的结构体偏移映射到实际运行的内核上。
内核里到底有多少 BPF 代码?
$ grep -c bpf /proc/kallsyms
1986 ← 总计 1986 个 BPF 相关内核符号
$ grep -c "bpf.*map\|map.*bpf" /proc/kallsyms
184 ← Map 子系统相关函数
$ grep -c "bpf.*prog\|prog.*bpf" /proc/kallsyms
160 ← 程序加载/管理相关函数
$ grep -c "bpf.*verif\|verif.*bpf" /proc/kallsyms
7 ← 验证器核心函数
1986 个符号——这还不算各种辅助宏和静态函数。eBPF 在内核里已经是一个体量相当大的子系统了。Map 相关 184 个、程序管理 160 个,验证器虽然只暴露了 7 个顶层符号,但它背后的代码是 2 万多行的 verifier.c。
BPF 文件系统
$ ls -la /sys/fs/bpf/
total 0
dr-xr-xr-x 2 root root 0 Jul 15 20:01 .
drwxr-xr-x 12 root root 0 Jul 15 20:01 ..
/sys/fs/bpf/已经挂载了,但里面是空的——没有已加载的 eBPF 程序。这说明当前这台机器上还没有人在跑 eBPF。如果有 Parca、Cilium 之类的工具在运行,你会在这里看到一堆持久化的 BPF 对象文件,比如bpf_program_XXX、bpf_map_XXX。
权限检查
$ sysctl kernel.unprivileged_bpf_disabled
kernel.unprivileged_bpf_disabled = 1
值为 1,意味着普通用户完全不能加载 eBPF 程序。只有持有 CAP_BPF 或 CAP_SYS_ADMIN 权限的进程才行。这是安全最佳实践——你肯定不希望一个普通进程随便往内核里塞一段代码。
容器环境的"坑"
$ cat /proc/cmdline
... systemd.unit=kata-containers.target ...
$ cat /proc/1/cgroup
0::/../vefaas-sandbox
这是一台跑在Kata Containers上的容器。容器环境有几个典型问题:
•/boot/config-$(uname -r)不存在——容器里通常不打包内核配置文件,你没法直接 grep 检查内核开了哪些选项
• debugfs 无法挂载——/sys/kernel/debug/tracing/不可用,很多传统 tracing 工具的依赖断了
• 但好消息是:内核本身是共享的,6.6 的能力该有都有,BTF 也在。只是容器层面缺少一些用户态辅助文件和调试入口
这就是为什么现网接入时,不能只看"我的容器能不能跑",得看"我的节点内核能不能跑"——容器共享宿主内核,容器的 eBPF 能力上限 = 宿主机内核的能力。
内核版本——最硬的门槛
刚才那台服务器的内核是 6.6,属于 eBPF 的"满配"状态。但不是每台服务器都这么幸运。eBPF 的能力是随着内核版本逐步解锁的,不同功能特性对应不同的最低内核版本。
eBPF 关键特性 vs 最低内核版本
| 功能特性 | 最低内核版本 | 一句话说明 |
| 基础 eBPF | ≥ 4.10 | 能用,但功能很有限 |
| BPF LSM(安全策略) | ≥ 5.7 | 自定义内核安全策略 |
| Ring Buffer | ≥ 5.8 | 比 Perf Buffer 吞吐高 20%+ |
| CO-RE(跨内核兼容) | ≥ 5.8 | 一次编译到处运行,生产必备 |
| fentry/fexit(高性能追踪) | ≥ 5.5 | 替代传统 kprobe 的方案 |
| bpf_loop | ≥ 5.17 | 支持循环结构 |
| bpf_kfunc(调用内核函数) | ≥ 5.18 | 灵活性大幅提升 |
| TCX(TC 增强版) | ≥ 6.6 | 网络场景高性能挂载 |
实操建议(划重点):
•4.15 以下:基本没戏,跟 eBPF 说再见
•4.15 ~ 5.4:能用基础功能,但高级特性缺失,维护成本高
•5.4 ~ 5.8:主流云厂商节点镜像的起点,基本够用
•5.8 及以上:进入舒适区,CO-RE 生态成熟,工具链开箱即用
•5.15+:最佳体验,生产环境首选
内核配置——光有版本不够,还得"开门"
就算内核版本够了,对应的功能开关没打开,eBPF 也用不了。核心需要开启的内核配置项:
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_CORE=y # CO-RE 必需
CONFIG_DEBUG_INFO_BTF=y # BTF 类型信息,CO-RE 的根基
CONFIG_CGROUP_BPF=y # cgroup 挂载点
CONFIG_XDP_SOCKETS=y # XDP 场景
CONFIG_TRACEPOINTS=y # 追踪点
好消息:阿里云 ACK、华为云 CCE、腾讯云 TKE 等主流云厂商的 K8s 节点镜像已经默认开启了这些配置。自建集群需要手动检查和开启。
快速自检命令(拷走直接用):# 检查内核版本
uname -r
# 检查 BTF 是否存在(CO-RE 的硬前提)
ls /sys/kernel/btf/vmlinux
# 检查内核配置是否开启(需要 /boot/config 文件)
grep -E 'CONFIG_BPF|CONFIG_BPF_SYSCALL|CONFIG_DEBUG_INFO_BTF' /boot/config-$(uname -r)
# 如果 /boot/config 不存在(容器环境),可以换这种方式:
cat /proc/config.gz | zcat | grep CONFIG_BPF # 部分内核支持
权限——不是随便谁都能加载 eBPF 程序
eBPF 程序跑在内核态,能直接影响系统行为。Linux 对加载 eBPF 程序有严格的权限要求——不是你在 shell 里敲个命令就能加载的。刚才实测的那台服务器unprivileged_bpf_disabled = 1,就是最直接的体现。
| 权限 | 用途 | 什么时候需要 |
| CAP_BPF + CAP_PERFMON | 加载程序 + 性能监控 | 内核 ≥ 5.8,推荐的最小权限 |
| CAP_SYS_ADMIN | 兜底大权限 | 老内核没有 CAP_BPF 时不得不给 |
| CAP_SYS_RESOURCE | 调整 mlock 内存限制 | eBPF Map 需要锁内存 |
| CAP_NET_ADMIN | 网络相关挂载 | XDP / TC 场景 |
在 K8s 环境下,运行 eBPF 的 Pod 通常需要配置privileged: true或者精确配置 SecurityContext 的 capabilities。另外别忘了开启 JIT 编译,不开的话性能差 5~10 倍:
sysctl -w net.core.bpf_jit_enable=1
现网接入最大的坑:内核版本碎片化
单台机器的约束说完了,集群维度的问题才是很多团队真正栽跟头的地方。
真实案例:某金融公司的 24 节点 K8s 集群,staging 跑 6.1 内核,一切正常。上线 production,节点跑的是 5.15 LTS——同一个 Helm chart,同一个 eBPF Agent,16 个节点跑不了完整功能。原因很简单:
bpf_loop只有 8/24 个节点的内核支持(需要 ≥ 5.17)
RHEL 8 节点连 BTF 都没有,导致 fentry/fexit 无法挂载、CO-RE 重定位无从谈起
bpf_kfunc需要 5.18+,绝大多数集群还在用 5.15 LTS,几乎全军覆没
所以现网接入的第一步不是部署,是审计。你得先搞清楚集群里每个节点的内核版本和能力矩阵,然后才能决定哪些 eBPF 程序能跑、跑在哪。
现网接入怎么搞:先审计内核能力——跑一遍 feature probe,搞清楚每个节点支持什么:
sudo bpftool feature probe kernel | grep "map_type ringbuf"
sudo bpftool feature probe kernel | grep "program_type tracing"
选对工具链:
• 内核 ≥ 5.8 → 用
CO-RE + libbpf(一次编译到处运行,生产首选)
• 内核 4.15~5.8 → 用
BCC(需要运行时编译,体积大启动慢,但兼容性更好)
• 内核 < 4.15 → 放弃 eBPF,考虑传统方案
运行时特性探测,不要假设——同一个部署包打到不同内核版本的节点,必须在运行时做 capability probe,按节点能力决定加载哪些程序。不要指望"一个 eBPF object 到处跑"。
关注发行版的后向移植——RHEL/CentOS 系列经常会把新内核的 eBPF 特性 backport 到老版本内核上,所以别只看版本号,要看实际能力。
还有一个隐藏约束值得一提:CentOS 7 基本没戏。CentOS 7 默认内核 3.10,eBPF 支持极其有限,2024 年 6 月已经 EOL。如果你还有 CentOS 7 的机器在跑,建议尽早规划升级——不只是 eBPF 的问题,整个安全补丁都停了。Rocky Linux 9(5.14 内核)是目前最平滑的替代方案。
写在最后
运维同学应该是最有感触的群体。以前排查一个疑难杂症,可能要改内核代码、编译、重启。现在写个 eBPF 程序加载进去,实时就能拿到数据。系统调用追踪、网络延迟分析、进程行为监控,全部可以在生产环境不停机完成——前提是内核版本够。
做云原生的更不用说了。Cilium + eBPF 已经是 Kubernetes 网络的主流方案,性能碾压 iptables。Service Mesh 正在从 Sidecar 模式向 eBPF 内核模式迁移,2026 年这个趋势已经非常明显。好消息是云厂商的节点镜像基本已经帮你把内核配好了。
做安全的可能更兴奋——eBPF 让安全策略的执行从用户态下沉到了内核态,更难被绕过,性能开销更低。Tetragon 这类工具正在成为云原生安全的标配组件。
当然 eBPF 也不是万能的。学习门槛不低——你得理解内核数据结构、熟悉 BPF 指令集约束、跟严格的验证器斗智斗勇。内核版本的碎片化更是每个生产环境都要面对的现实问题。但方向很清楚:eBPF 代表了 Linux 系统编程的未来,它让"动态、安全、高性能地扩展内核"从理想变成了现实。
想上手的话,记住三条线就够了:内核 ≥ 5.8 + BTF 开启 + CAP_BPF 权限到位。低于这个标准,每降一个版本就多一层兼容性的痛。