古詩詞大全網 - 古詩大全 - kube-proxy IPVS 模式的工作原理

kube-proxy IPVS 模式的工作原理

Kubernetes 中的 Service 就是壹組同 label 類型 Pod 的服務抽象,為服務提供了負載均衡和反向代理能力,在集群中表示壹個微服務的概念。 kube-proxy 組件則是 Service 的具體實現,了解了 kube-proxy 的工作原理,才能洞悉服務之間的通信流程,再遇到網絡不通時也不會壹臉懵逼。

kube-proxy 有三種模式: userspace 、 iptables 和 IPVS ,其中 userspace 模式不太常用。 iptables 模式最主要的問題是在服務多的時候產生太多的 iptables 規則,非增量式更新會引入壹定的時延,大規模情況下有明顯的性能問題。為解決 iptables 模式的性能問題,v1.11 新增了 IPVS 模式(v1.8 開始支持測試版,並在 v1.11 GA),采用增量式更新,並可以保證 service 更新期間連接保持不斷開。

目前網絡上關於 kube-proxy 工作原理的文檔幾乎都是以 iptables 模式為例,很少提及 IPVS ,本文就來破例解讀 kube-proxy IPVS 模式的工作原理。為了理解地更加徹底,本文不會使用 Docker 和 Kubernetes,而是使用更加底層的工具來演示。

我們都知道,Kubernetes 會為每個 Pod 創建壹個單獨的網絡命名空間 (Network Namespace) ,本文將會通過手動創建網絡命名空間並啟動 HTTP 服務來模擬 Kubernetes 中的 Pod。

本文的目標是通過模擬以下的 Service 來探究 kube-proxy 的 IPVS 和 ipset 的工作原理:

跟著我的步驟,最後妳就可以通過命令 curl 10.100.100.100:8080 來訪問某個網絡命名空間的 HTTP 服務。為了更好地理解本文的內容,推薦提前閱讀以下的文章:

首先需要開啟 Linux 的路由轉發功能:

接下來的命令主要做了這麽幾件事:

在網絡命名空間 netns_dustin 中啟動 HTTP 服務:

打開另壹個終端窗口,在網絡命名空間 netns_leah 中啟動 HTTP 服務:

測試各個網絡命名空間之間是否能正常通信:

整個實驗環境的網絡拓撲結構如圖:

為了便於調試 IPVS 和 ipset,需要安裝兩個 CLI 工具:

下面我們使用 IPVS 創建壹個虛擬服務 (Virtual Service) 來模擬 Kubernetes 中的 Service :

創建了虛擬服務之後,還得給它指定壹個後端的 Real Server ,也就是後端的真實服務,即網絡命名空間 netns_dustin 中的 HTTP 服務:

該命令會將訪問 10.100.100.100:8080 的 TCP 請求轉發到 10.0.0.11:8080 。這裏的 --masquerading 參數和 iptables 中的 MASQUERADE 類似,如果不指定,IPVS 就會嘗試使用路由表來轉發流量,這樣肯定是無法正常工作的。

測試是否正常工作:

實驗成功,請求被成功轉發到了後端的 HTTP 服務!

上面只是在 Host 的網絡命名空間中進行測試,現在我們進入網絡命名空間 netns_leah 中進行測試:

哦豁,訪問失敗!

要想順利通過測試,只需將 10.100.100.100 這個 IP 分配給壹個虛擬網絡接口。至於為什麽要這麽做,目前我還不清楚,我猜測可能是因為網橋 bridge_home 不會調用 IPVS,而將虛擬服務的 IP 地址分配給壹個網絡接口則可以繞過這個問題。

Netfilter 是壹個基於用戶自定義的 Hook 實現多種網絡操作的 Linux 內核框架。Netfilter 支持多種網絡操作,比如包過濾、網絡地址轉換、端口轉換等,以此實現包轉發或禁止包轉發至敏感網絡。

針對 Linux 內核 2.6 及以上版本,Netfilter 框架實現了 5 個攔截和處理數據的系統調用接口,它允許內核模塊註冊內核網絡協議棧的回調功能,這些功能調用的具體規則通常由 Netfilter 插件定義,常用的插件包括 iptables、IPVS 等,不同插件實現的 Hook 點(攔截點)可能不同。另外,不同插件註冊進內核時需要設置不同的優先級, 例如默認配置下,當某個 Hook 點同時存在 iptables 和 IPVS 規則時,iptables 會被優先處理。

Netfilter 提供了 5 個 Hook 點,系統內核協議棧在處理數據包時,每到達壹個 Hook 點,都會調用內核模塊中定義的處理函數。 調用哪個處理函數取決於數據包的轉發方向,進站流量和出站流量觸發的 Hook 點是不壹樣的。

內核協議棧中預定義的回調函數有如下五個:

iptables 實現了所有的 Hook 點,而 IPVS 只實現了 LOCAL_IN 、 LOCAL_OUT 、 FORWARD 這三個 Hook 點。既然沒有實現 PRE_ROUTING ,就不會在進入 LOCAL_IN 之前進行地址轉換,那麽數據包經過路由判斷後,會進入 LOCAL_IN Hook 點,IPVS 回調函數如果發現目標 IP 地址不屬於該節點,就會將數據包丟棄。

如果將目標 IP 分配給了虛擬網絡接口,內核在處理數據包時,會發現該目標 IP 地址屬於該節點,於是可以繼續處理數據包。

當然,我們不需要將 IP 地址分配給任何已經被使用的網絡接口,我們的目標是模擬 Kubernetes 的行為。Kubernetes 在這裏創建了壹個 dummy 接口,它和 loopback 接口類似,但是妳可以創建任意多的 dummy 接口。它提供路由數據包的功能,但實際上又不進行轉發。dummy 接口主要有兩個用途:

