kube-proxy 的 NFTables 模式

在 Kubernetes 1.29 中,引入了新的 kube-proxy nftables 模式作為 Alpha 功能。目前處於 Beta 階段,預計在 1.33 版本中達到 GA(正式釋出)。新模式解決了 iptables 模式中長期存在的效能問題,我們鼓勵所有在擁有較新核心的系統上執行的使用者嘗試一下。(出於相容性原因,即使 nftables 成為 GA,iptables 仍將是**預設**模式。)

為什麼選擇 nftables?第一部分:資料平面延遲

iptables API 是為實現簡單防火牆而設計的,在擴充套件以支援擁有數萬個 Service 的大型 Kubernetes 叢集中的 Service 代理時存在問題。

通常,在 iptables 模式下,kube-proxy 生成的規則集中的 iptables 規則數量與 Service 數量和端點總數的總和成正比。特別地,在規則集的頂層,對於資料包可能的目標地址,每個可能的 Service IP(和埠)都有一條規則來測試。

# If the packet is addressed to 172.30.0.41:80, then jump to the chain
# KUBE-SVC-XPGD46QRK7WJZT7O for further processing
-A KUBE-SERVICES -m comment --comment "namespace1/service1:p80 cluster IP" -m tcp -p tcp -d 172.30.0.41 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O

# If the packet is addressed to 172.30.0.42:443, then...
-A KUBE-SERVICES -m comment --comment "namespace2/service2:p443 cluster IP" -m tcp -p tcp -d 172.30.0.42 --dport 443 -j KUBE-SVC-GNZBNJ2PO5MGZ6GT

# etc...
-A KUBE-SERVICES -m comment --comment "namespace3/service3:p80 cluster IP" -m tcp -p tcp -d 172.30.0.43 --dport 80 -j KUBE-SVC-X27LE4BHSL4DOUIK

這意味著當一個數據包進入時,核心檢查它與所有 Service 規則的時間複雜度是**O(n)**,其中 n 是 Service 的數量。隨著 Service 數量的增加,新連線的第一個資料包的平均和最壞情況延遲都會增加(最好、平均和最壞情況之間的差異主要取決於給定的 Service IP 地址在 `KUBE-SERVICES` 鏈中出現的位置是靠前還是靠後)。

kube-proxy iptables first packet latency, at various percentiles, in clusters of various sizes

相比之下,使用 nftables,編寫此類規則集的常規方法是使用**單一**規則,透過“verdict map”(判決對映)來進行排程。

table ip kube-proxy {

        # The service-ips verdict map indicates the action to take for each matching packet.
	map service-ips {
		type ipv4_addr . inet_proto . inet_service : verdict
		comment "ClusterIP, ExternalIP and LoadBalancer IP traffic"
		elements = { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-namespace1/service1/tcp/p80,
                             172.30.0.42 . tcp . 443 : goto service-42NFTM6N-namespace2/service2/tcp/p443,
                             172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-namespace3/service3/tcp/p80,
                             ... }
        }

        # Now we just need a single rule to process all packets matching an
        # element in the map. (This rule says, "construct a tuple from the
        # destination IP address, layer 4 protocol, and destination port; look
        # that tuple up in "service-ips"; and if there's a match, execute the
        # associated verdict.)
	chain services {
		ip daddr . meta l4proto . th dport vmap @service-ips
	}

        ...
}

由於只有一條規則,並且對映查詢的時間複雜度大致為 **O(1)**,所以無論叢集大小如何,資料包處理時間都或多或少是恆定的,最好、平均和最壞情況都非常相似。

kube-proxy nftables first packet latency, at various percentiles, in clusters of various sizes

但請注意 iptables 和 nftables 圖表在垂直尺度上的巨大差異!在擁有 5000 和 10000 個 Service 的叢集中,nftables 的 p50(平均)延遲與 iptables 的 p01(大約是最佳情況)延遲大致相同。在擁有 30000 個 Service 的叢集中,nftables 的 p99(大約是最壞情況)延遲甚至比 iptables 的 p01 延遲快了幾個微秒!這裡是兩組資料放在一起的圖表,但你可能需要眯著眼睛才能看到 nftables 的結果!

kube-proxy iptables-vs-nftables first packet latency, at various percentiles, in clusters of various sizes

為什麼選擇 nftables?第二部分:控制平面延遲

雖然在大型叢集中資料平面延遲的改進非常棒,但 iptables kube-proxy 還存在另一個問題,這個問題常常導致使用者甚至無法將叢集擴充套件到那麼大的規模:當 Service 及其端點發生變化時,kube-proxy 編寫新 iptables 規則所需的時間。

對於 iptables 和 nftables,整個規則集的大小(實際規則加上相關資料)與 Service 及其端點的總數成 **O(n)** 關係。最初,iptables 後端會在每次更新時重寫所有規則,當有數萬個 Service 時,這可能會變成數十萬條 iptables 規則。從 Kubernetes 1.26 開始,我們開始改進 kube-proxy,使其在每次更新中可以跳過更新**大部分**未更改的規則,但 `iptables-restore` 作為 API 的侷限性意味著仍然需要傳送一個與 Service 數量成 **O(n)** 關係的更新(儘管常數比以前小得多)。即使有了這些最佳化,仍然可能需要使用 kube-proxy 的 `minSyncPeriod` 配置選項,以確保它不會把所有時間都花在推送 iptables 更新上。

