阶段二 · CNI 与数据平面

eBPF 与 Cilium:下一代数据平面

一句话总结

eBPF 让我们能给 Linux 内核"动态加载小程序", 在数据包经过的最早路径上高效处理它.
Cilium 用它重写了 K8s 数据平面--尤其用一张哈希表替掉了 kube-proxy 那条随服务数量线性变长的 iptables 规则链, 顺带做到了 L7 策略和 Hubble 级别的可观测.

前置回顾

第 03 篇把 Cilium 列为"基于 eBPF 的下一代", 但没说清 eBPF 是什么, 它凭什么"下一代". 本篇补上.

要理解 Cilium 的价值, 得先看清它要解决的一个真实痛点--kube-proxy 的 iptables 模式在大规模下的瓶颈. 这也顺带回答了 Kubernetes 笔记里 Service/kube-proxy 留下的"底层怎么转发"的尾巴.

先看痛点: kube-proxy 的 iptables 为什么会拖垮大集群

回顾一下 Service 怎么工作(Kubernetes 笔记): 访问一个 Service 的 ClusterIP, kube-proxy 负责把它转发(负载均衡)到后端某个 Pod. 默认实现用的是 iptables--Linux 的包过滤框架. 问题就出在这里.

iptables 的工作方式是一条线性规则链, 包来了从上往下逐条匹配:

  1. 集群有 1 个 Service, 3 个后端 Pod → 几条规则, 匹配飞快
  2. 集群有 5000 个 Service, 每个 5 个后端 → 数万条 iptables 规则

每个数据包进来, 内核可能要从头遍历这条巨长的规则链:

  1. 转发延迟随规则数线性增长(O(n))
  2. 规则更新是全量刷新: 加一个 Service, 要重算整张表, 可能锁住数据路径数秒
  3. 一个有几千服务的大集群, kube-proxy 能成为实打实的性能瓶颈

核心矛盾: iptables 本是为防火墙设计的, 用它来做大规模服务负载均衡, 是"拿错了工具". 规则一多, 线性匹配和全量更新就成了灾难.(注: kube-proxy 还有个 IPVS 模式, 用哈希查找改善了匹配性能, 但仍受限于在 iptables/netfilter 框架内打转.)要根治, 得换一套更底层, 更高效的机制--这就是 eBPF 登场的地方.

iptables 规则链像机场只有一条安检通道, 贴了几千条"如果是 X 航班就走 Y 登机口"的纸条, 每个乘客都得从头念到尾.
eBPF 像直接给每个乘客发一张电子登机牌, 刷一下就知道去哪--用哈希查表(O(1))取代逐条念纸条(O(n)).

eBPF 是什么: 给内核动态装"小程序"

eBPF(extended Berkeley Packet Filter) 是 Linux 内核的一项革命性能力, 一句话概括: 它允许把一段小程序, 安全地, 动态地加载到内核里, 挂在内核的各种"钩子"上运行--无需改内核源码, 无需重启.

这为什么了不起? 传统上想改内核行为(比如自定义网络包处理), 要么改内核重新编译(几乎不可能), 要么写内核模块(危险, 易崩). eBPF 提供了第三条路:

eBPF 的关键特性 意义
安全 加载前有验证器(verifier) 检查, 保证程序不会让内核崩溃, 不会死循环
高效 程序在内核态直接运行(可 JIT 编译成机器码), 没有内核↔用户态切换开销
动态 随时加载/卸载, 无需重启; 挂载点遍布网络, 系统调用, 追踪等
可在最早路径处理包 能挂在网卡驱动层(XDP)等极早的位置, 包刚到就处理, 绕开冗长的内核网络栈

eBPF 不只用于网络

eBPF 是个通用的"内核可编程"框架, 网络只是它最火的应用之一. 它还广泛用于可观测性(无侵入地追踪系统调用, 抓性能数据--可观测性笔记里的很多 APM/追踪工具底层就用它)和安全(运行时监控异常行为).

可以说 eBPF 正在重塑整个 Linux 基础设施层. 但本篇聚焦它在 K8s 网络里的应用.

Cilium 如何用 eBPF 重写数据平面

Cilium 是"eBPF for Kubernetes networking"的代表. 它把 Pod 网络的转发, 负载均衡, 策略执行, 统统用 eBPF 程序实现, 挂在内核的网络钩子上. 最有代表性的是它替代 kube-proxy:

kube-proxy(iptables):
包→ [逐条遍历数万条 iptables 规则] →匹配到→ 转发到后端 Pod
└─ O(n), 服务越多越慢, 更新要全量刷表

Cilium(eBPF):
包→ [eBPF 程序查一张哈希表(Service→后端)] →O(1)直接→ 后端 Pod
└─ 与服务数量无关, 增删服务只改哈希表的一项

差别一目了然: eBPF 用哈希表查找(O(1)) 取代了 iptables 的线性遍历(O(n)), 且更新是增量的(改一项而非刷全表). 在几千服务的大集群里, 这是数量级的性能与稳定性提升. Cilium 可以完全运行在无 kube-proxy 模式下, 把这块彻底接管.

超越转发: L7 策略与身份模型

