在 Kubernetes 集群里,Service 到 Pod 的流量转发通常由 kube-proxy 负责,但越来越多的发行版开始用 eBPF 实现数据路径,甚至直接替代 kube-proxy。要理解这种变化,必须从内核转发机制和规则同步方式入手。

kube-proxy 的核心任务是把访问 Service 的流量转换成对具体 Pod 的访问,而 eBPF 数据路径则直接在更靠近网卡的内核上下文中完成同样的工作。两者的差异不仅体现在性能数据上,也直接影响到大规模集群的稳定性和可维护性。
kube-proxy 的规则模型与性能代价
kube-proxy 早期使用 userspace 模式,所有流量都要先进入用户态再由代理进程转发,效率很低。现代版本默认使用 iptables 模式,通过在每个节点上刷写大量 netfilter 规则来实现 Service 的 DNAT 和负载均衡。kube-proxy 监听 API Server 中的 Service 和 Endpoints 变化,然后调用 iptables-restore 将规则同步到内核。当集群中 Service 数量达到数千个时,节点上的规则数量会迅速膨胀,因为每个 Service 可能对应多条 KUBE-SERVICES 链和 KUBE-SVC-xxx 链。
以 iptables 模式为例,一个 ClusterIP 类型的 Service 通常会生成类似下面的规则,用于在多个后端 Pod 之间按概率选择目标。这些规则在内核网络栈的 PREROUTING 和 OUTPUT 阶段被执行。
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m tcp --dport 80 -j KUBE-SVC-XXXX -A KUBE-SVC-XXXX -m statistic --mode random --probability 0.33333 -j KUBE-SEP-AAAA -A KUBE-SVC-XXXX -m statistic --mode random --probability 0.50000 -j KUBE-SEP-BBBB -A KUBE-SVC-XXXX -j KUBE-SEP-CCCC
iptables 的问题不只是规则数量,还在于规则匹配是线性遍历的。每个数据包进入 netfilter 后,要从链的第一条规则开始逐条比较,直到命中。虽然内核有优化,但在大规模场景中,几千条甚至几万条规则会导致明显的尾部延迟。另外,当 Service 或 Endpoints 发生变化时,kube-proxy 需要重新生成并全量刷新规则,这个过程中会持有锁,进一步影响数据面稳定性。
IPVS 模式在一定程度上缓解了线性匹配问题,它使用内核 IPVS 模块维护一张哈希表,通过目的地址和端口直接查找后端,转发性能比 iptables 高。但 IPVS 仍然依赖 kube-proxy 这个用户态组件去同步规则,并且与 netfilter 的 conntrack 模块配合时,会话状态管理仍有额外开销。此外,IPVS 模式对网络策略、会话保持等高级功能的支持并不完整,往往还需要配合其他 CNI 插件。
eBPF 数据路径的挂载点与转发逻辑
eBPF 数据路径不再通过修改 netfilter 规则来工作,而是将小型程序加载到内核的特定挂载点上,例如 TC 层的 clsact、套接字层的 sock_ops 或者 XDP 层。Cilium 是最典型的实现,它在每个节点上运行一个 cilium-agent,负责把 Service 和 Endpoints 信息编译成 BPF map,然后将 eBPF 程序附加到 Pod 虚拟网卡的 ingress 和 egress 路径上。
当数据包从 Pod 发出时,eBPF 程序会直接读取 BPF map 中的服务映射表,查找到目标 ClusterIP 和端口对应的后端列表,并完成 DNAT 或直接封装。由于决策在靠近网卡的内核上下文完成,数据包不需要排队进入用户态,也不需要遍历几百条 iptables 规则。负载均衡算法可以在 BPF map 中预计算权重,程序通过一次哈希或随机选择后端,时间复杂度接近 O(1)。
下面是一段简化的 eBPF 程序片段,展示如何在 TC 层处理服务转发。它假设 BPF map 中保存了后端地址和端口,并根据五元组做负载均衡。
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
struct backend_info {
__u32 ip;
__u16 port;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, __u32);
__type(value, struct backend_info);
} lb_map SEC(".maps");
SEC("classifier")
int svc_forward(struct __sk_buff *skb)
{
__u32 key = 1;
struct backend_info *backend = bpf_map_lookup_elem(&lb_map, &key);
if (!backend)
return TC_ACT_OK;
// 这里省略对 skb 的解析、DNAT 和校验和更新
// 实际实现会通过 bpf_skb_store_bytes 修改目标地址
return TC_ACT_OK;
}
与 kube-proxy 相比,eBPF 数据路径最大的优势是绕过了 netfilter 的完整协议栈处理。kube-proxy 生成的 DNAT 规则依赖 conntrack 来保证反向流量回到同一个后端,而 eBPF 可以在 BPF map 中直接记录连接状态,或者使用更轻量的会话表。这样既减少了 conntrack 表的压力,也避免了因为 conntrack 满导致的新连接丢包问题。
另一个关键差异是规则更新方式。kube-proxy 需要全量刷写 iptables 或 IPVS 规则,eBPF 则通过 BPF map 的原子更新来修改服务端点。cilium-agent 收到 Kubernetes 事件后,只需要更新 map 中的键值对,数据面程序下一次查询时就会自动使用新地址。这种增量更新对长连接和突发流量更加友好,不会出现规则同步期间的数据面短暂不可用。
功能覆盖与可观测性对比
除了转发性能,功能覆盖也是选择数据路径的重要依据。kube-proxy 本身只负责 Service 的负载均衡和会话保持,网络策略通常需要额外的 CNI 插件通过 iptables 或 eBPF 实现。而 eBPF 数据路径可以与网络策略、服务网格、加密传输等能力共享同一套 map 和钩子,减少不同组件之间的规则冲突。
下面的表格从几个核心维度对比了两者的差异。
| 对比维度 | kube-proxy | eBPF 数据路径 |
|---|---|---|
| 转发机制 | iptables 线性规则或 IPVS 哈希表 | BPF 程序在 TC/socket 层直接处理 |
| 规则更新 | 全量刷新,存在锁竞争 | BPF map 增量更新 |
| 连接跟踪 | 依赖 conntrack 模块 | 可自定义会话表或绕过 conntrack |
| 网络策略 | 需要额外组件或 CNI 配合 | 可与 eBPF 策略统一管理 |
| 可观测性 | 依赖 iptables 计数器或 IPVS 统计 | 原生支持细粒度指标和事件 |
| 调试难度 | iptables 规则直观但量大难读 | 需要理解 BPF 程序与内核版本 |
可观测性方面,kube-proxy 提供的指标相对有限,通常只有规则同步延迟和错误计数。而 eBPF 数据路径可以在每个数据包经过时记录来源 Pod、目标 Service、后端选择结果和转发时延,配合 ring buffer 或 perf event 输出到用户态。Cilium 的 Hubble 组件就是基于这一能力实现服务依赖图和流量监控,这在排查微服务问题时比单纯查看 iptables 计数器要高效得多。
不过,eBPF 对内核版本和内核编译选项有要求。较新的 Linux 内核才能完整支持 BPF-to-BPF 调用、CO-RE 等特性。如果集群节点运行的是较旧的内核,或者某些云厂商对 eBPF 功能有限制,那么切换到 eBPF 数据路径前需要先评估兼容性。而 kube-proxy 的 iptables 模式几乎可以在任何 Linux 发行版上运行,这也是它长期作为默认组件的原因之一。
替换 kube-proxy 的实践路径
如果决定在集群中启用 eBPF 数据路径,通常不需要先卸载 kube-proxy,而是采用渐进式切换。以 Cilium 为例,可以通过 Helm 参数设置 kubeProxyReplacement=strict 来完全禁用 kube-proxy,或者使用 probe 模式自动检测节点能力并回退。完全替换后,Service 的 ClusterIP 转发、NodePort 访问以及外部流量策略都由 eBPF 程序处理。
下面是一个 Cilium 的配置片段,用于在安装时开启 kube-proxy 替换功能。
apiVersion: v1 kind: ConfigMap metadata: name: cilium-config namespace: kube-system data: kube-proxy-replacement: "strict" enable-bpf-masquerade: "true" enable-host-reachable-services: "true"
开启替换后,需要验证节点上的 iptables 规则是否已经不再包含 KUBE-SERVICES 链。可以在任意节点执行 iptables-save | grep KUBE-SVC,如果没有输出,说明 kube-proxy 规则已被移除。同时要检查 Service 的 ClusterIP 和 NodePort 访问是否正常,尤其是使用了外部流量策略或会话保持的服务。
从实际运行效果看,在几千个 Service 的大规模集群中,eBPF 数据路径通常能降低节点 CPU 使用率,因为不再需要频繁调用 iptables-restore。长连接在服务端点变化时也更加稳定,因为 BPF map 更新不会中断现有连接,而 iptables 全量刷新可能导致短暂的规则丢失。但这并不意味着 eBPF 在所有场景下都优于 kube-proxy。如果集群规模很小,Service 只有几十个,iptables 或 IPVS 的性能已经足够,引入 eBPF 反而增加了对内核和学习成本的要求。
总体而言,选择哪种数据路径取决于集群规模、内核版本、团队对 eBPF 的熟悉程度以及是否需要统一的可观测性平台。对于新建的、使用较新内核的生产集群,可以考虑直接采用 eBPF 数据路径;对于已有的、运行稳定的传统集群,可以先将 IPVS 作为过渡,再评估切换到 eBPF 的收益。
eBPFkube-proxyKubernetes 网络修改时间:2026-10-04 02:56:06