K8S網路之Service網路

K8S網路之Pod網路

K8S網路之Service網路

有了pod網路,k8s叢集當中的所有的pod在邏輯上都可以看做在一個平面網路內可以正常的做ip定址和互通,

但是這個pod僅僅是k8s雲平臺當中的虛擬機器抽象,最終我們需要在k8s叢集當中執行的是應用或者服務service,

而一個service背後一般是多個pod組成的叢集,這個時候就引入了服務發現discover和負載均衡load balancer,

這些問題就是第二層service網路要解決的問題。

Service網路的概念模型

K8S網路之Service網路

假定第一層的pod網路已經存在了,account應用4個pod或虛擬機器組成的叢集一起提供服務,每個pod都有自己的pod ip和埠號,再假定叢集內還部署了其他的應用,這些應用有些是account app的消費方,也就說有些client pod要訪問account-service,這個時候就自然引入2個問題:

一個是服務發現,client pod如何發現並且定位account app叢集中的pod ip,況且account app叢集中的ip有可能會變(逾期的情況,比如重新發布;非逾期的情況, 比如pod掛了,k8s重新排程部署,pod ip也會變化)。

另外一個是client pod要以某種負載均衡策略訪問account app叢集中不同的pod例項來實現負載分攤和高可用的訪問。

那麼實際上k8s透過在client和account app pod叢集之間引入一層account service抽象來解決服務發現和負載均衡的問題。

cluster ip來解決服務發現問題,client不關心pod叢集中具體的pod數量和pod ip,即使pod ip發生變化也會被cluster ip所遮蔽(注意這裡的cluster ip是虛擬ip),支援以不同的策略去訪問pod叢集中的不同的pod例項以實現負載分攤還有HA高可用。

k8s當中預設的負載均衡策略是Round-Robin(輪詢排程演算法的原理是每一次把來自使用者的請求輪流分配給內部中的伺服器,從1開始,直到N(內部伺服器個數),然後重新開始迴圈),也可以採用其他的複雜的負載均衡策略。

k8s當中為什麼要引入service 抽象,背後的原理是什麼?

DNS方案

K8S網路之Service網路

解決服務發現問題,其中比較古老的技術DNS是最早的一種服務發現技術。

假設在k8s當中引入DNS來解決服務發現,實際上k8s本身就支援DNS這樣的元件。

k8s可以把pod叢集的pod ip和埠註冊到DNS上,client應用透過查詢DNS就可以發現目標pod,就可以發起呼叫。

這個方案不僅簡單而且對client無侵入,因為目前幾乎所有的作業系統都自帶這個DNS Client,但是基於DNS的服務發現有下面幾個問題:

不同DNS client實現不同的DNS功能是有差異的,有些客戶端每次呼叫都會去查詢DNS服務造成不必要的開銷。另外有些客戶端會快取DNS的資訊,預設的超時時間可能設定的比較長,當目標例項pod ip發生變化的時候,就會存在快取重新整理不及時的問題,會導致訪問pod失效。

實現的負載均衡策略都比較簡單,比如Round-Robin,有些甚至還不支援負載均衡呼叫。

考慮上述DNS客戶端實現功能的差異 ,這些都不在k8s的控制範圍內,所以k8s並沒有直接採用DNS來做服務發現。

k8s實際還是引入了Kube-DNS來實現透過域名訪問服務,不過這個是建立在cluster ip和service網路之上的。

Service Registry + Client方案

K8S網路之Service網路

在當前微服務時代這個是比較流行的做法,這個方案典型的代表比如Eureka+Ribbon/Consul/Nacos。

k8s自身就帶有分散式儲存etcd,就可以實現service registry叢集。

k8s把account-app pod叢集的資訊(ip和埠)自動註冊到service registry,client 應用透過service registry可以查詢和發現這些目標pod,然後就可以發起呼叫。

這個方案實現也不復雜,客戶端可以實現比較靈活的負載均衡策略,但是需要客戶端進行配合,對客戶應用是有侵入性的,所以k8s也沒有直接採用這種方案。

K8S網路之Service網路

