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

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 基本上已被弃用,并且不再得到改进。(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 希望在 natKUBE-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 跟踪。