本文发表时间已超过一年。较早的文章可能包含过时内容。请检查页面中的信息自发布以来是否已失效。

Kubernetes 的 IPTables Chain 不是 API

一些 Kubernetes 组件(例如 kubelet 和 kube-proxy)在其操作过程中会创建 iptables 链和规则。这些链从未打算作为任何 Kubernetes API/ABI 保证的一部分,但一些外部组件仍在使用其中的一些(特别是使用 KUBE-MARK-MASQ 来标记需要进行 IP 伪装的数据包)。

作为 v1.25 版本的一部分,SIG Network 明确声明:(除一个例外)Kubernetes 创建的 iptables 链仅供 Kubernetes 自身内部使用,第三方组件不应假定 Kubernetes 会创建任何特定的 iptables 链,也不应假定即使存在这些链,它们也会包含任何特定的规则。

然后,在未来的版本中,作为 KEP-3178 的一部分,我们将开始逐步淘汰 Kubernetes 本身不再需要的某些链。Kubernetes 自身以外使用 KUBE-MARK-MASQKUBE-MARK-DROP 或其他 Kubernetes 生成的 iptables 链的组件现在应该开始迁移,不再使用它们。

背景

除了各种服务特定的 iptables 链外,kube-proxy 还会创建某些通用 iptables 链,作为服务代理的一部分使用。过去,kubelet 也使用 iptables 实现一些功能(例如为 Pod 设置 hostPort 映射),因此它也冗余地创建了一些相同的链。

然而,随着 Kubernetes 1.24 中移除 dockershim,kubelet 现在不再为其自身目的使用任何 iptables 规则;它以前使用 iptables 实现的功能现在始终由容器运行时或网络插件负责,kubelet 没有理由创建任何 iptables 规则。

同时,虽然 iptables 在 Linux 上仍然是 kube-proxy 的默认后端,但它不太可能永远保持默认,因为相关的命令行工具和内核 API 实际上已经废弃,并且不再接收改进。(RHEL 9 会记录警告,如果您使用 iptables API,即使是通过 iptables-nft 也是如此。)

尽管截至 Kubernetes 1.25 版本,iptables kube-proxy 仍然流行,并且 kubelet 继续创建它历史上创建的 iptables 规则(尽管不再 使用 它们),但第三方软件不能假定核心 Kubernetes 组件未来会持续创建这些规则。

即将发生的变更

从现在起几个版本后,kubelet 将不再在 nat 表中创建以下 iptables 链:

  • KUBE-MARK-DROP
  • KUBE-MARK-MASQ
  • KUBE-POSTROUTING

此外,filter 表中的 KUBE-FIREWALL 链将不再拥有当前与 KUBE-MARK-DROP 相关联的功能(并且它最终可能会完全消失)。

此项变更将通过 IPTablesOwnershipCleanup 特性门控分阶段引入。该特性门控在 Kubernetes 1.25 中可用,并可手动启用进行测试。目前的计划是它将在 Kubernetes 1.27 中默认启用,但这可能会推迟到更晚的版本。(不会早于 Kubernetes 1.27 版本发生。)

如果你使用了 Kubernetes 的 iptables 链,该怎么办

(尽管下面的讨论侧重于仍然基于 iptables 的短期修复,但你可能也应该开始考虑最终迁移到 nftables 或其他 API。)

如果你使用 KUBE-MARK-MASQ...

如果你正在利用 KUBE-MARK-MASQ 链来使数据包被伪装,你有两个选择:(1) 重写你的规则直接使用 -j MASQUERADE,(2) 创建你自己的替代性“标记以进行伪装”链。

kube-proxy 使用 KUBE-MARK-MASQ 的原因是存在许多情况,它需要对一个数据包同时调用 -j DNAT-j MASQUERADE,但在 iptables 中不可能同时执行这两者;DNAT 必须从 PREROUTING (或 OUTPUT) 链调用(因为它可能改变数据包将被路由到的位置),而 MASQUERADE 必须从 POSTROUTING 调用(因为它选择的伪装源 IP 取决于最终的路由决策)。

