比較 kube-proxy 模式:iptables 還是 IPVS?
kube-proxy是任何 Kubernetes 部署中的關(guān)鍵組件。它的作用是將流向服務(wù)(通過集群 IP 和節(jié)點端口)的流量負載均衡到正確的后端pod。kube-proxy可以運行在三種模式之一,每種模式都使用不同的數(shù)據(jù)平面技術(shù)來實現(xiàn):userspace、iptables 或 IPVS。
userspace 模式非常舊且慢,絕對不推薦!但是,應(yīng)該如何權(quán)衡選擇 iptables 還是 IPVS 模式呢?在本文中,我們將比較這兩種模式,在實際的微服務(wù)環(huán)境中衡量它們的性能,并解釋在何種情況下你可能會選擇其中一種。
首先,我們將簡要介紹這兩種模式的背景,然后深入測試和結(jié)果……
背景:iptables 代理模式
iptables 是一個 Linux 內(nèi)核功能,旨在成為一個高效的防火墻,具有足夠的靈活性來處理各種常見的數(shù)據(jù)包操作和過濾需求。它允許將靈活的規(guī)則序列附加到內(nèi)核數(shù)據(jù)包處理管道中的各個鉤子上。在 iptables 模式下,kube-proxy將規(guī)則附加到 “NAT pre-routing” 鉤子上,以實現(xiàn)其 NAT 和負載均衡功能。這種方式可行,簡單,使用成熟的內(nèi)核功能,并且與其他使用 iptables 進行過濾的程序(例如 Calico)能夠很好地兼容。
然而,kube-proxy 編程 iptables 規(guī)則的方式意味著它名義上是一個 O(n) 風格的算法,其中 n 大致與集群規(guī)模成正比(更準確地說,是與服務(wù)的數(shù)量和每個服務(wù)背后的后端 pod 數(shù)量成正比)。
背景:IPVS 代理模式
IPVS 是一個專門為負載均衡設(shè)計的 Linux 內(nèi)核功能。在 IPVS 模式下,kube-proxy通過編程 IPVS 負載均衡器來代替使用 iptables。它同樣使用成熟的內(nèi)核功能,且 IPVS 專為負載均衡大量服務(wù)而設(shè)計;它擁有優(yōu)化的 API 和查找例程,而不是一系列順序規(guī)則。
結(jié)果是,在 IPVS 模式下,kube-proxy 的連接處理具有名義上的 O(1) 計算復雜度。換句話說,在大多數(shù)情況下,它的連接處理性能將保持恒定,而不受集群規(guī)模的影響。
此外,作為一個專用的負載均衡器,IPVS 擁有多種不同的調(diào)度算法,如輪詢、最短期望延遲、最少連接數(shù)和各種哈希方法。相比之下,iptables 中的 kube-proxy 使用的是隨機的等成本選擇算法。
IPVS 的一個潛在缺點是,通過 IPVS 處理的數(shù)據(jù)包在 iptables 過濾鉤子中的路徑與正常情況下的數(shù)據(jù)包非常不同。如果計劃將 IPVS 與其他使用 iptables 的程序一起使用,則需要研究它們是否能夠預期地協(xié)同工作。(別擔心,Calico 早在很久以前就已經(jīng)與 IPVS kube-proxy 兼容了?。?/p>
性能比較
好的,從名義上來說,kube-proxy在 iptables 模式下的連接處理是 O(n) 復雜度,而在 IPVS 模式下是 O(1) 復雜度。但在實際微服務(wù)環(huán)境中,這意味著什么呢?
在大多數(shù)情況下,涉及應(yīng)用程序和微服務(wù)時,kube-proxy性能有兩個關(guān)鍵屬性可能是您關(guān)心的:
- 對往返響應(yīng)時間的影響:當一個微服務(wù)向另一個微服務(wù)發(fā)出 API 調(diào)用時,平均而言第一個微服務(wù)發(fā)送請求并從第二個微服務(wù)接收響應(yīng)需要多長時間?
- 對總 CPU 使用率的影響:在運行微服務(wù)時,包括用戶空間和內(nèi)核/系統(tǒng)使用在內(nèi)的主機總 CPU 使用率是多少?這包括了支持微服務(wù)所需的所有進程,包括 kube-proxy。
為了說明這一點,我們在一個專用節(jié)點上運行了一個clinet微服務(wù)pod,每秒生成 1000 個請求,發(fā)送到由 10 個service微服務(wù)pod支持的 Kubernetes 服務(wù)。然后,我們在iptables和IPVS模式下,在客戶端節(jié)點上測量了性能,使用了各種數(shù)量的 Kubernetes 服務(wù),每個服務(wù)有10個pod支持,最多達到 10,000 個服務(wù)(即 100,000 個服務(wù)后端)。對于微服務(wù),我們使用golang編寫的簡單測試工具作為我們的客戶端微服務(wù),并使用標準NGINX作為服務(wù)器微服務(wù)的后端pods。
往返響應(yīng)時間
在考慮往返響應(yīng)時間時,理解連接和請求之間的區(qū)別非常重要。通常,大多數(shù)微服務(wù)將使用持久連接或“keepalive”連接,這意味著每個連接會在多個請求之間重復使用,而不是每個請求都需要新建一個連接。這一點很重要,因為大多數(shù)新連接都需要在網(wǎng)絡(luò)上進行三次握手(這需要時間),并且在 Linux 網(wǎng)絡(luò)棧內(nèi)進行更多處理(這也需要一點時間和 CPU 資源)。
為了說明這些差異,我們在有和沒有 keepalive 連接的情況下進行了測試。對于 keepalive 連接,我們使用了 NGINX 的默認配置,該配置會將每個連接保持活躍狀態(tài)以供最多 100 個請求重復使用。請參見下圖,注意響應(yīng)時間越低越好。
圖表顯示了兩個關(guān)鍵點:
- 在超過 1,000 個服務(wù)(10,000 個后端 pod)之前,iptables 和 IPVS 之間的平均往返響應(yīng)時間差異微不足道。
- 平均往返響應(yīng)時間的差異僅在不使用 keepalive 連接時才明顯。也就是說,當每個請求都使用新連接時,差異才會顯現(xiàn)。
對于 iptables 和 IPVS 模式,kube-proxy 的響應(yīng)時間開銷與建立連接有關(guān),而不是與連接上的數(shù)據(jù)包或請求數(shù)量有關(guān)。這是因為 Linux 使用連接跟蹤(conntrack),能夠非常高效地將數(shù)據(jù)包匹配到現(xiàn)有連接。如果數(shù)據(jù)包在 conntrack 中被匹配到,就不需要通過 kube-proxy 的 iptables 或 IPVS 規(guī)則來決定如何處理它。
值得注意的是,在這個例子中,“服務(wù)器”微服務(wù)使用的是 NGINX pod 提供一個小的靜態(tài)響應(yīng)體。許多微服務(wù)需要做的工作遠不止這些,這將導致相應(yīng)更高的響應(yīng)時間,這意味著 kube-proxy 處理的時間差在這種圖表中將占據(jù)更小的百分比。
最后有一個奇怪現(xiàn)象需要解釋:為什么在 10,000 個服務(wù)時,非 keepalive 連接的響應(yīng)時間在 IPVS 模式下變得更慢,即使 IPVS 中新連接的處理復雜度是 O(1)?要真正深入了解這一點,我們需要進行更多的挖掘,但其中一個因素是整個系統(tǒng)由于主機上增加的 CPU 使用而變得更慢。這也引出了下一個話題。
總CPU使用率
為了說明總 CPU 使用率,下面的圖表集中在不使用持久/keepalive 連接的最壞情況下,此時 kube-proxy 連接處理的開銷影響最大。
圖表顯示了兩個關(guān)鍵點:
- 在超過 1,000 個服務(wù)(10,000 個后端 pod)之前,iptables 和 IPVS 之間的 CPU 使用率差異相對不顯著。
- 在 10,000 個服務(wù)(100,000 個后端 pod)時,iptables 的 CPU 增加約為一個內(nèi)核的 35%,而 IPVS 增加約為一個內(nèi)核的 8%。
有兩個主要因素影響這種 CPU 使用模式。第一個因素是,默認情況下,kube-proxy 每 30 秒重新編程一次內(nèi)核中的所有服務(wù)。這解釋了為什么即使 IPVS 的新連接處理名義上是 O(1) 復雜度,IPVS 模式下的 CPU 也會略有增加。此外,較早版本內(nèi)核中重新編程 iptables 的 API 速度比現(xiàn)在慢得多。因此,如果您在 iptables 模式下使用舊版內(nèi)核,CPU 增長將比圖表中顯示的更高。
第二個因素是 kube-proxy 使用 iptables 或 IPVS 處理新連接所需的時間。對于 iptables,這在名義上是 O(n) 復雜度。在大量服務(wù)的情況下,這對 CPU 使用有顯著影響。例如,在 10,000 個服務(wù)(100,000 個后端 pod)時,iptables 每個新連接執(zhí)行約 20,000 條規(guī)則。不過,請記住,在這個圖表中,我們展示的是每個請求都使用新連接的最壞情況。如果我們使用 NGINX 默認的每個連接 100 次 keepalive 請求,那么 kube-proxy 的 iptables 規(guī)則執(zhí)行頻率將減少 100 倍,從而大大降低使用 iptables 而非 IPVS 的 CPU 影響,接近一個內(nèi)核的 2%。
值得注意的是,在這個例子中,“客戶端”微服務(wù)簡單地丟棄了從“服務(wù)器”微服務(wù)接收到的每個響應(yīng)。一個實際的微服務(wù)需要做的工作遠不止這些,這將增加圖表中的基礎(chǔ) CPU 使用率,但不會改變與服務(wù)數(shù)量相關(guān)的 CPU 增加的絕對值。
結(jié)論
在顯著超過 1,000 個服務(wù)的規(guī)模下,kube-proxy 的 IPVS 模式可以提供一些不錯的性能提升。具體效果可能有所不同,但一般來說,對于使用持久“keepalive”連接風格的微服務(wù),且運行在現(xiàn)代內(nèi)核上的情況下,這些性能提升可能相對有限。對于不使用持久連接的微服務(wù),或者在較舊內(nèi)核上運行時,切換到 IPVS 模式可能會帶來顯著的收益。
除了性能方面的考慮外,如果您需要比 kube-proxy 的 iptables 模式的隨機負載均衡更復雜的負載均衡調(diào)度算法,也應(yīng)該考慮使用 IPVS 模式。
如果您不確定 IPVS 是否會對您有利,那么堅持使用 kube-proxy 的 iptables 模式。它已經(jīng)經(jīng)過大量的生產(chǎn)環(huán)境驗證,盡管并不完美,但可以說它作為默認選擇是有原因的。
比較 kube-proxy 和 Calico 對 iptables 的使用
在本文中,我們看到 kube-proxy 使用 iptables 在非常大規(guī)模時會導致性能影響。我有時會被問到,為什么 Calico 沒有遇到同樣的挑戰(zhàn)。答案是 Calico 對 iptables 的使用與 kube-proxy 有顯著不同。kube-proxy 使用了一條非常長的規(guī)則鏈,其長度大致與集群規(guī)模成正比,而 Calico 使用的是非常短且優(yōu)化的規(guī)則鏈,并廣泛使用 ipsets,其查找時間復雜度為 O(1),不受其大小影響。為了更好地理解這一點,下面的圖表顯示了在集群中的節(jié)點平均托管 30 個 pod,且集群中的每個 pod 平均適用 3 個網(wǎng)絡(luò)策略的情況下,每個連接由 kube-proxy 和 Calico 執(zhí)行的 iptables 規(guī)則的平均數(shù)量。
即使在完全擴展的集群中運行,擁有 10,000 個服務(wù)和 100,000 個后端 pod 時,Calico 每個連接執(zhí)行的 iptables 規(guī)則數(shù)量大致與 kube-proxy 在擁有 20 個服務(wù)和 200 個后端 pod 時執(zhí)行的規(guī)則數(shù)量相同。換句話說,Calico 對 iptables 的使用是可擴展的!