kube-proxy 的 NFTables 模式

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

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

iptables API 设计用于实现简单的防火墙,但扩展到支持具有数万个 Service 的大型 Kubernetes 集群时会遇到问题。

通常,kube-proxy 在 iptables 模式下生成的规则集具有与 Service 数量和端点总数之和成比例的 iptables 规则数量。特别是,在规则集的顶层,对于每个可能寻址到的 Service IP (和端口) 的数据包,都有一个规则来测试它

# If the packet is addressed to 172.30.0.41:80, then jump to the chain
# KUBE-SVC-XPGD46QRK7WJZT7O for further processing
-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

# If the packet is addressed to 172.30.0.42:443, then...
-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

# etc...
-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 规则所需的时间与 Service 数量呈 O(n) 关系。随着 Service 数量的增加,新连接的第一个数据包的平均延迟和最差延迟都会增加 (最佳情况、平均情况和最差情况之间的差异主要取决于给定 Service IP 地址在 KUBE-SERVICES 链中出现得更早还是更晚)。

kube-proxy iptables first packet latency, at various percentiles, in clusters of various sizes

相比之下,对于 nftables,编写此类规则集的常用方法是使用 单一 规则,并使用“判决映射”(verdict map) 进行分派

table ip kube-proxy {

        # The service-ips verdict map indicates the action to take for each matching packet.
	map service-ips {
		type ipv4_addr . inet_proto . inet_service : verdict
		comment "ClusterIP, ExternalIP and LoadBalancer IP traffic"
		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,
                             ... }
        }

        # Now we just need a single rule to process all packets matching an
        # element in the map. (This rule says, "construct a tuple from the
        # destination IP address, layer 4 protocol, and destination port; look
        # that tuple up in "service-ips"; and if there's a match, execute the
        # associated verdict.)
	chain services {
		ip daddr . meta l4proto . th dport vmap @service-ips
	}

        ...
}

由于只有一个规则,并且映射查找的时间复杂度大约是 O(1),所以数据包处理时间或多或少是恒定的,与集群大小无关,并且最佳/平均/最差情况非常相似

kube-proxy nftables first packet latency, at various percentiles, in clusters of various sizes

但请注意 iptables 和 nftables 图表中垂直刻度的巨大差异!在拥有 5000 和 10000 个 Service 的集群中,nftables 的 p50(平均)延迟与 iptables 的 p01(大约最佳情况)延迟大致相同。在 30000 个 Service 的集群中,nftables 的 p99(大约最差情况)延迟甚至比 iptables 的 p01 延迟快了几微秒!这是两组数据放在一起的情况,但你可能需要仔细看才能看到 nftables 的结果!

kube-proxy iptables-vs-nftables first packet latency, at various percentiles, in clusters of various sizes

为什么选择 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 那样存在组件间的全局锁竞争。因此,nftables 的 kube-proxy 更新比 iptables 更高效。

(遗憾的是,这部分没有酷炫的图表。)

为什么选择 nftables?

尽管如此,目前您可能不希望立即切换到使用 nftables 后端有几个原因。

首先,代码还相当新。虽然它有大量的单元测试,在我们的 CI 系统中表现正确,并且已经被多个用户在真实世界中使用,但它并没有像 iptables 后端那样经历过大量的真实世界使用,所以我们无法保证它像 iptables 那样稳定和无 bug。

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

第三,您的集群中可能还有其他网络组件,例如 Pod 网络或 NetworkPolicy 实现,它们尚不支持 nftables 模式的 kube-proxy。您应该查阅这些组件的文档 (或论坛、Bug 跟踪器等),以查看它们是否存在 nftables 模式的问题。(在很多情况下,它们不会有问题;只要它们不尝试直接与 kube-proxy 的 iptables 规则交互或覆盖它们,它们就不应该关心 kube-proxy 使用的是 iptables 还是 nftables。) 此外,尚未更新的可观测性和监控工具在 nftables 模式下报告的数据可能少于在 iptables 模式下报告的数据。

最后,nftables 模式的 kube-proxy 有意与 iptables 模式的 kube-proxy 不是 100% 兼容。有一些旧的 kube-proxy 特性,其默认行为安全性较低、性能较差或不太直观,而我们认为改变默认行为会破坏兼容性。由于 nftables 模式是可选加入的,这给了我们一个机会在不破坏未预期更改的用户的情况下修复这些不好的默认行为。(特别是,在 nftables 模式下,NodePort Service 现在只能通过其节点的默认 IP 地址访问,而在 iptables 模式下,它们可以通过所有 IP 地址访问,包括 127.0.0.1。) kube-proxy 文档 中有更多相关信息,包括如何查看指标以确定您是否依赖于任何已更改的功能,以及有哪些配置选项可用于获得更向后兼容的行为。

尝试 nftables 模式

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

如果您使用 kubeadm 设置集群,kubeadm 文档解释了如何将 KubeProxyConfiguration 传递给 kubeadm init。您还可以使用 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-vs-nftables first packet latency, at various percentiles, in clusters of various sizes

(理论上,IPVS 模式还有一个优势,就是能够使用其他各种 IPVS 功能,例如用于平衡端点的其他“调度器”。实际上,这最终并没有太大的用处,因为 kube-proxy 在每个节点上独立运行,并且每个节点上的 IPVS 调度器无法与其他节点上的代理共享其状态,从而阻碍了更巧妙地平衡流量的努力。)

虽然 Kubernetes 项目目前没有计划放弃 IPVS 后端,但从长远来看,它可能会被淘汰,目前使用 IPVS 模式的人应该尝试 nftables 模式(如果您认为 nftables 模式存在您无法解决的缺失功能,请提交 Bug)。

了解更多