(譯)kube-proxy 模式對比:iptables 還是 IPVS?

kube-proxy 是 Kubernetes 中的關鍵組件。他的角色就是在服務(ClusterIP 和 NodePort)和其後端 Pod 之間進行負載均衡。kube-proxy 有三種運行模式,每種都有不同的實現技術:

userspaceiptables 或者 IPVS

userspace 模式非常陳舊、緩慢,已經不推薦使用。但是 iptables 和 IPVS 該如何選擇呢?本文中我們會對這兩種模式進行比較,看看他們在真正的微服務上下文中的表現,並解釋在特定情況下的選擇方法。

首先我們說一下這兩種模式的背景,然後開始測試並查看結果。

背景:iptables 模式

iptables 是一個 Linux 內核功能,是一個高效的防火牆,並提供了大量的數據包處理和過濾方面的能力。它可以在核心數據包處理管線上用 Hook 掛接一系列的規則。iptables 模式中 kube-proxy 在 NAT pre-routing Hook 中實現它的 NAT 和負載均衡功能。這種方法簡單有效,依賴於成熟的內核功能,並且能夠和其它跟 iptables 協作的應用(例如 Calico)融洽相處。

然而 kube-proxy 的用法是一種 O(n) 算法,其中的 n 隨集群規模同步增長,這裡的集群規模,更明確的說就是服務和後端 Pod 的數量。

背景:IPVS 模式

IPVS 是一個用於負載均衡的 Linux 內核功能。IPVS 模式下,kube-proxy 使用 IPVS 負載均衡代替了 iptable。這種模式同樣有效,IPVS 的設計就是用來為大量服務進行負載均衡的,它有一套優化過的 API,使用優化的查找算法,而不是簡單的從列表中查找規則。

這樣一來,kube-proxy 在 IPVS 模式下,其連接過程的複雜度為 O(1)。換句話說,多數情況下,他的連接處理效率是和集群規模無關的。

另外作為一個獨立的負載均衡器,IPVS 包含了多種不同的負載均衡算法,例如輪詢、最短期望延遲、最少連接以及各種哈希方法等。而 iptables 就只有一種隨機平等的選擇算法。

IPVS 的一個潛在缺點就是,IPVS 處理數據包的路徑和通常情況下 iptables 過濾器的路徑是不同的。如果計劃在有其他程序使用 iptables 的環境中使用 IPVS,需要進行一些研究,看看他們是否能夠協調工作。(Calico 已經和 IPVS kube-proxy 兼容)

性能對比

iptables 的連接處理算法複雜度是 O(n),而 IPVS 模式是 O(1),但是在微服務環境中,其具體表現如何呢?

在多數場景中,有兩個關鍵屬性需要關注:

  • 響應時間:一個微服務向另一個微服務發起調用時,第一個微服務發送請求,並從第二個微服務中得到響應,中間消耗了多少時間?
  • CPU 消耗:運行微服務的過程中,總體 CPU 使用情況如何?包括用戶和核心空間的 CPU 使用,包含所有用於支持微服務的進程(也包括 kube-proxy)。

為了說明問題,我們運行一個微服務作為客戶端,這個微服務以 Pod 的形式運行在一個獨立的節點上,每秒鐘發出 1000 個請求,請求的目標是一個 Kubernetes 服務,這個服務由 10 個 Pod 作為後端,運行在其它的節點上。接下來我們在客戶端節點上進行了測量,包括 iptables 以及 IPVS 模式,運行了數量不等的 Kubernetes 服務,每個服務都有 10 個 Pod,最大有 10,000 個服務(也就是 100,000 個 Pod)。我們用 golang 編寫了一個簡單的測試工具作為客戶端,用標準的 NGINX 作為後端服務。

響應時間

響應時間很重要,有助於我們理解連接和請求的差異。典型情況下,多數微服務都會使用持久或者 keepalive 連接,這意味著每個連接都會被多個請求複用,而不是每個請求一次連接。這很重要,因為多數連接的新建過程都需要完成三次 TCP 握手的過程,這需要消耗時間,也需要在 Linux 網絡棧中進行更多操作,也就會消耗更多 CPU 和時間。

(譯)kube-proxy 模式對比:iptables 還是 IPVS?

這張圖展示了兩個關鍵點:

  • iptables 和 IPVS 的平均響應時間在 1000 個服務(10000 個 Pod)以上時,會開始觀察到差異。
  • 只有在每次請求都發起新連接的情況下,兩種模式的差異才比較明顯。

