Kubernetes 网络模型深度剖析:CNI、Service Mesh 与 eBPF
把 K8s 的 iptables、IPVS、CNI、Sidecar、eBPF 拼成一张完整的图。理解它们各自的代价与延迟来源,是解决「Pod 之间为什么总有几毫秒抖动」问题的前置功课。
K8s 网络模型的两条铁律
Kubernetes 的网络模型看上去复杂,核心只有两条铁律:
- Pod - Pod 直连:所有 Pod 在不使用 NAT 的前提下彼此可达。
- 节点 - 节点直连:所有节点也可以和所有 Pod 直连。
这两条「看似简单」的要求带来了非常多的实现复杂度。CNI、Service Mesh、kube-proxy、eBPF 本质都是为这两条铁律服务。
CNI:Pod 网络的「网线」
CNI(Container Network Interface)解决的是「Pod 启动时拿到 IP、得到路由、与其他 Pod 通」。主流实现包括 Calico、Cilium、Flannel、Antrea。
它们的差别主要在:
| 实现 | 封装 | 策略 | 性能 |
|---|---|---|---|
| Flannel | VXLAN | 不支持 | 中 |
| Calico | BGP | 支持 | 高 |
| Cilium | eBPF | 支持 | 最高 |
| Antrea | OVS | 支持 | 中 |
选 CNI 之前先想清楚:你是否需要 NetworkPolicy?是否需要高并发?是否需要链路加密?
kube-proxy:从 iptables 到 IPVS 到 eBPF
Service 是 K8s 给一组 Pod 提供「稳定入口」的能力。它的工作机制背后是 kube-proxy:
- iptables 模式:为每条 service 写一组 iptables 规则。简单但慢,集群大时 P99 抖动明显。
- IPVS 模式:用内核虚拟服务器 hash 表查。后端规模上万仍稳定。
- eBPF 模式:Cilium 提供,绕过 iptables,性能与可见性最好。
# 检查 kube-proxy 模式
kubectl get kube-proxy -o yaml | grep mode
# 切换为 IPVS
kubectl -n kube-system patch ds kube-proxy --type merge \
-p '{"spec":{"template":{"spec":{"containers":[{"name":"kube-proxy","args":["--proxy-mode=ipvs"]}]}}}}'
Service Mesh:把网络功能塞进 Sidecar
Service Mesh(Istio / Linkerd / Kuma)通过 Sidecar 把「超时、重试、熔断、加密、可观测」做到 Pod 内部。它的代价是「多一跳」,但收益是「业务代码完全解放」。
Sidecar 模式让请求多了 1 个本机内部跳转。每次跳转都增加 0.2–0.5ms 的延迟。当一个链路有 8 个微服务时,单纯 Mesh 就会增加 1.6–4ms。
这种代价对大多数业务是值得的,但对极致低延迟(如游戏后端、高频交易)就要谨慎评估。
eBPF:它替代了什么、它提供了什么
eBPF 允许在内核中运行沙箱程序,而无需修改内核源码或加载内核模块。它在 K8s 网络里最常见的应用是:
- 替代 kube-proxy:用 eBPF 哈希表做 service 转发。
- 替代 CNI 部分功能:Cilium 把网络策略、加密、负载均衡全部用 eBPF 实现。
- 可观测性:Hubble 提供细粒度的网络流观测。
我们做过的对比:Cilium eBPF 模式下,集群内 P99 抖动从 3ms 降到 0.8ms。CPU 也明显降低。
Mesh or eBPF?二选一还是共存?
这两年最常见的争论是「eBPF 是否会取代 Service Mesh」。我的看法是:
- eBPF 解决的是「网络转发」与「基础策略」。
- Mesh 解决的是「应用层路由、灰度、流量镜像、精细治理」。
两者解决的问题域不一样。eBPF 会吃掉 Mesh 在「网络层」的需求,但应用层的精细能力 Mesh 仍然占优。
当下最稳的实践是:Cilium 做 CNI + 策略;Linkerd 或 Istio 做细粒度流量治理。
DNS:被忽视的抖动源
K8s 内 Pod 之间通信经常遇到「奇怪的几百毫秒延迟」。很多时候凶手是 CoreDNS:
- Service 域名解析需要跨域查询。
- 外部域名查询可能被 NAT 拦截。
- 上游 DNS 限速会影响集群。
解决方案:在 NodeLocal DNSCache 部署本地缓存,把跨节点 DNS 查询转为本地命中。这是肉眼可见的延迟下降。
MTU 与分片:那些把链路毁掉的细节
在 VXLAN 模式下,每个 Pod 包被封装成 UDP 后总长度变大。如果 MTU 设置不当,会被中间网络分片。分片带来 100ms+ 抖动。
具体建议:
- VLAN 环境下 MTU 设为 1500。
- VXLAN 设为 1450 或更小。
- 用
ping -M do -s 1472验证不分片上限。
真实案例:把 Pod 间延迟从 4ms 压到 0.8ms
某次我们遇到「Pod 间通信尾巴抖动 4ms」,用两周排查。问题定位在三个叠加点:
- kube-proxy 是 iptables 模式,几千条 Service 规则慢路径。
- Veth pair 没启用 checksum offload。
- CoreDNS 跨节点查询。
改造方案:
- kube-proxy 切 IPVS。
- 网卡 GRO/GSO 启用。
- NodeLocal DNSCache。
- Service Mesh Sidecar 切到 Ambient Mesh(去除本机跳转)。
改造后 P99 从 4ms 降到 0.8ms。这是把 K8s 网络模型「拆开看」才能换来的收益。
给团队的几条建议
- 先理解 K8s 网络模型两条铁律,再选 CNI。
- kube-proxy 模式下首选 IPVS。
- eBPF 是趋势,但 Mesh 仍有不可替代的能力。
- DNS 与 MTU 都是「看不见的抖动源」,必查。
K8s 网络不是一层协议。它是若干层叠加,每一层都有自己的代价。我们不能期待「换组件」解决一切,但理解结构能让我们「不再迷信任何单点」。