1. 背景:BPF 是什么?
BPF 全称是 Berkeley Packet Filter,最早主要用于网络包过滤。
内核提供一个小型、安全的“过滤程序执行环境”,让用户可以把一段规则放进内核里,对网络包进行判断。
早期典型场景是 tcpdump。比如你执行:
tcpdump port 80
如果没有 BPF,内核可能要把大量网络包都拷贝到用户态,然后 tcpdump 再过滤。这样很浪费。
有了 BPF 后,可以先把“只要 80 端口的数据包”这个过滤逻辑放到内核里执行。内核只把符合条件的包交给用户态程序,效率更高。
Linux 内核文档现在把 BPF 文档重点放在 extended BPF,也就是 eBPF 上。
2. 问题:传统内核扩展方式有什么痛点?
操作系统内核很强大,但也很敏感。
假设你想做这些事情:
- 观察某个进程为什么慢;
- 统计每个容器的网络流量;
- 拦截异常系统调用;
- 做高性能负载均衡;
- 在内核路径上采集指标。
传统方式通常有几个选择。
第一种是改内核源码。问题是成本高、风险大、升级困难。
第二种是写内核模块。问题是内核模块权限很高,一旦写错,可能导致内核崩溃,甚至带来安全漏洞。
第三种是在用户态采集数据。问题是用户态和内核态之间切换成本高,很多细粒度事件也不容易拿到。
所以核心矛盾是:
我们想扩展内核能力,但又不想频繁改内核、不想重启、不想引入不安全的内核模块。
3. 解决方案:eBPF 是什么?
eBPF 可以理解为 BPF 的增强版。现在它已经不只是“网络包过滤器”,而是 Linux 内核里的一个通用可编程机制。
官方 eBPF 文档对它的定义大致是:eBPF 可以在操作系统内核这样的特权上下文中运行沙箱程序,用来安全、高效地扩展内核能力,而不需要修改内核源码或加载传统内核模块。
它的工作方式可以简化成这样:
用户写 eBPF 程序
↓
编译成 eBPF 字节码
↓
加载到内核
↓
Verifier 安全检查
↓
JIT 编译 / 解释执行
↓
挂到某个内核事件点上运行
关键组件有几个。
1)Hook:挂载点
eBPF 程序不会凭空运行,它要挂在某个事件点上。
常见 hook 包括:
- 网络包进入或离开网卡;
- 某个系统调用发生;
- 某个内核函数被调用;
- 某个用户态函数被调用;
- 性能事件触发。
也就是说,eBPF 像是在内核关键路径上放了一些“可编程探针”。
2)Verifier:安全检查器
这是 eBPF 很核心的地方。
程序加载进内核前,内核会先验证它是否安全。比如检查它会不会越界访问、会不会无限循环、会不会非法访问内核内存等。eBPF 官方文档也强调,程序加载进内核前会经过 verification 阶段,以确保程序安全运行。
这解决了一个很大的问题:
允许用户扩展内核,但不能让用户随便把内核搞崩。
3)Maps:内核态和用户态共享数据
eBPF 程序运行在内核中,但结果通常要给用户态程序看。
比如:
- 某个进程调用了多少次
open(); - 某个 IP 发了多少包;
- 某个容器的延迟是多少。
这些数据可以存在 eBPF Map 里。用户态程序可以读取 map,eBPF 程序也可以更新 map。
可以简单理解成:
eBPF 程序负责在内核里采集 / 处理
用户态程序负责读取 / 展示 / 控制
Map 是两者之间的共享数据结构
4)JIT:接近原生性能
eBPF 字节码可以被 JIT 编译成机器码执行。Linux 内核文档提到,eBPF 的设计目标之一就是支持高效 JIT,使其性能可以接近原生编译代码。
所以它不是简单的“脚本解释执行”,而是面向高性能内核路径设计的。
4. 效果:eBPF 带来了什么?
第一,观测能力大幅增强
以前排查系统问题,很多时候只能看到用户态日志,或者靠比较重的 tracing 工具。
有了 eBPF,可以在内核层面观察:
进程 → 系统调用 → 网络 → 文件 → 调度 → TCP 栈
比如你可以回答:
- 哪个进程在疯狂创建连接?
- 哪个系统调用耗时最高?
- 哪个容器丢包严重?
- 某个请求慢到底慢在用户态、内核态还是网络路径?
第二,网络性能和灵活性提升
eBPF 很常见的一个方向是高性能网络。
比如 Kubernetes 网络组件 Cilium 就是基于 eBPF 做网络、安全和可观测性的开源项目。Cilium 官方描述它用于云原生环境中的 networking、security 和 observability。
在这类场景里,eBPF 可以用于:
- 高性能转发;
- 负载均衡;
- 网络策略;
- 服务可观测性;
- 替代部分传统 iptables 路径。
第三,安全能力增强
eBPF 可以观察和限制系统行为,例如:
- 监控异常系统调用;
- 检测容器逃逸行为;
- 记录敏感文件访问;
- 结合策略系统做运行时安全。
不过也要注意:eBPF 本身能力很强,所以它通常需要权限控制。官方文档也提到,加载 eBPF 程序需要满足权限要求,非特权 eBPF 是否可用取决于系统配置。
第四,不需要频繁改内核
这是 eBPF 的核心价值之一。
传统方式:
想扩展内核能力
→ 改内核 / 写内核模块
→ 编译
→ 部署
→ 重启或高风险加载
eBPF 方式:
写 eBPF 程序
→ 加载到内核
→ verifier 检查
→ 挂到事件点
→ 运行
也就是说,eBPF 让内核变得“可编程”,但又通过 verifier、权限控制、沙箱机制尽量保证安全。
5. 简单示例:用 eBPF 观察谁在执行命令
我们用一个很直观的例子:
每当系统里有进程执行新命令时,把进程名、PID、执行的文件路径打印出来。
这类事件对应 Linux 的 execve 系统调用。比如你在终端执行:
ls
python app.py
curl example.com
这些都会触发 execve。
可以用 bpftrace 写一个非常短的 eBPF 程序:
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_execve
{
printf("process=%s pid=%d exec=%s\n", comm, pid, str(args->filename));
}
'
它的含义是:
tracepoint:syscalls:sys_enter_execve
表示把 eBPF 程序挂到 execve 系统调用入口。
comm
表示当前进程名。
pid
表示当前进程 ID。
args->filename
表示这次要执行的程序路径。
运行之后,另开一个终端执行:
ls
whoami
date
你可能会看到类似输出:
process=bash pid=12345 exec=/usr/bin/ls
process=bash pid=12346 exec=/usr/bin/whoami
process=bash pid=12347 exec=/usr/bin/date
这个例子体现了 eBPF 的核心思想:
用户态写一段小程序
↓
加载到内核
↓
挂到某个内核事件点
↓
事件发生时自动执行
↓
把结果输出给用户态
对应到前面的四点:
背景:我们想观察内核里的系统调用行为
问题:传统方式很难低成本、低侵入地看到这些事件
解决方案:用 eBPF 挂到 execve 这个 tracepoint 上
效果:不用改内核、不用写内核模块,就能实时看到进程执行行为
所以这个例子可以总结成一句话:
eBPF 让我们可以在内核事件发生时,安全地插入一段小程序,实时观察或处理系统行为。
6. 生产环境例子:Kubernetes 集群用 eBPF 做网络、负载均衡和可观测性
一个非常典型的生产场景是:
公司有一个 Kubernetes 集群,里面跑了几百到几千个 Pod。服务之间调用频繁,网络策略复杂,还需要排查“某个服务为什么访问慢 / 为什么连接失败”。
这时可以使用 Cilium 这类基于 eBPF 的 Kubernetes 网络方案。Cilium 官方定位就是为 Kubernetes 等云原生环境提供 networking、security、observability;它使用 eBPF 作为核心数据平面。
1. 背景
假设线上有这样一套系统:
用户请求
↓
Ingress / Gateway
↓
order-service
↓
payment-service
↓
inventory-service
↓
database
这些服务都跑在 Kubernetes 里,每个服务可能有多个 Pod。
比如:
order-service-1
order-service-2
order-service-3
payment-service-1
payment-service-2
Kubernetes 需要解决几个基础问题:
服务发现
负载均衡
Pod 到 Pod 通信
网络策略
流量可观测性
传统 Kubernetes 里,很多 Service 转发依赖 kube-proxy,常见实现方式是 iptables 或 IPVS。
2. 问题
当集群规模变大后,传统网络路径会遇到一些问题。
比如,一个线上订单服务偶尔报错:
order-service 调 payment-service 超时
运维和开发需要回答几个问题:
请求到底有没有发出去?
被哪个网络策略拦了?
连接到了哪个 payment-service Pod?
延迟发生在客户端、服务端,还是网络路径?
某个节点是不是丢包?
如果只看应用日志,通常不够。
因为应用日志只能看到:
payment request timeout
但它不一定能告诉你:
包在内核网络栈哪里被处理
Service 转发到了哪个后端 Pod
哪条网络策略放行或拒绝了流量
TCP 重传是否变多
另一个问题是性能。
如果集群里 Service、Pod、Endpoint 很多,传统 iptables 规则会变得复杂,网络转发路径和排障难度都会上升。Cilium 文档中就有专门的 “Kubernetes Without kube-proxy” 模式,用 Cilium 替代 kube-proxy。
3. 解决方案:用 eBPF 接管部分内核网络路径
在这个生产场景里,Cilium 会在每个 Kubernetes 节点上运行 Agent,并把 eBPF 程序加载到 Linux 内核的网络路径中。
简化后是这样:
Pod 发出请求
↓
Linux 内核网络路径
↓
eBPF 程序执行
↓
判断目标 Service
↓
选择后端 Pod
↓
执行转发 / 策略检查 / 观测记录
也就是说,eBPF 程序可以直接在内核里做这些事情:
Service 负载均衡
网络策略判断
流量转发
连接跟踪
指标采集
流量日志
Cilium 的 GitHub 描述中也提到,它使用 eBPF-based dataplane,支持跨集群网络、L3/L4/L7 策略,并能用 eBPF 替代 kube-proxy,实现分布式负载均衡。
4. 效果
效果一:请求路径更清楚
以前排查问题可能是这样:
应用日志 → 容器日志 → 节点日志 → iptables → tcpdump
用了 eBPF 之后,可以直接观察服务之间的网络流。
例如:
order-service → payment-service
状态:FORWARDED
协议:HTTP
延迟:35ms
源 Pod:order-service-7b9c
目标 Pod:payment-service-6f2a
Cilium 生态里的 Hubble 就是用来做网络可观测性的组件,Cilium 文档也把 observability、flow logs、metrics、tracing 作为其重要能力。
效果二:网络策略更直观
比如生产要求:
order-service 可以访问 payment-service
frontend 可以访问 order-service
其他服务不能直接访问 payment-service
传统方式可能要写很多网络规则,而且排查困难。
使用 Cilium 后,可以基于服务身份做策略,而不是只依赖 IP。
例如逻辑上是:
允许:
order-service → payment-service
拒绝:
unknown-service → payment-service
这对 Kubernetes 很重要,因为 Pod IP 经常变化,但服务身份相对稳定。
效果三:可以替代 kube-proxy
在一些生产集群中,会启用 Cilium 的 kube-proxy replacement 模式。
传统路径大概是:
Pod
↓
Service IP
↓
kube-proxy / iptables
↓
后端 Pod
使用 eBPF 后可以变成:
Pod
↓
eBPF Service 负载均衡
↓
后端 Pod
Cilium 官方文档明确提供了不用 kube-proxy、由 Cilium 替代它的部署模式。
效果四:低侵入
这点很重要。
生产环境里你通常不希望:
修改业务代码
重启所有服务
给每个服务加 sidecar
手动抓包
加载高风险内核模块
eBPF 的好处是,很多观测和网络能力可以发生在内核路径里,对业务应用比较透明。
也就是说,业务代码仍然是:
requests.post("http://payment-service/pay")
但底层的服务发现、负载均衡、策略判断、流量观测,可能已经由 eBPF 在内核里完成了。
5. 一个具体生产故障例子
假设线上出现报警:
payment-service 错误率升高
order-service 调用 payment-service 超时
没有 eBPF 时,排查可能是:
1. 看 order-service 日志
2. 看 payment-service 日志
3. 查 Kubernetes Service
4. 查 Endpoint
5. 查节点网络
6. tcpdump 抓包
7. 查 iptables 规则
有 eBPF / Cilium / Hubble 后,可以直接看流量路径:
order-service-7b9c → payment-service-6f2a
状态:DROPPED
原因:Policy denied
这时问题就很明确:
不是 payment-service 挂了,而是网络策略把流量拦掉了。
或者你看到:
order-service-7b9c → payment-service-6f2a
状态:FORWARDED
延迟:900ms
TCP retransmission increased
这说明流量没有被拒绝,但网络质量或目标 Pod 处理能力可能有问题。
再比如看到:
order-service-7b9c → payment-service-9a21
状态:FORWARDED
HTTP status:500
那就说明网络层正常,问题更可能在 payment-service 应用逻辑里。
这个例子里的 eBPF 价值
可以总结成一句话:
在生产 Kubernetes 集群里,eBPF 可以把原本黑盒的内核网络路径变成可编程、可观测、可控制的数据平面。
对应到实际效果:
网络转发更高效
服务负载均衡更灵活
网络策略更细粒度
故障排查更快
对业务代码侵入更低
所以,eBPF 在生产环境里不是单纯“看系统调用”的小工具,而是可以成为云原生网络、安全和可观测性的底层基础设施。