kube-proxy 的 NFTables 模式

Kubernetes 1.29 引入了一种新的 Alpha 特性:kube-proxy 的 nftables 模式。 目前该模式处于 Beta 阶段,并预计将在 1.33 版本中达到一般可用(GA)状态。 新模式解决了 iptables 模式长期存在的性能问题,建议所有运行在较新内核版本系统上的用户尝试使用。 出于兼容性原因,即使 nftables 成为 GA 功能,iptables 仍将是默认模式。

为什么选择 nftables?第一部分:数据平面延迟

iptables API 是被设计用于实现简单的防火墙功能,在扩展到支持大型 Kubernetes 集群中的 Service 代理时存在局限性,尤其是在包含数万个 Service 的集群中。

通常,kube-proxy 在 iptables 模式下生成的规则集中的 iptables 规则数量与 Service 数量和总端点数量的总和成正比。 特别是,在规则集的顶层,针对数据包可能指向的每个可能的 Service IP(以及端口), 都有一条规则用于测试。

# 如果数据包的目标地址是 172.30.0.41:80,则跳转到 KUBE-SVC-XPGD46QRK7WJZT7O 链进行进一步处理
-A KUBE-SERVICES -m comment --comment "namespace1/service1:p80 cluster IP" -m tcp -p tcp -d 172.30.0.41 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O

# 如果数据包的目标地址是 172.30.0.42:443,则...
-A KUBE-SERVICES -m comment --comment "namespace2/service2:p443 cluster IP" -m tcp -p tcp -d 172.30.0.42 --dport 443 -j KUBE-SVC-GNZBNJ2PO5MGZ6GT

# 等等...
-A KUBE-SERVICES -m comment --comment "namespace3/service3:p80 cluster IP" -m tcp -p tcp -d 172.30.0.43 --dport 80 -j KUBE-SVC-X27LE4BHSL4DOUIK

这意味着当数据包到达时,内核检查该数据包与所有 Service 规则所需的时间是 O(n), 其中 n 为 Service 的数量。随着 Service 数量的增加,新连接的第一个数据包的平均延迟和最坏情况下的延迟都会增加 (最佳情况、平均情况和最坏情况之间的差异主要取决于某个 Service IP 地址在 KUBE-SERVICES 链中出现的顺序是靠前还是靠后)。

kube-proxy iptables 在不同规模集群中各百分位数下的第一个数据包延迟

相比之下,使用 nftables,编写此类规则集的常规方法是使用一个单一规则, 并通过"判决映射"(verdict map)来完成分发:

table ip kube-proxy {

  # service-ips 判决映射指示了对每个匹配数据包应采取的操作。
  map service-ips {
    type ipv4_addr . inet_proto . inet_service : verdict
    comment "ClusterIP、ExternalIP 和 LoadBalancer IP 流量"
    elements = { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-namespace1/service1/tcp/p80,
                 172.30.0.42 . tcp . 443 : goto service-42NFTM6N-namespace2/service2/tcp/p443,
                 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-namespace3/service3/tcp/p80,
                 ... }
    }

  # 现在我们只需要一条规则来处理所有与映射中元素匹配的数据包。
  # (此规则表示:"根据目标 IP 地址、第 4 层协议和目标端口构建一个元组;
  # 在 'service-ips' 中查找该元组;如果找到匹配项,则执行与之关联的判定。")
  chain services {
    ip daddr . meta l4proto . th dport vmap @service-ips
  }

  ...
}

由于只有一条规则,并且映射查找的时间复杂度大约为 O(1),因此数据包处理时间几乎与集群规模无关, 并且最佳、平均和最坏情况下的表现非常接近:

kube-proxy nftables 在不同规模集群中各百分位数下的第一个数据包延迟

但请注意图表中 iptables 和 nftables 之间在纵轴上的巨大差异! 在包含 5000 和 10,000 个 Service 的集群中,nftables 的 p50(平均)延迟与 iptables 的 p01(接近最佳情况)延迟大致相同。 在包含 30,000 个 Service 的集群中,nftables 的 p99(接近最坏情况)延迟比 iptables 的 p01 延迟快了几微秒! 以下是两组数据的对比图,但你可能需要仔细观察才能看到 nftables 的结果!

kube-proxy iptables 与 nftables 在不同规模集群中各百分位数下的第一个数据包延迟对比

为什么选择 nftables?第二部分:控制平面延迟

虽然在大型集群中数据平面延迟的改进非常显著,但 iptables 模式的 kube-proxy 还存在另一个问题, 这往往使得用户无法将集群扩展到较大规模:那就是当 Service 及其端点发生变化时,kube-proxy 更新 iptables 规则所需的时间。

对于 iptables 和 nftables,规则集的整体大小(实际规则加上相关数据)与 Service 及其端点的总数呈 O(n) 关系。原来,iptables 后端在每次更新时都会重写所有规则, 当集群中存在数万个 Service 时,这可能导致规则数量增长至数十万条 iptables 规则。 从 Kubernetes 1.26 开始,我们开始优化 kube-proxy,使其能够在每次更新时跳过对大多数未更改规则的更新, 但由于 iptables-restore API 的限制,仍然需要发送与 Service 数量呈 O(n) 比例的更新(尽管常数因子比以前明显减小)。即使进行了这些优化,有时仍需使用 kube-proxy 的 minSyncPeriod 配置选项,以确保它不会每秒钟都在尝试推送 iptables 更新。

nftables API 支持更为增量化的更新,当以 nftables 模式运行的 kube-proxy 执行更新时, 更新的规模仅与自上次同步以来发生变化的 Service 和端点数量呈 O(n) 关系,而与总的 Service 和端点数量无关。 此外,由于 nftables API 允许每个使用 nftables 的组件拥有自己的私有表,因此不会像 iptables 那样在组件之间产生全局锁竞争。结果是,kube-proxy 在 nftables 模式下的更新可以比 iptables 模式下高效得多。

(不幸的是,这部分我没有酷炫的图表。)

不选择 nftables 的理由有哪些?

尽管如此,仍有几个原因可能让你目前不希望立即使用 nftables 后端。

首先,该代码仍然相对较新。虽然它拥有大量的单元测试,在我们的 CI 系统中表现正确, 并且已经在现实世界中被多个用户使用,但其实际使用量远远不及 iptables 后端, 因此我们无法保证它同样稳定且无缺陷。

其次,nftables 模式无法在较旧的 Linux 发行版上工作;目前它需要 5.13 或更高版本的内核。 此外,由于早期版本的 nft 命令行工具存在缺陷,不应在运行旧版本(早于 1.0.0) nft 的节点主机文件系统中上以 nftables 模式运行 kube-proxy(否则 kube-proxy 对 nftables 的使用可能会影响系统上其他程序对 nftables 的使用)。

第三,你的集群中可能还存在其他网络组件,例如 Pod 网络或 NetworkPolicy 实现, 这些组件可能尚不支持以 nftables 模式运行的 kube-proxy。你应查阅相关组件的文档(或论坛、问题跟踪系统等), 以确认它们是否与 nftables 模式存在兼容性问题。(在许多情况下,它们并不会受到影响; 只要它们不尝试直接操作或覆盖 kube-proxy 的 iptables 规则,就不在乎 kube-proxy 使用的是 iptables 还是 nftables。) 此外,相较于 iptables 模式下,尚未更新的可观测性和监控工具在 nftables 模式下可能会为 kube-proxy 提供更少的数据。

最后,以 nftables 模式运行的 kube-proxy 有意不与以 iptables 模式运行的 kube-proxy 完全兼容。 有一些较旧的 kube-proxy 功能,默认行为不如我们期望的那样安全、高效或直观,但我们认为更改默认行为会导致兼容性问题。 由于 nftables 模式是可选的,这为我们提供了一个机会,在不影响期望稳定性的用户的情况下修复这些不良默认设置。 (特别是,在 nftables 模式下,NodePort 类型的 Service 现在仅在其节点的默认 IP 上可访问,而在 iptables 模式下, 它们在所有 IP 上均可访问,包括 127.0.0.1。)kube-proxy 文档 提供了更多关于此方面的信息, 包括如何通过查看某些指标来判断你是否依赖于任何已更改的特性,以及有哪些配置选项可用于实现更向后兼容的行为。

尝试使用 nftables 模式

准备尝试了吗?在 Kubernetes 1.31 及更高版本中,你只需将 --proxy-mode nftables 参数传递给 kube-proxy(或在 kube-proxy 配置文件中设置 mode: nftables)。

如果你使用 kubeadm 部署集群,kubeadm 文档解释了如何向 kubeadm init 传递 KubeProxyConfiguration。 你还可以通过 kind 部署基于 nftables 的集群

你还可以通过更新 kube-proxy 配置并重启 kube-proxy Pod,将现有集群从 iptables(或 ipvs)模式转换为 nftables 模式。(无需重启节点: 在以 nftables 模式重新启动时,kube-proxy 会删除现有的所有 iptables 或 ipvs 规则; 同样,如果你之后切换回 iptables 或 ipvs 模式,它将删除现有的所有 nftables 规则。)

未来计划

如上所述,虽然 nftables 现在是的 kube-proxy 的最佳模式,但它还不是默认模式, 我们目前还没有更改这一设置的计划。我们将继续长期支持 iptables 模式。

kube-proxy 的 IPVS 模式的未来则不太确定:它相对于 iptables 的主要优势在于速度更快, 但 IPVS 的架构和 API 在某些方面对 kube-proxy 来说不够理想(例如,kube-ipvs0 设备需要被分配所有 Service IP 地址), 并且 Kubernetes Service 代理的部分语义使用 IPVS 难以实现(特别是某些 Service 根据连接的客户端是本地还是远程,需要有不同的端点)。 现在,nftables 模式的性能与 IPVS 模式相同(实际上略胜一筹),而且没有任何缺点:

kube-proxy IPVS 与 nftables 在不同规模集群中各百分位数下的第一个数据包延迟对比

(理论上,IPVS 模式还具有可以使用其他 IPVS 功能的优势,例如使用替代的"调度器"来平衡端点。 但实际上,这并不太有用,因为 kube-proxy 在每个节点上独立运行,每个节点上的 IPVS 调度器无法与其他节点上的代理共享状态,从而无法实现更智能的流量均衡。)

虽然 Kubernetes 项目目前没有立即放弃 IPVS 后端的计划,但从长远来看,IPVS 可能难逃被淘汰的命运。 目前使用 IPVS 模式的用户应尝试使用 nftables 模式(如果发现 nftables 模式中缺少某些无法绕过的功能, 请提交问题报告)。

进一步了解