nftables API 允許進行更增量的更新,當 kube-proxy 在 nftables 模式下進行更新時,更新的大小僅與自上次同步以來已更改的 Service 和端點數量成 **O(n)** 關係,而與 Service 和端點的總數無關。nftables API 允許每個使用 nftables 的元件擁有自己的私有表,這也意味著元件之間不會像 iptables 那樣存在全域性鎖爭用。因此,kube-proxy 的 nftables 更新可以比 iptables 更高效地完成。

(很遺憾,這部分我沒有酷炫的圖表。)

為什麼**不**用 nftables?

儘管如此,目前有幾個原因可能讓你不想立即切換到使用 nftables 後端。

首先,程式碼還很新。雖然它有大量的單元測試,在我們的 CI 系統中表現正常,並且已經被多個使用者在實際環境中使用,但它的實際使用量遠不及 iptables 後端,所以我們不能保證它同樣穩定和無 bug。

其次,nftables 模式在較舊的 Linux 發行版上無法工作;目前它需要 5.13 或更新的核心。此外,由於早期版本 `nft` 命令列工具中的 bug,你不應該在主機檔案系統中有舊版本(早於 1.0.0)`nft` 的節點上以 nftables 模式執行 kube-proxy(否則 kube-proxy 對 nftables 的使用可能會干擾系統上其他對 nftables 的使用)。

第三,你的叢集中可能還有其他網路元件,例如 Pod 網路或 NetworkPolicy 實現,它們可能尚不支援 nftables 模式下的 kube-proxy。你應該查閱這些元件的文件(或論壇、bug 跟蹤器等),看看它們是否與 nftables 模式存在問題。(在許多情況下它們不會有問題;只要它們不直接與 kube-proxy 的 iptables 規則互動或覆蓋它們,它們就不應該關心 kube-proxy 是使用 iptables 還是 nftables。)此外,未更新的可觀測性和監控工具在 nftables 模式下報告的資料可能比在 iptables 模式下要少。

最後,nftables 模式下的 kube-proxy 故意與 iptables 模式下的 kube-proxy 不 100% 相容。有一些舊的 kube-proxy 功能,其預設行為不如我們期望的那樣安全、高效能或直觀,但我們認為更改預設值會破壞相容性。由於 nftables 模式是可選的,這給了我們一個機會來修復那些不好的預設值,而不會影響那些不期望有變化的使用者。(特別是,在 nftables 模式下,NodePort Service 現在只能在其節點的預設 IP 上訪問,而在 iptables 模式下,它們可以在所有 IP 上訪問,包括 `127.0.0.1`。)kube-proxy 文件提供了更多關於這方面的資訊,包括有關可以檢視哪些指標來確定你是否依賴於任何已更改的功能,以及有哪些配置選項可用於獲得更向後相容的行為。

試用 nftables 模式

準備好試用了嗎?在 Kubernetes 1.31 及更高版本中,你只需向 kube-proxy 傳遞 `--proxy-mode nftables`(或在你的 kube-proxy 配置檔案中設定 `mode: nftables`)。

如果你使用 kubeadm 來設定叢集,kubeadm 文件解釋了如何向 `kubeadm init` 傳遞 `KubeProxyConfiguration`。你也可以使用 `kind` 部署基於 nftables 的叢集

你還可以透過更新 kube-proxy 配置並重新啟動 kube-proxy Pod,將現有叢集從 iptables(或 ipvs)模式轉換為 nftables 模式。(你不需要重新啟動節點:當以 nftables 模式重新啟動時,kube-proxy 將刪除任何現有的 iptables 或 ipvs 規則,同樣,如果你稍後恢復到 iptables 或 ipvs 模式,它將刪除任何現有的 nftables 規則。)

未來計劃

如上所述,雖然 nftables 現在是**最佳**的 kube-proxy 模式,但它並不是**預設**模式,我們目前還沒有改變這一點的計劃。我們將繼續長期支援 iptables 模式。

kube-proxy 的 IPVS 模式的未來則不那麼確定:它相對於 iptables 的主要優勢是速度更快,但 IPVS 架構和 API 的某些方面對於 kube-proxy 的目的來說很笨拙(例如,`kube-ipvs0` 裝置需要分配**每個** Service IP 地址),並且 Kubernetes Service 代理的某些語義很難用 IPVS 實現(特別是某些 Service 根據你是從本地還是遠端客戶端連線而需要有不同的端點)。而現在,nftables 模式具有與 IPVS 模式相同的效能(實際上略好),而且沒有任何缺點。

kube-proxy ipvs-vs-nftables first packet latency, at various percentiles, in clusters of various sizes

(理論上,IPVS 模式還有一個優勢,即能夠使用各種其他 IPVS 功能,例如用於平衡端點的替代“排程器”。實際上,這最終並不十分有用,因為 kube-proxy 在每個節點上獨立執行,並且每個節點上的 IPVS 排程器無法與其他節點上的代理共享其狀態,從而阻礙了更智慧地平衡流量的努力。)

雖然 Kubernetes 專案沒有立即放棄 IPVS 後端的計劃,但從長遠來看,它很可能被淘汰,目前使用 IPVS 模式的人應該嘗試 nftables 模式(如果你認為 nftables 模式中缺少你無法繞過的功能,請提交 bug)。

瞭解更多