理论上,kube-proxy 可以有一组规则在 PREROUTING/OUTPUT 中匹配数据包并调用 -j DNAT,然后有第二组规则在 POSTROUTING 中匹配相同的数据包并调用 -j MASQUERADE。但为了效率,它只匹配一次,在 PREROUTING/OUTPUT 期间,此时它调用 -j DNAT,然后调用 -j KUBE-MARK-MASQ 在内核数据包标记上设置一个位作为提醒。之后,在 POSTROUTING 期间,它有一个单独的规则匹配所有之前标记的数据包,并对其调用 -j MASQUERADE

如果你有很多规则需要像 kube-proxy 那样同时对同一数据包应用 DNAT 和伪装,那么你可能需要类似的安排。但在许多情况下,使用 KUBE-MARK-MASQ 的组件这样做只是因为他们模仿了 kube-proxy 的行为,而没有理解 kube-proxy 为何那样做。这些组件中的许多可以很容易地重写,只使用单独的 DNAT 和伪装规则。(在没有发生 DNAT 的情况下,使用 KUBE-MARK-MASQ 就更没有意义了;只需将你的规则从 PREROUTING 移到 POSTROUTING 并直接调用 -j MASQUERADE。)

如果你使用 KUBE-MARK-DROP...

KUBE-MARK-DROP 的原理与 KUBE-MARK-MASQ 类似:kube-proxy 希望在 nat 表的 KUBE-SERVICES 链中与其他决策一起做出数据包丢弃决策,但你只能从 filter 表调用 -j DROP。所以,它转而使用 KUBE-MARK-DROP 来标记数据包,以便稍后丢弃。

总的来说,移除对 KUBE-MARK-DROP 依赖的方法与移除对 KUBE-MARK-MASQ 依赖的方法相同。在 kube-proxy 的情况下,实际上很容易将 nat 表中对 KUBE-MARK-DROP 的使用替换为在 filter 表中直接调用 DROP,因为 DNAT 规则和丢弃规则之间没有复杂的相互作用,因此丢弃规则可以简单地从 nat 移到 filter

在更复杂的情况下,可能需要在 natfilter 中都“重新匹配”相同的数据包。

如果你使用 Kubelet 的 iptables 规则来区分 iptables-legacyiptables-nft...

从容器内部操作主机网络命名空间 iptables 规则的组件需要某种方式来判断主机是使用旧的 iptables-legacy 二进制文件还是较新的 iptables-nft 二进制文件(它们底层与不同的内核 API 通信)。

iptables-wrappers 模块为这些组件提供了一种自动检测系统 iptables 模式的方法,但过去它是通过假定 Kubelet 会在任何容器启动之前创建“一堆” iptables 规则来实现的,因此可以通过查看哪种模式定义了更多规则来猜测主机文件系统中 iptables 二进制文件使用的是哪种模式。

在未来的版本中,Kubelet 将不再创建许多 iptables 规则,因此基于规则数量计数的启发式方法可能会失效。

然而,从 1.24 版本开始,Kubelet 始终在使用中的 iptables 子系统的 mangle 表中创建一个名为 KUBE-IPTABLES-HINT 的链。组件现在可以通过查找此特定链来了解 Kubelet(以及因此,推测整个系统)正在使用哪个 iptables 子系统。

(此外,自 Kubernetes 1.17 以来,kubelet 已在 mangle 表中创建了一个名为 KUBE-KUBELET-CANARY 的链。虽然这个链未来可能会被移除,但它当然仍然会存在于旧版本中,因此在任何较新的 Kubernetes 版本中,KUBE-IPTABLES-HINTKUBE-KUBELET-CANARY 至少有一个会存在。)

iptables-wrappers 包已更新包含此新的启发式方法,因此如果您之前使用过该包,现在可以使用更新的版本重建您的容器镜像。

进一步阅读

清理 iptables 链所有权和废弃旧链的项目由 KEP-3178 跟踪。