本文发表时间已超过一年。较早的文章可能包含过时内容。请检查页面中的信息自发布以来是否已失效。
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-MASQ
、KUBE-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
。
在更复杂的情况下,可能需要在 nat
和 filter
中都“重新匹配”相同的数据包。
如果你使用 Kubelet 的 iptables 规则来区分 iptables-legacy
和 iptables-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-HINT
或 KUBE-KUBELET-CANARY
至少有一个会存在。)
iptables-wrappers
包已更新包含此新的启发式方法,因此如果您之前使用过该包,现在可以使用更新的版本重建您的容器镜像。
进一步阅读
清理 iptables 链所有权和废弃旧链的项目由 KEP-3178 跟踪。