本文已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已不正确。

Kubernetes 的 IPTables 链不是 API

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

作为 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 本质上已弃用,并且不再进行改进。(如果您使用 iptables API,即使通过 iptables-nft,RHEL 9 也会记录警告。)

尽管截至 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 的情况下,实际上很容易用 filter 表中对 DROP 的直接调用来替换 nat 表中对 KUBE-MARK-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 跟踪。