本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

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 跟蹤。