不管是 iptables 還是 IPVS,kube-proxy 的響應時間開銷都是和建立連接的數量相關的,而不是數據包或者請求數量,這是因為 Linux 使用了 Conntrack,能夠高效地將數據包和現存連接關聯起來。如果數據包能夠被 Conntrack 成功匹配,那就不需要通過 kube-proxy 的 iptables 或 IPVS 規則來推算去向。Linux conntrack 非常棒!(絕大多數時候)

值得注意的是,例子中的服務端微服務使用 NGINX 提供一個靜態小頁面。多數微服務要做更多操作,因此會產生更高的響應時間,也就是 kube-proxy 處理過程在總體時間中的佔比會減少。

還有個需要解釋的古怪問題:既然 IPVS 的連接過程複雜度是 O(1),為什麼在 10,000 服務的情況下,非 Keepalive 的響應時間還是提高了?我們需要深入挖掘更多內容才能解釋這一問題,但是其中一個因素就是因為上升的 CPU 用量拖慢了整個系統。這就是下一個主題需要探究的內容。

CPU 用量

為了描述 CPU 用量,下圖關注的是最差情況:不使用持久/keepalive 連接的情況下,kube-proxy 會有最大的處理開銷。

(譯)kube-proxy 模式對比:iptables 還是 IPVS?

上圖說明了兩件事:

  • 在超過 1000 個服務(也就是 10,000 個 Pod)的情況下,CPU 用量差異才開始明顯。
  • 在一萬個服務的情況下(十萬個後端 Pod),iptables 模式增長了 0.35 個核心的佔用,而 IPVS 模式僅增長了 8%。

有兩個主要因素造成 CPU 用量增長:

第一個因素是,缺省情況下 kube-proxy 每 30 秒會用所有服務對內核重新編程。這也解釋了為什麼 IPVS 模式下,新建連接的 O(1) 複雜度也仍然會產生更多的 CPU 佔用。另外,如果是舊版本內核,重新編程 iptables 的 API 會更慢。所以如果你用的內核較舊,iptables 模式可能會佔用更多的 CPU。

另一個因素是,kube-proxy 使用 IPVS 或者 iptables 處理新連接的消耗。對 iptables 來說,通常是 O(n) 的複雜度。在存在大量服務的情況下,會出現顯著的 CPU 佔用升高。例如在 10,000 服務(100,000 個後端 Pod)的情況下,iptables 會為每個請求的每個連接處理大約 20000 條規則。如果使用 NINGX 缺省每連接 100 請求的 keepalive 設置,kube-proxy 的 iptables 規則執行次數會減少為 1%,會把 iptables 的 CPU 消耗降低到和 IPVS 類似的水平。

客戶端微服務會簡單的丟棄響應內容。真實世界中自然會進行更多處理,也會造成更多的 CPU 消耗,但是不會影響 CPU 消耗隨服務數量增長的事實。

結論

在超過 1000 服務的規模下,kube-proxy 的 IPVS 模式會有更好的性能表現。雖然可能有多種不同情況,但是通常來說,讓微服務使用持久連接、運行現代內核,也能取得較好的效果。如果運行的內核較舊,或者無法使用持久連接,那麼 IPVS 模式可能是個更好的選擇。

拋開性能問題不談,IPVS 模式還有個好處就是具有更多的負載均衡算法可供選擇。

如果你還不確定 IPVS 是否合適,那就繼續使用 iptables 模式好了。這種傳統模式有大量的生產案例支撐,他是一個不完美的缺省選項。

補充:Calico 和 kube-proxy 的 iptables 比較

本文中我們看到,kube-proxy 中的 iptables 用法在大規模集群中可能會產生性能問題。有人問我 Calico 為什麼沒有類似的問題。答案是 Calico 中 kube-proxy 的用法是不同的。kube-proxy 使用了一個很長的規則鏈條,鏈條長度會隨著集群規模而增長,Calico 使用的是一個很短的優化過的規則鏈,經由 ipsets 的加持,也具備了 O(1) 複雜度的查詢能力。

下圖證明了這一觀點,其中展示了每次連接過程中,kube-proxy 和 Calico 中 iptables 規則數量的平均值。這裡假設集群中的節點平均有 30 個 Pod,每個 Pod 具有 3 個網絡規則。

(譯)kube-proxy 模式對比:iptables 還是 IPVS?

即使是使用 10,000 個服務和 100,000 個 Pod 的情況下,Calico 每連接執行的 iptables 規則也只是和 kube-proxy 在 20 服務 200 個 Pod 的情況基本一致。


分享到:


相關文章: