当 K8s 遇上 Cilium:生产环境下替换 kube-proxy 的避坑指南与性能调优
在 Kubernetes 集群规模达到数百个节点、Service 数量突破万级时,传统的 kube-proxy(无论是 iptables 还是 IPVS 模式)都会遭遇明显的性能瓶颈。iptables 的 $O(N)$ 逐条匹配在大规模集群下会导致 CPU 消耗雪崩;而 IPVS 虽然是 $O(1)$,但在大规模动态更新时,内核锁竞争依然会导致网络延迟抖动。
作为云原生网络的新一代标准,Cilium 基于 eBPF(Extended Berkeley Packet Filter)技术,提供了完全替代 kube-proxy 的能力。它通过在套接字层(Socket Layer)和网络设备驱动层直接劫持和分发流量,实现了真正的 $O(1)$ 路由转发,并绕过了极其沉重的内核 TCP/IP 协议栈。
然而,在生产环境将 kube-proxy 彻底替换为 Cilium,并非简单的 helm install 就能一键搞定。本文将基于真实的生产实践,分享在替换过程中必须踩过的“坑”以及深度的性能调优方案。
一、 核心架构:为什么 eBPF 比 IPVS 快?
在着手替换前,我们需要理清 Cilium 替换 kube-proxy 后的流量路径差异:
- 传统 IPVS 模式:数据包到达网卡 -> 经过内核网卡驱动 -> 进入 IP 协议栈 -> Netfilter 钩子 -> IPVS 规则匹配 -> 转换目标 IP (DNAT) -> 重新封装数据包 -> 发送。
- Cilium eBPF 模式(Socket LB):在应用发起
connect()系统调用时,eBPF 程序在 Socket 层 直接将目标 Service IP 替换为后端 Pod IP。数据包在诞生之初就已经知道了终点,无需在网络层进行任何 NAT 转换,直接走极简路径发送。
这种“降维打击”式的优化,使得 Pod 间通信的延迟几乎逼近裸机。
二、 生产环境的“避坑指南”
在生产环境中进行平滑替换,需要应对复杂的网络拓扑和复杂的内核环境。以下是五个高频致命红线:
1. 内核版本的“硬骨头”
虽然 Cilium 官方声称支持 4.19+ 内核,但如果你想完全替换 kube-proxy(即设置 kubeProxyReplacement=true),强烈建议使用 Linux Kernel 5.4 及以上版本,推荐使用 5.10+ 或 6.x LTS。
- 4.19 内核缺陷:在 4.19 内核下,Cilium 无法启用
bpf_host_routing(BPF 主机路由)。流量依然需要进入主机的 Netfilter 框架,导致性能提升减半。 - 5.10+ 优势:支持完整的 Socket LB、GSO(Generic Segmentation Offload)以及更完善的 eBPF 尾部调用(Tail Calls)优化。
2. 悬挂的 MTU 陷阱(最大传输单元)
这是最容易导致生产故障的配置项。如果你的物理网卡 MTU 是 1500:
- 如果使用 Overlay 模式(VXLAN 或 Geneve 隧道),Cilium 会在原始数据包外层再封装一层 UDP。VXLAN 头部占用 50 字节,因此 Pod 内的 MTU 必须手动设置为
1450。 - 故障现象:如果未正确设置 MTU,小包(如 Ping、HTTP Get 握手)正常,但大包(如传输大文件、API 返回大数据)会因为分片被丢弃,导致 TCP 连接无故挂起(Hang 住)。
- 避坑指南:在 Helm 部署时,务必显式指定 MTU:
mtu: 1450 # VXLAN 模式下设为 1450,直路由模式下可保持 1500
3. 消失的 NodePort 与 L4 负载均衡健康检查
很多企业在 Kubernetes 外部挂载了物理负载均衡器(如 F5、LVS)或云厂商的 SLB,通过 NodePort 探测节点健康状况。
- 在完全替换
kube-proxy后,Cilium 默认的 NodePort 实现依赖于 eBPF 的 BPF NodePort 功能。 - 陷阱:如果你的外部负载均衡器发送的探测包源 IP 与 K8s 节点不在同一子网,且未开启 SNAT,Cilium 默认的 eBPF 路由可能会因为“反向路径过滤(rp_filter)”将回包丢弃。
- 解决方法:
并在 Cilium 配置中开启:# 调整内核反向路径过滤参数 sysctl -w net.ipv4.conf.all.rp_filter=0 sysctl -w net.ipv4.conf.default.rp_filter=0nodePort: enabled: true bpf: masquerade: true # 开启 eBPF 伪装,确保跨网段回包正常
4. 彻底瘫痪的 hostPort
一些特定的应用(如 Ingress-Controller 或 CoreDNS)会使用 hostPort 将容器端口直接映射到宿主机。
- 在没有
kube-proxy的情况下,Cilium 默认不会处理hostPort。 - 解决方法:必须在 Helm 部署中显式启用 CNI Portmap 插件支持:
cni: exclusive: true portmap: enabled: true
三、 生产环境下的极限性能调优
成功替换仅仅是第一步。要榨干 eBPF 的网络性能,必须进行深度调优。以下是经过生产检验的调优参数组合:
1. 开启 BPF 主机路由 (BPF Host Routing)
默认情况下,即使不用 kube-proxy,数据包在穿过 veth pair 到达宿主机网卡时,依然会经过内核的主机网络协议栈。通过开启 bpf_host_routing,Cilium 可以让数据包绕过所有的 iptables 规则,直接在内核中实现从 veth pair 到物理网卡的二层转发。
# Helm values.yaml
bpf:
masquerade: true
tunnel: disabled # 必须运行在 Direct Routing(直路由)模式下
autoDirectNodeRoutes: true
注意:这要求集群节点处于同一个二层网络中,或者通过 BGP(如 Cilium BGP Control Plane)实现了路由可达。
2. 启用 XDP(eXpress Data Path)加速
XDP 允许 BPF 程序在网卡驱动接收到数据包的最早期(还未分配 sk_buff 结构体,未进入内核内存空间)就进行处理。对于抵御 Syn-Flood 等 DDoS 攻击,或者极速转发 NodePort 流量,具有质的提升。
- 前置条件:网卡驱动必须支持 XDP(现代 Intel、Mellanox 网卡均支持 native XDP)。
- 优化配置:
# 开启 Cilium 的 XDP 加速功能 loadBalancer: mode: dsr # 采用 Direct Server Return 模式,回包不经过 LB 节点 acceleration: native # 开启 native XDP 加速 devices: '{eth0}' # 绑定你的物理网卡
3. 启用 Socket 级别负载均衡 (Socket LB)
在 Pod-to-Service 场景中,通过直接在套接字系统调用级别(如 connect, sendmsg)拦截,将 Service IP 直接在用户态空间转化为 Backend IP。
socketLB:
enabled: true
hostNamespaceOnly: false # 允许在 Host 命名空间和 Pod 命名空间同时开启 Socket LB
开启后,本地访问 Service 几乎没有任何额外的 TCP 握手开销。
4. 调整垃圾回收(Garbage Collection)与连接跟踪表
在大并发场景下,Cilium 的 eBPF 连接跟踪表(CT Map)如果爆满,会导致新连接直接被 Drop。
- 查看当前 CT Map 使用状态:
cilium bpf ct list global - 优化调整大并发下的 Map 容量:
bpf: ctMax: 1048576 # 默认值为 262144,高并发集群建议调大到 100万+ ctMaxTCP: 524288
5. 算法调优:引入 Maglev 一致性哈希
默认情况下,Cilium 使用随机(Random)算法选择后端 Pod。对于需要会话保持(Session Affinity)或者希望提高本地缓存命中率的业务,可以将其替换为 Google 提出的 Maglev 一致性哈希算法。
loadBalancer:
algorithm: maglev
maglev:
tableSize: 16381 # 推荐使用素数
四、 生产迁移与平滑替换方案
在已有的生产集群中,直接卸载 kube-proxy 并安装 Cilium 是极其危险的。推荐采用渐进式热替换方案:
步骤 1:准备内核与配置环境
在所有节点上挂载 BPF 文件系统(现代系统一般默认已挂载):
mount bpffs /sys/fs/bpf -t bpf
步骤 2:安装 Cilium(保留 kube-proxy 兼容模式)
首先安装 Cilium,但保持 kubeProxyReplacement 为 partial 或 disabled。此时 Cilium 只充当普通的 CNI,不抢夺 kube-proxy 的控制权。
helm install cilium cilium/cilium --version 1.14.x \
--set kubeProxyReplacement=disabled \
--set k8sServiceHost=YOUR_API_SERVER_IP \
--set k8sServicePort=6443
注意:必须指定 k8sServiceHost 和 k8sServicePort,因为一旦后续干掉 kube-proxy,Cilium 必须知道如何绕过 Service 直接与 ApiServer 通信。
步骤 3:灰度升级 Cilium 开启完全替换
在确认 Cilium 工作正常后,修改 Helm 配置,将替换模式调整为 true:
helm upgrade cilium cilium/cilium \
--reuse-values \
--set kubeProxyReplacement=true
此时,Cilium 会自动接管已经存在的 Service 路由,但原有的 kube-proxy 依然在运行(两套规则并存,eBPF 规则优先级更高)。
步骤 4:优雅下线 kube-proxy
- 修改
kube-proxy的 DaemonSet,将其节点亲和性(NodeAffinity)设置为一个不存在的标签,让其在所有节点上平滑收缩(Scale to 0)。 - 清理各节点上残留的 iptables/IPVS 规则(重要!防止残留规则干扰干扰物理网卡):
# 清理 IPVS 规则 ipvsadm -C # 清理 iptables 规则(视具体情况,可重启节点以获取最干净的网络空间) iptables -t nat -F iptables -t filter -F
五、 核心排错工具箱
当遇到网络不通时,传统的 tcpdump 在 eBPF 面前会显得有些力不从心,因为很多流量在进入网卡前就已经被 BPF 程序丢弃或重定向了。我们需要掌握以下 BPF 专属排错工具:
- 检查 Cilium 状态与 eBPF 模式是否完全启用:
cilium status --verbose # 确认 Kube-Proxy Replacement 状态为: Strict 或 True - 监控 eBPF 数据包丢弃事件(定位网络不通的神器):
如果发生丢包,该命令会实时输出丢包原因(如cilium monitor --type dropPolicy denied、Invalid packet、CT map insertion failed)。 - 查看 eBPF 维护的 Service 负载均衡内核 Map:
此命令可以清晰看到 ClusterIP 到 Pod IP 的真实映射关系,相当于 eBPF 版的cilium bpf lb listipvsadm -L。
总结
Cilium 替换 kube-proxy 不仅仅是一次技术栈的升级,更是对 Kubernetes 底层网络基础设施的一次重构。通过绕过传统的内核网络协议栈,集群的网络吞吐量、延迟以及大连接下的稳定性都将获得质的飞跃。
在实施过程中,务必重视内核版本的匹配与 MTU 的设定,并在上线前进行充分的压测。唯有如此,才能在生产环境中安全、平滑地享受到 eBPF 技术带来的网络红利。