WEBKT

当 K8s 遇上 Cilium:生产环境下替换 kube-proxy 的避坑指南与性能调优

7 0 0 0

在 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)”将回包丢弃。
  • 解决方法
    # 调整内核反向路径过滤参数
    sysctl -w net.ipv4.conf.all.rp_filter=0
    sysctl -w net.ipv4.conf.default.rp_filter=0
    
    并在 Cilium 配置中开启:
    nodePort:
      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,但保持 kubeProxyReplacementpartialdisabled。此时 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

注意:必须指定 k8sServiceHostk8sServicePort,因为一旦后续干掉 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

  1. 修改 kube-proxy 的 DaemonSet,将其节点亲和性(NodeAffinity)设置为一个不存在的标签,让其在所有节点上平滑收缩(Scale to 0)。
  2. 清理各节点上残留的 iptables/IPVS 规则(重要!防止残留规则干扰干扰物理网卡):
    # 清理 IPVS 规则
    ipvsadm -C
    # 清理 iptables 规则(视具体情况,可重启节点以获取最干净的网络空间)
    iptables -t nat -F
    iptables -t filter -F
    

五、 核心排错工具箱

当遇到网络不通时,传统的 tcpdump 在 eBPF 面前会显得有些力不从心,因为很多流量在进入网卡前就已经被 BPF 程序丢弃或重定向了。我们需要掌握以下 BPF 专属排错工具:

  1. 检查 Cilium 状态与 eBPF 模式是否完全启用
    cilium status --verbose
    # 确认 Kube-Proxy Replacement 状态为: Strict 或 True
    
  2. 监控 eBPF 数据包丢弃事件(定位网络不通的神器)
    cilium monitor --type drop
    
    如果发生丢包,该命令会实时输出丢包原因(如 Policy deniedInvalid packetCT map insertion failed)。
  3. 查看 eBPF 维护的 Service 负载均衡内核 Map
    cilium bpf lb list
    
    此命令可以清晰看到 ClusterIP 到 Pod IP 的真实映射关系,相当于 eBPF 版的 ipvsadm -L

总结

Cilium 替换 kube-proxy 不仅仅是一次技术栈的升级,更是对 Kubernetes 底层网络基础设施的一次重构。通过绕过传统的内核网络协议栈,集群的网络吞吐量、延迟以及大连接下的稳定性都将获得质的飞跃。

在实施过程中,务必重视内核版本的匹配与 MTU 的设定,并在上线前进行充分的压测。唯有如此,才能在生产环境中安全、平滑地享受到 eBPF 技术带来的网络红利。

云原生践行者 KubernetesCiliumeBPF

评论点评