eBPF 的能力不止于"更快的转发". 因为它能在内核里理解和处理数据包的内容, Cilium 还做到了传统方案做不到的事:

  • L7 层策略: 传统 NetworkPolicy(第 05 篇)只能到 L3/4(IP + 端口), Cilium 能到 L7--比如"允许访问 /api/public 但拒绝 /api/admin""只允许 GET 不允许 DELETE". 这是应用层的精细管控.
  • 基于身份(Identity)而非 IP 的策略: Pod IP 频繁变化(Pod 是短暂的), 用 IP 写策略很脆弱. Cilium 给每组 Pod 分配一个稳定的安全身份(基于标签), 策略基于身份而非 IP--Pod 重建 IP 变了, 身份不变, 策略照常生效.

为什么基于身份是个大进步

这呼应了一个老问题: Pod IP 是短暂的(Kubernetes 笔记反复强调). 基于 IP 的防火墙在云原生里天生别扭--IP 一直在变.

Cilium 把"这个 Pod 是谁"(由标签决定的身份)和"它现在的 IP 是什么"解耦, 策略只关心身份. 这和第 05 篇零信任, 可观测性系列里"Pod 短暂, 要靠标签而非 IP"的思路一脉相承--在动态系统里, 身份比地址可靠.

Hubble: 数据平面自带可观测性

因为所有流量都流经 eBPF 程序, Cilium 顺手就能无侵入地观测它们--这就是 Hubble: 它能实时展示"哪个服务在和哪个服务通信, 哪些流量被策略放行/拒绝, L7 层的请求详情", 还能画出服务依赖拓扑图.

这正是可观测性系列的延续

回想可观测性笔记: 我们想看清"服务间流量"得费力埋点. 而 Hubble 因为站在 eBPF 数据平面这个"咽喉要道"上, 不改一行应用代码就能看到所有网络流量--这是 eBPF "在内核里观测一切"能力的红利.

它能补足可观测性三支柱里"网络层"这块常被忽视的视角. 可见网络, 安全, 可观测在 eBPF 上是融为一体的.

eBPF 不是银弹

Cilium/eBPF 强大, 但有真实门槛: 对内核版本有要求(老内核 eBPF 能力受限); 概念多, 学习曲线陡; 排障需要新的工具和知识(传统的 iptables 排查经验在这儿用不上).

对一个只有几十个服务, Flannel/Calico 跑得好好的集群, 强行上 Cilium 可能是"杀鸡用牛刀". 它的价值在大规模, 高性能要求, 需要 L7 安全与深度可观测的场景才充分兑现--技术选型永远要匹配真实需求(第 02, 03 篇反复强调的原则).

快速回顾

  • 痛点: kube-proxy 的 iptables 模式用线性规则链(O(n))做服务转发, 大规模(数千服务)时延迟高, 更新要全量刷表, 成为瓶颈--"拿防火墙工具做负载均衡"
  • eBPF: 把小程序安全, 动态, 高效地加载进内核钩子运行, 无需改内核或重启; 有验证器保证安全, 内核态运行无切换开销
  • Cilium 重写数据平面: 用 eBPF 哈希表查找(O(1), 增量更新)替代 iptables 线性遍历, 可完全替代 kube-proxy
  • 超越转发: 支持 L7 策略(到 HTTP 方法/路径), 基于身份(标签)而非 IP 的策略(应对 Pod IP 短暂)
  • Hubble: 站在 eBPF 数据平面上, 无侵入观测全部网络流量, 延续可观测性理念
  • 不是银弹: 对内核有要求, 门槛高, 适合大规模/高性能/L7 安全/深度可观测场景, 小集群不必强上

kube-proxy 不是还有 IPVS 模式吗? 那是不是就不需要 eBPF 了?

IPVS 模式确实改善了性能: 它用哈希表做查找(O(1)), 解决了 iptables 线性匹配的问题, 大集群下比 iptables 模式好很多. 但 IPVS 仍工作在内核的 netfilter 框架内, 只解决了"Service 负载均衡"这一件事.

eBPF/Cilium 的优势是更彻底, 更全面: 它不仅做 Service 转发, 还统一接管了 Pod 网络, L3-L7 策略, 可观测性, 是一整套数据平面的重写, 而非对某个点的优化. 所以即便有 IPVS, eBPF 仍代表了更前沿, 更一体化的方向.

eBPF 程序能随便写吗? 会不会被恶意利用搞崩内核?

不能随便写, 这正是 eBPF 设计的精妙处. 每段 eBPF 程序加载进内核前, 必须通过内核的验证器(verifier) 静态检查: 确保它没有无界循环(保证会终止), 不会访问非法内存, 不会让内核崩溃. 通不过验证的程序根本加载不进去.

正是这套安全保证, 才让"往内核里塞代码"这件听起来很危险的事变得可行--它是 eBPF 区别于传统内核模块(可以随意崩溃内核)的关键.

动手练习

  1. 感受 iptables 规则量: 在一个用 iptables 模式 kube-proxy 的集群上, 创建几个 Service, 然后 iptables-save | grep KUBE | wc -l 数数生成了多少规则--直观感受规则量
  2. 推算大规模瓶颈: 想象把 Service 数量乘以 100, 体会为什么"逐条遍历"会成为瓶颈; 再查阅 IPVS 模式如何用哈希改善它
  3. 部署 Cilium: 用 kind/k3d 装 Cilium(开启 kube-proxy-free 模式), 部署几个互相调用的服务
  4. 体验 Hubble: 装上 Hubble UI, 实时观察服务间流量与依赖拓扑--亲眼看到"不埋点就能看见网络流量"
  5. 写 L7 策略(进阶): 写一条 Cilium 的 L7 策略(如只允许对某服务的 GET /api/public), 验证 DELETE 或访问其他路径被拒绝--体会传统 L3/4 NetworkPolicy 做不到的精细度