看來 dummy 接口完美符合實驗需求,那就創建壹個 dummy 接口吧:

將虛擬 IP 分配給 dummy 接口 dustin-ipvs0 :

到了這壹步,仍然訪問不了 HTTP 服務,還需要另外壹個黑科技: bridge-nf-call-iptables 。在解釋 bridge-nf-call-iptables 之前,我們先來回顧下容器網絡通信的基礎知識。

Kubernetes 集群網絡有很多種實現,有很大壹部分都用到了 Linux 網橋:

不管是 iptables 還是 ipvs 轉發模式,Kubernetes 中訪問 Service 都會進行 DNAT,將原本訪問 ClusterIP:Port 的數據包 DNAT 成 Service 的某個 Endpoint (PodIP:Port) ,然後內核將連接信息插入 conntrack 表以記錄連接,目的端回包的時候內核從 conntrack 表匹配連接並反向 NAT,這樣原路返回形成壹個完整的連接鏈路:

但是 Linux 網橋是壹個虛擬的二層轉發設備,而 iptables conntrack 是在三層上,所以如果直接訪問同壹網橋內的地址,就會直接走二層轉發,不經過 conntrack:

啟用 bridge-nf-call-iptables 這個內核參數 (置為 1),表示 bridge 設備在二層轉發時也去調用 iptables 配置的三層規則 (包含 conntrack),所以開啟這個參數就能夠解決上述 Service 同節點通信問題。

所以這裏需要啟用 bridge-nf-call-iptables :

現在再來測試壹下連通性:

終於成功了!

雖然我們可以從網絡命名空間 netns_leah 中通過虛擬服務成功訪問另壹個網絡命名空間 netns_dustin 中的 HTTP 服務,但還沒有測試過從 HTTP 服務所在的網絡命名空間 netns_dustin 中直接通過虛擬服務訪問自己,話不多說,直接測壹把:

啊哈?竟然失敗了,這又是哪裏的問題呢?不要慌,開啟 hairpin 模式就好了。那麽什麽是 hairpin 模式呢? 這是壹個網絡虛擬化技術中常提到的概念,也即交換機端口的VEPA模式。這種技術借助物理交換機解決了虛擬機間流量轉發問題。很顯然,這種情況下,源和目標都在壹個方向,所以就是從哪裏進從哪裏出的模式。

怎麽配置呢?非常簡單,只需壹條命令:

再次進行測試:

還是失敗了。。。

然後我花了壹個下午的時間,終於搞清楚了啟用混雜模式後為什麽還是不能解決這個問題,因為混雜模式和下面的選項要壹起啟用才能對 IPVS 生效:

最後再測試壹次:

這次終於成功了,但我還是不太明白為什麽啟用 conntrack 能解決這個問題,有知道的大神歡迎留言告訴我!

如果想讓所有的網絡命名空間都能通過虛擬服務訪問自己,就需要在連接到網橋的所有 veth 接口上開啟 hairpin 模式,這也太麻煩了吧。有壹個辦法可以不用配置每個 veth 接口,那就是開啟網橋的混雜模式。

什麽是混雜模式呢?普通模式下網卡只接收發給本機的包(包括廣播包)傳遞給上層程序,其它的包壹律丟棄。混雜模式就是接收所有經過網卡的數據包,包括不是發給本機的包,即不驗證MAC地址。

如果壹個網橋開啟了混雜模式,就等同於將所有連接到網橋上的端口(本文指的是 veth 接口)都啟用了 hairpin 模式 。可以通過以下命令來啟用 bridge_home 的混雜模式:

現在即使妳把 veth 接口的 hairpin 模式關閉:

仍然可以通過連通性測試:

在文章開頭準備實驗環境的章節,執行了這麽壹條命令:

這條 iptables 規則會對所有來自 10.0.0.0/24 的流量進行偽裝。然而 Kubernetes 並不是這麽做的,它為了提高性能,只對來自某些具體的 IP 的流量進行偽裝。

為了更加完美地模擬 Kubernetes,我們繼續改造規則,先把之前的規則刪除:

然後添加針對具體 IP 的規則:

果然,上面的所有測試都能通過。先別急著高興,又有新問題了,現在只有兩個網絡命名空間,如果有很多個怎麽辦,每個網絡命名空間都創建這樣壹條 iptables 規則?我用 IPVS 是為了啥?就是為了防止有大量的 iptables 規則拖垮性能啊,現在豈不是又繞回去了。

不慌,繼續從 Kubernetes 身上學習,使用 ipset 來解決這個問題。先把之前的 iptables 規則刪除:

然後使用 ipset 創建壹個集合 (set) :

這條命令創建了壹個名為 DUSTIN-LOOP-BACK 的集合,它是壹個 hashmap ,裏面存儲了目標 IP、目標端口和源 IP。

接著向集合中添加條目:

現在不管有多少網絡命名空間,都只需要添加壹條 iptables 規則:

網絡連通性測試也沒有問題:

最後,我們把網絡命名空間 netns_leah 中的 HTTP 服務也添加到虛擬服務的後端:

再向 ipset 的集合 DUSTIN-LOOP-BACK 中添加壹個條目:

終極測試來了,試著多運行幾次以下的測試命令:

妳會發現輪詢算法起作用了:

相信通過本文的實驗和講解,大家應該理解了 kube-proxy IPVS 模式的工作原理。在實驗過程中,我們還用到了 ipset,它有助於解決在大規模集群中出現的 kube-proxy 性能問題。如果妳對這篇文章有任何疑問,歡迎和我進行交流。