摘要:理解 eBPF 程序、Map、XDP、TC 与 Socket Hook,结合 Cilium 分析 Service 转发、网络策略和 Hubble 流量观测的实现思路。
这一节继续深入 Cilium 的核心技术:eBPF(extended Berkeley Packet Filter)。
这部分知识将帮助你理解现代 Kubernetes 网络是如何在 Linux 内核中完成流量转发、Service 负载均衡、网络策略和流量观测的。
本节重点:
- eBPF 是什么,为什么 Linux 需要它。
- eBPF 程序如何加载和执行。
- XDP、TC、Socket 三种网络处理位置。
- eBPF Map 如何保存网络状态。
- Cilium 如何使用 eBPF 实现 Service 转发。
- Cilium NetworkPolicy 与 Hubble 的工作原理。
一、eBPF 是什么?
可以先把 eBPF 理解为:一种允许经过验证的程序,在 Linux 内核支持的特定执行点运行的技术。
传统情况下,如果你希望修改 Linux 网络内核的某些行为,可能需要:
- 修改 Linux 内核代码。
- 编译或加载内核模块。
- 使用内核已经提供的固定功能,例如 iptables。
eBPF 提供了另一种方式:通过程序描述处理逻辑,在内核支持的位置执行,而不必为每项自定义功能都修改内核源码。
flowchart TB
subgraph G0["传统规则式处理"]
direction TB
N0["应用程序"]
N1["iptables 规则"]
N2["Netfilter"]
end
subgraph G1["eBPF 处理方式"]
direction TB
N3["用户态控制程序"]
N4["eBPF 程序与 Map"]
N5["内核执行点"]
end
N0 --> N1
N1 --> N2
N3 --> N4
N4 --> N5
概念对比:两者并非互斥,实际系统可以同时使用 Netfilter、eBPF 和其他 Linux 网络机制。
例如,eBPF 可以用于:
- 网络数据包过滤和转发。
- 网络负载均衡。
- 运行时安全监控。
- 性能分析与内核事件追踪。
- 系统调用观测。
但是 eBPF 不是一个可以任意执行所有内核操作的通用运行环境。程序类型、可用辅助函数、内存访问以及执行行为都受到内核约束。
二、eBPF 程序是如何运行的?
以一个使用 C 编写的 eBPF 程序为例,整体流程如下:
flowchart TB
N0["1. 编写 eBPF 程序<br/>通常使用 C,也有 Rust 等工具链"]
N1["2. 编译为 eBPF 字节码<br/>例如使用 Clang/LLVM"]
N2["3. 加载并通过 Verifier 验证<br/>内核检查程序是否合法、安全性约束是否满足"]
N3["4. 挂载到内核执行点<br/>例如 XDP、TC 或 cgroup Socket Hook"]
N4["5. 事件触发程序执行<br/>内核根据程序类型执行相应逻辑"]
N0 --> N1
N1 --> N2
N2 --> N3
N3 --> N4
这里有三个关键组件。
1. Verifier:验证器
eBPF 程序在加载到内核时,需要通过验证器检查。
验证器会分析程序的控制流、内存访问、指针使用和辅助函数调用等,判断是否满足相应的安全约束。
例如,非法的越界内存访问可能导致程序被拒绝加载。
但验证器不能证明程序的所有业务逻辑都正确,也不能保证程序永远不会造成性能问题。
2. JIT:即时编译器
在支持的架构和配置下,eBPF 字节码可以通过 JIT 编译为本机机器指令执行。
这样可以避免每次都通过字节码解释器逐条执行,提高执行效率。
不过,JIT 是否可用以及采用何种执行方式,取决于内核、体系结构与系统配置。
3. Hook:执行位置
eBPF 程序不是加载后就随时自动运行。
它通常需要附着到特定事件或执行点,例如:
- 网络设备收到数据包。
- 数据包经过 TC 处理点。
- 应用调用某些 Socket 操作。
- 内核函数或系统调用触发跟踪事件。
不同 Hook 可以访问的信息和能够执行的操作不同。
三、XDP、TC 和 Socket Hook 有什么区别?
这是理解 Cilium eBPF 数据面的重点。
flowchart TB
N0["物理网卡接收数据包"]
N1["XDP<br/>尽早处理进入网卡的数据包"]
N2["Linux 网络协议栈<br/>包括 TC、路由、Netfilter 等处理机制"]
N3["Socket / 应用进程<br/>连接建立、数据收发"]
N0 --> N1
N1 --> N2
N2 --> N3
简化模型:TC ingress/egress 与 Socket Hook 属于不同的处理位置,不能把所有 Hook 理解为单一的线性执行顺序。
1. XDP:较早处理入站数据包
XDP(eXpress Data Path)可以在网卡驱动的较早处理阶段执行 eBPF 程序。
典型应用:
- 高性能数据包丢弃。
- DDoS 流量过滤。
- 快速转发。
- 特定负载均衡加速。
在支持的环境中,XDP 可以让部分数据包不必经过完整的上层网络协议栈。
但是 XDP 不一定总是性能最优,也不是所有网卡驱动都支持同样的 XDP 模式。
2. TC:更灵活的网络数据包处理
TC(Traffic Control)属于 Linux 网络流量处理机制。
eBPF 可以附着到 TC ingress 或 egress 等位置,处理经过网络接口的数据包。
典型应用:
- 数据包过滤。
- 网络策略执行。
- 地址转换。
- 流量重定向。
- 容器网络转发。
相比 XDP,TC 所处位置通常可以使用更多 Linux 网络协议栈相关上下文,但具体能力取决于内核和程序类型。
3. Socket Hook:在连接或收发阶段处理
Socket 层的 eBPF 程序可以附着到特定 cgroup Socket Hook 等执行点。
对于 Kubernetes Service,某些实现可以在应用建立连接时就完成 Service 地址到后端地址的转换。
例如,应用发起:
connect(10.96.120.30:8080)Cilium 的 Socket Load Balancing 可以在相关 Hook 中选择后端:
10.244.2.20:80这样,流量在进入后续网络路径之前,就可能已经绑定到实际后端地址。
三种 Hook 对比
| 对比 | XDP | TC | Socket |
|---|---|---|---|
| 主要执行位置 | 网卡接收的早期阶段 | 网络接口的流量处理位置 | 特定 Socket 操作位置 |
| 典型用途 | 快速过滤、转发 | 策略、流量转发 | Socket 级负载均衡 |
| 处理对象 | 数据包 | 数据包 | Socket 操作或相关状态 |
| Cilium 应用 | 部分外部流量加速 | 网络数据面与策略 | Service Socket LB |
需要记住:eBPF 不是一种单独的网络转发方式,而是一套可以部署到多个内核执行点的可编程机制。
四、eBPF Map:内核里的网络状态表
理解了程序如何运行,接下来还有一个问题:
eBPF 程序如何知道某个 Service 有哪些后端 Pod?
答案之一就是 eBPF Map。
Map 是内核管理的数据结构,可以供 eBPF 程序访问,也可以在权限允许的情况下由用户态程序进行管理。
常见 Map 类型包括:
- Hash Map:键值映射。
- Array Map:按索引访问数据。
- LRU Hash Map:具备最近最少使用淘汰机制的哈希表。
- LPM Trie:适合最长前缀匹配等场景。
- Ring Buffer:常用于向用户态传递事件数据。
1. 以 Service 转发为例
假设有一个 Service:
Service:10.96.120.30:8080
后端:Pod A:10.244.1.10:80Pod B:10.244.2.20:80可以用两张逻辑表帮助理解:
Service 查找表(概念示意)
| Service 地址 | 后端集合 |
|---|---|
| 10.96.120.30:8080 | 后端 A、B |
后端映射表(概念示意)
| 后端 ID | 实际地址 |
|---|---|
| A | 10.244.1.10:80 |
| B | 10.244.2.20:80 |
真实 Cilium Map 的键、值结构会随版本、协议和功能配置变化,并不一定直接按照这两张表存储。
用户态的 Cilium Agent 可以观察 Kubernetes Service 和 EndpointSlice 的变化,随后维护相关数据面状态。
例如,Pod A 被删除后:
- Kubernetes 更新相应的 EndpointSlice。
- Cilium Agent 感知服务端点变化。
- Agent 更新 eBPF 相关 Map。
- 后续新连接选择当前可用的后端。
这是一种典型的控制面与数据面分离设计。
- 控制面:负责管理期望状态和同步数据。
- 数据面:负责根据已配置的状态处理实际网络流量。
2. Map 为什么很重要?
因为数据包处理时,内核中的 eBPF 程序可以直接查询相关 Map,而不必为了每个请求都向 Kubernetes API Server 查询后端信息。
例如:
flowchart TB
N0["请求进入"]
N1["eBPF 程序"]
N2["查询 Service Map"]
N3["选择后端"]
N4["转发或重定向"]
N0 --> N1
N1 --> N2
N2 --> N3
N3 --> N4
这样有利于减少频繁的用户态与内核态交互。
但 Map 不是无限容量的,其容量、内存占用和更新效率也需要监控。
五、Cilium 如何实现 kube-proxy 替代?
传统 kube-proxy 的 iptables 模式通常依赖 Netfilter 规则进行 Service 匹配、后端选择和 NAT。
Cilium 在启用适用的 kube-proxy 替代配置时,可以通过 eBPF 实现 Service 负载均衡。
1. 两种数据面逻辑比较
传统 iptables 方式
** 客户端 Socket**
** 数据包进入协议栈 **
**Netfilter / DNAT**
**Pod 后端 **Cilium Socket LB 方式
** 客户端 Socket**
**eBPF Socket Hook**
** 后端地址已选定 **
**Pod 后端 **Socket LB 的一个重要特点是:可以在应用建立连接阶段完成后端选择,而不是要求后续每个数据包都进行同样的 Service 目的地址转换。
但是,Cilium 的 Service 实现并不只有 Socket LB。
根据不同流量类型、网络配置和兼容性要求,它还可以通过 TC、XDP 等方式执行负载均衡。
2. 是否意味着 eBPF 一定比 iptables 快?
不能简单这么认为。
eBPF 在某些工作负载下能够减少规则匹配或数据包处理开销,但实际性能受到很多因素影响,例如:
- CPU 和网卡硬件。
- Linux 内核版本。
- Service 数量和连接规模。
- 是否跨节点。
- 是否进行 SNAT、DSR 或隧道封装。
- 是否启用网络策略、监控和加密。
- 工作负载主要受 CPU、内存还是网络带宽限制。
所以,正确的比较方式是:在相同硬件、网络拓扑和业务负载下,比较延迟、吞吐量、CPU 使用量和丢包等指标。
六、Cilium 如何实现 NetworkPolicy?
传统 Kubernetes NetworkPolicy 的规则通常围绕 Pod 标签、Namespace、IP 地址和端口定义。
Cilium 进一步引入与工作负载身份相关的网络策略机制。
可以这样理解:
flowchart TB
N0["Frontend Pod<br/>标签:app=frontend"]
N1["Cilium eBPF 策略检查"]
N2["Backend Pod<br/>标签:app=backend"]
N3["拒绝:不符合策略"]
N0 -->|"访问 TCP 8080"| N1
N1 -->|"允许:符合策略"| N2
N1 --x N3
在 Cilium 的身份模型中,工作负载可以根据安全相关标签关联到数字安全身份。相应的 eBPF 数据面利用这些身份信息和策略状态进行判断。
一个重要优势是:策略可以围绕工作负载身份表达,而不必仅仅依赖某个临时的 Pod IP。
例如,后端 Pod 被重新创建:
旧 IP:10.244.2.20新 IP:10.244.2.35如果它保持同样的安全相关标签,策略语义可以继续表达“允许 frontend 访问 backend”,而不必让用户逐个维护临时 Pod IP。
NetworkPolicy 是如何执行的?
概念上:
- 用户创建 NetworkPolicy。
- Cilium Agent 读取相关策略与工作负载信息。
- 将策略转换为适合数据面的状态。
- 更新相关 eBPF 程序所使用的 Map 等数据。
- 数据包经过相应 Hook 时执行策略判断。
不过,网络策略的实际执行还涉及流量方向、连接状态、身份识别和特定数据面配置,不能把所有策略简化为一个固定的 IP 匹配过程。
七、Hubble:如何观察 eBPF 网络流量?
学习 eBPF 最有价值的部分之一,就是理解 Cilium 如何通过 Hubble 提供网络可观测性。
Hubble 是建立在 Cilium 之上的网络可观测性工具,可以帮助观察:
- 哪个 Pod 访问了哪个 Pod。
- 使用了什么协议和端口。
- 流量是否被允许或拒绝。
- 部分 DNS 和 HTTP 层面的请求信息。
- 某些流量为什么被网络策略拒绝。
flowchart TB
N0["Pod 网络流量"]
N1["Cilium eBPF 数据面<br/>产生网络流量与策略相关事件"]
N2["Cilium Agent / Hubble<br/>收集和提供流量事件"]
N3["Hubble CLI / Hubble UI<br/>查询流量、查看拒绝原因和调用关系"]
N0 --> N1
N1 --> N2
N2 --> N3
注意:Hubble 的观察范围取决于部署方式、功能配置和事件采集限制。它不能保证记录每一个数据包,也不能直接看到所有加密应用流量的明文内容。
八、eBPF、iptables 与 Kubernetes 网络的关系总结
| 维度 | iptables / Netfilter | eBPF |
|---|---|---|
| 配置模型 | 规则、链和匹配条件 | 可加载程序与 Map |
| 执行位置 | Netfilter 处理路径 | XDP、TC、Socket 等多个 Hook |
| Service 转发 | 传统 kube-proxy 的一种实现 | Cilium 等方案可实现 |
| 网络策略 | 网络插件可通过规则实现 | 网络插件可通过 eBPF 实现 |
| 可观测性 | 计数器、日志及相关工具 | 可结合 eBPF 事件和 Hubble |
| 主要优势 | 成熟、工具生态完善 | 可编程性与灵活的执行位置 |
| 主要挑战 | 大规模规则管理与排障复杂度 | 内核兼容性、程序与 Map 调试 |
不要认为 eBPF 完全取代了 iptables,也不要认为 eBPF 一定绕过所有 Linux 网络协议栈处理。
两者可以在同一台 Linux 主机上共存,实际走哪条路径由系统配置和流量类型决定。
九、本节总结
你现在应该能够区分:
- eBPF 程序:决定在特定内核执行点处理什么。
- Verifier:验证 eBPF 程序是否满足加载约束。
- XDP、TC、Socket Hook:对应不同的内核执行位置。
- eBPF Map:保存数据面需要的状态。
- Cilium Agent:负责维护相应的数据面配置。
- Hubble:用于观察 Cilium 管理的网络流量与策略事件。
推荐深入阅读: