天天看點

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

作者:shengdong

以我的經驗來講,了解K8S叢集服務的概念,是比較不容易的一件事情。尤其是當我們基于似是而非的了解,去排查服務相關問題的時候,會非常不順利。

這展現在,對于新手來說,ping不通服務的IP位址這樣基礎的問題,都很難了解;而就算對經驗很豐富的工程師來說,看懂服務相關的iptables配置,也是相當的挑戰。

今天這邊文章,我來深入解釋一下K8S叢集服務的原理與實作,便于大家了解。

K8S叢集服務的本質是什麼

概念上來講,K8S叢集的服務,其實就是負載均衡、或反向代理。這跟阿裡雲的負載均衡産品,有很多類似的地方。和負載均衡一樣,服務有它的IP位址以及前端端口;服務後邊會挂載多個容器組Pod作為其“後端伺服器”,這些“後端伺服器”有自己的IP以及監聽端口。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

當這樣的負載均衡和後端的架構,與K8S叢集結合的時候,我們可以想到的最直覺的實作方式,就是叢集中某一個節點專門做負載均衡(類似LVS)的角色,而其他節點則用來負載後端容器組。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

這樣的實作方法,有一個巨大的缺陷,就是單點問題。K8S叢集是Google多年來自動化運維實踐的結晶,這樣的實作顯然與其智能運維的哲學相背離的。

自帶通信員

邊車模式(Sidecar)是微服務領域的核心概念。邊車模式,換一句通俗一點的說法,就是自帶通信員。熟悉服務網格的同學肯定對這個很熟悉了。但是可能比較少人注意到,其實K8S叢集原始服務的實作,也是基于Sidecar模式的。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

在K8S叢集中,服務的實作,實際上是為每一個叢集節點上,部署了一個反向代理Sidecar。而所有對叢集服務的通路,都會被節點上的反向代理轉換成對服務後端容器組的通路。基本上來說,節點和這些Sidecar的關系如下圖所示。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

把服務照進現實

前邊兩節,我們看到了,K8S叢集的服務,本質上是負載均衡,即反向代理;同時我們知道了,在實際實作中,這個反向代理,并不是部署在叢集某一個節點上,而是作為叢集節點的邊車,部署在每個節點上的。

在這裡把服務照進反向代理這個現實的,是K8S叢集的一個控制器,即kube-proxy。關于K8S叢集控制器的原理,請參考我另外一篇關于控制器的文章。簡單來說,kube-proxy作為部署在叢集節點上的控制器,它們通過叢集API Server監聽着叢集狀态變化。當有新的服務被建立的時候,kube-proxy則會把叢集服務的狀态、屬性,翻譯成反向代理的配置。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

那剩下的問題,就是反向代理,即上圖中Proxy的實作。

一種實作

K8S叢集節點實作服務反向代理的方法,目前主要有三種,即userspace、iptables以及ipvs。今天我們隻深入分析iptables的方式,底層網絡基于阿裡雲flannel叢集網絡。

過濾器架構

現在,我們來設想一種場景。我們有一個屋子。這個屋子有一個入水管和出水管。從入水管進入的水,是不能直接飲用的,因為有雜質。而我們期望,從出水管流出的水,可以直接飲用。為了達到目的,我們切開水管,在中間加一個雜質過濾器。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

過了幾天,我們的需求變了,我們不止要求從屋子裡流出來的水可以直接飲用,我們還希望水是熱水。是以我們不得不再在水管上增加一個切口,然後增加一個加熱器。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

很明顯,這種切開水管,增加新功能的方式是很醜陋的。因為需求可能随時會變,我們甚至很難保證,在經過一年半載之後,這跟水管還能找得到可以被切開的地方。

是以我們需要重新設計。首先我們不能随便切開水管,是以我們要把水管的切口固定下來。以上邊的場景為例,我們確定水管隻能有一個切口位置。其次,我們抽象出對水的兩種處理方式:實體變化和化學變化。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

基于以上的設計,如果我們需要過濾雜質,就可以在化學變化這個功能子產品裡增加一條過濾雜質的規則;如果我們需要增加溫度的話,就可以在實體變化這個功能子產品裡增加一條加熱的規則。

以上的過濾器架構,顯然比切水管的方式,要優秀很多。設計這個架構,我們主要做了兩件事情,一個是固定水管切口位置,另外一個是抽象出兩種水處理方式。

了解這兩件事情之後,我們可以來看下iptables,或者更準确的說法,netfilter的工作原理。netfilter實際上就是一個過濾器架構。netfilter在網絡包收發及路由的管道上,一共切了5個口,分别是PREROUTING,FORWARD,POSTROUTING,INPUT以及OUTPUT;同時netfilter定義了包括nat、filter在内的若幹個網絡包處理方式。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

需要注意的是,routing和forwarding很大程度上增加了以上netfilter的複雜程度,如果我們不考慮routing和forwarding,那麼netfilter會變得更我們的水質過濾器架構一樣簡單。

節點網絡大圖

現在我們看一下K8S叢集節點的網絡全貌。橫向來看,節點上的網絡環境,被分割成不同的網絡命名空間,包括主機網絡命名空間和Pod網絡命名空間;縱向來看,每個網絡命名空間包括完整的網絡棧,從應用到協定棧,再到網絡裝置。

在網絡裝置這一層,我們通過cni0虛拟網橋,組建出系統内部的一個虛拟區域網路。Pod網絡通過veth對連接配接到這個虛拟區域網路内。cni0虛拟區域網路通過主機路由以及網口eth0與外部通信。

在網絡協定棧這一層,我們可以通過程式設計netfilter過濾器架構,來實作叢集節點的反向代理。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

實作反向代理,歸根結底,就是做DNAT,即把發送給叢集服務IP和端口的資料包,修改成發給具體容器組的IP和端口。

參考netfilter過濾器架構的圖,我們知道,在netfilter裡,可以通過在PREROUTING,OUTPUT以及POSTROUGING三個位置加入NAT規則,來改變資料包的源位址或目的位址。

因為這裡需要做的是DNAT,即改變目的位址,這樣的修改,必須在路由(ROUTING)之前發生以保證資料包可以被路由正确處理,是以實作反向代理的規則,需要被加到PREROUTING和OUTPUT兩個位置。

其中,PREOURTING的規則,用來處理從Pod通路服務的流量。資料包從Pod網絡veth發送到cni0之後,進入主機協定棧,首先會經過netfilter PREROUTING來做處理,是以發給服務的資料包,會在這個位置做DNAT。經過DNAT處理之後,資料包的目的位址變成另外一個Pod的位址,進而經過主機路由,轉發到eth0,發送給正确的叢集節點。

而添加在OUTPUT這個位置的DNAT規則,則用來處理從主機網絡發給服務的資料包,原理也是類似,即經過路由之前,修改目的位址,以友善路由轉發。

更新過濾器架構

在過濾器架構一節,我們看到netfilter是一個過濾器架構。netfilter在資料“管到”上切了5個口,分别在這5個口上,做一些資料包處理工作。雖然固定切口位置以及網絡包處理方式分類已經極大的優化了過濾器架構,但是有一個關鍵的問題,就是我們還是得在管道上做修改以滿足新的功能。換句話說,這個架構沒有做到管道和過濾功能兩者的徹底解耦。

為了實作管道和過濾功能兩者的解耦,netfilter用了表這個概念。表就是netfilter的過濾中心,其核心功能是過濾方式的分類(表),以及每種過濾方式中,過濾規則的組織(鍊)。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

把過濾功能和管道解耦之後,所有對資料包的處理,都變成了對表的配置。而管道上的5個切口,僅僅變成了流量的出入口,負責把流量發送到過濾中心,并把處理之後的流量沿着管道繼續傳送下去。

如上圖,在表中,netfilter把規則組織成為鍊。表中有針對每個管道切口的預設鍊,也有我們自己加入的自定義鍊。預設鍊是資料的入口,預設鍊可以通過跳轉到自定義鍊來完成一些複雜的功能。這裡允許增加自定義鍊的好處是顯然的。為了完成一個複雜過濾功能,比如實作K8S叢集節點的反向代理,我們可以使用自定義鍊來子產品化我們規則。

用自定義鍊實作服務的反向代理

叢集服務的反向代理,實際上就是利用自定義鍊,子產品化地實作了資料包的DNAT轉換。KUBE-SERVICE是整個反向代理的入口鍊,其對應所有服務的總入口;KUBE-SVC-XXXX鍊是具體某一個服務的入口鍊,KUBE-SERVICE鍊會根據服務IP,跳轉到具體服務的KUBE-SVC-XXXX鍊;而KUBE-SEP-XXXX鍊代表着某一個具體Pod的位址和端口,即endpoint,具體服務鍊KUBE-SVC-XXXX會以一定算法(一般是随機),跳轉到endpoint鍊。

K8S從懵圈到熟練 – 叢集服務的三個要點和一種實作

而如前文中提到的,因為這裡需要做的是DNAT,即改變目的位址,這樣的修改,必須在路由之前發生以保證資料包可以被路由正确處理。是以KUBE-SERVICE會被PREROUTING和OUTPUT兩個預設鍊所調用。

總結

通過這篇文章,大家應該對K8S叢集服務的概念以及實作,有了更深層次的認識。我們基本上需要把握三個要點。一、服務本質上是負載均衡;二、服務負載均衡的實作采用了與服務網格類似的Sidecar的模式,而不是LVS類型的獨占模式;三、kube-proxy本質上是一個叢集控制器。除此之外,我們思考了過濾器架構的設計,并在此基礎上,了解使用iptables實作的服務負載均衡的原理。

繼續閱讀