這種service網路實現方案是在前面兩種方案的基礎之上發展演化出來的,融合了前面兩種技術的優點同時也解決了它們的不足,下面來剖析下k8s網路具體的實現原理。

在k8s平臺的每個worker節點上面都部署2個元件(kubelet和kube-proxy),這兩個元件和master是實現k8s service網路的關鍵,pod例項釋出的時候 ,kubelet會負責啟動這些pod例項,啟動完之後,kubelet會把服務的pod ip列表彙報註冊到master節點上,這就是服務註冊。

k8s當中有service釋出的概念,k8s會為服務分配相應的cluster ip,相關的資訊也會記錄在cluster上面,並且cluster ip和pod ip是有對映關係的。

kube-proxy會監聽master,並且發現服務的cluster ip和pod之間的對映的列表,並且修改本地的linux iptables的轉發規則,這個iptables的轉發規則在接受到某個cluser ip請求的時候進行負載均衡並且轉發到對應的pod ip上面去,這個就是服務發現的過程。

在實際執行的時候,當有消費者pod需要訪問某個服務的時候,就透過cluser ip發請求,這個cluser ip呼叫會被本地的iptables所截獲,然後透過負載均衡轉發到目標服務的這些pod例項上面去,這就是服務實際呼叫時候的過程。

實際消費者pod也不是直接呼叫服務的cluster ip的,而是先呼叫服務名,因為cluster ip也可能會變化,比如針對不同的環境下(test環境、uat環境),只有服務名稱一般是不變的,為了遮蔽cluster ip的變化,k8s在每個worker節點上面還有一個Kube-DNS元件,它也監聽master節點並且發現cluster ip和服務名它們之間的對映關係,這樣消費者pod透過Kube-DNS可以間接的發現cluster ip,然後cluster ip就可以透過iptables轉發到這些目標pod上面去,這就是k8s內部完整的服務註冊發現和呼叫的流程。

k8s服務註冊發現機制和目前主流的微服務註冊發現機制(比如Eureka和Ribbon)區別?

原理是類似的,但是也有一些顯著的區別 ,這些區別主要體現在客戶端:

首先二者都是採用客戶端代理proxy的機制,和ribbon一樣,k8s的代理轉發和負載均衡也是在客戶端實現的,但是Ribbon的話,它是以library的形式,嵌入客戶應用當中的,有侵入性。而k8s的kube-proxy是獨立的元件,每個worker節點上面的都有一個kube-proxy,對客戶應用是無侵入的,k8s的做法類似Service Mesh當中的邊車sidecar。

第二點區別Ribbon轉發是通透的,而k8s中的轉發是透過iptables轉發,雖然k8s有Kube-Proxy,但它只負責服務發現和修改iptables或者ipvs的規則,實際請求是不穿透Kube Proxy的。早期的Kube-Proxy是代理穿透的 考慮到有效能損耗和單點問題,新的版本的k8s就改成iptables直接轉發,不穿透Kube-Proxy。

第三點差別Ribbon實現服務名到服務例項ip地址的對映,只有一層對映。在k8s當中是有兩層對映的,Kube-Proxy是實現cluster ip到pod ip的對映,Kube-DNS是實現服務名到cluster ip的對映,是第二層對映地址。

個人認為對比目前主流的微服務發現機制,k8s服務發現機制抽象的更好,透過cluster ip統一遮蔽服務發現和負載均衡的問題,一個服務就是一個cluster ip,這個模型和傳統的ip網路模型更貼近和更易於理解。

cluster ip是一個ip,但是這個ip後面跟的不是一個服務例項,而是一個服務叢集,同時對客戶應用是無侵入的,不穿透這個proxy,所以沒有額外的效能損耗。

總結

k8s的service網路構建與pod網路之上的,主要目標是解決服務發現和負載均衡。

k8s當中透過service name+cluster ip可以統一遮蔽服務發現和負載均衡,底層技術是DNS+service registry基礎之上發展演變而來。

k8s的服務發現和負載均衡是透過kube-proxy和iptables實現的,對應用是無侵入的,而且不穿透這個proxy,效能損耗比較小。

k8s的服務發現機制是現代服務發現機制和傳統的linux核心機制的結合。