深入掌握Service
Service是Kubernetes實現微服務架構的核心概念,透過建立Service,可以為一組具有相同功能的容器應用提供一個統一的入口地址,並且將請求負載分發到後端的各個容器應用上。
Service定義
Service用於為一組提供服務的Pod抽象一個穩定的網路訪問地址,是Kubernetes實現微服務的核心概念。透過Service的定義設定的訪問地址是DNS域名格式的服務名稱,對於客戶端應用來說,網路訪問方式並沒有改變(DNS域名的作用等價於主機名、網際網路域名或IP地址)。
Service還提供了負載均衡器功能,將客戶端請求負載分發到後端提供具體服務的各個Pod上。
Service的概念和原理
Service主要用於提供網路服務,透過Service的定義,能夠為客戶端應用提供穩定的訪問地址(域名或IP地址)和負載均衡功能,以及遮蔽後端Endpoint的變化,是Kubernetes實現微服務的核心資源。
Service的概念
Service實現的是微服務架構中的幾個核心功能:全自動的服務註冊、服務發現、服務負載均衡等。
Service定義中的關鍵欄位是ports和selector。
本例中的ports定義部分指定了Service本身的埠號為8080,targetPort則用來指定後端Pod的容器埠號,selector定義部分設定的是後端Pod所擁有的label:app=webapp。
在提供服務的Pod副本集執行過程中,如果Pod列表發生了變化,則Kubernetes的Service控制器會持續監控後端Pod列表的變化,實時更新Service對應的後端Pod列表。
一個Service對應的“後端”由Pod的IP和容器埠號組成,即一個完整的“IP:Port”訪問地址,這在Kubernetes系統中叫作Endpoint。
Service不僅具有標準網路協議的IP地址,還以DNS域名的形式存在。Service的域名錶示方法為<servicename>.<namespace>.svc.<clusterdomain>,
servicename為服務的名稱,
namespace為其所在namespace的名稱,
clusterdomain為Kubernetes叢集設定的域名字尾。
Service的負載均衡機制
當一個Service物件在Kubernetes叢集中被定義出來時,叢集內的客戶端應用就可以透過服務IP訪問到具體的Pod容器提供的服務了。從服務IP到後端Pod的負載均衡機制,則是由每個Node上的kube-proxy負責實現的。
kube-proxy的代理模式
目前kube-proxy提供了以下代理模式(透過啟動引數--proxy-mode設定)。
-
userspace模式:使用者空間模式,由kube-proxy完成代理的實現,效率最低,不再推薦使用。
-
iptables模式:kube-proxy透過設定Linux Kernel的iptables規則,實現從Service到後端Endpoint列表的負載分發規則,效率很高。但是,如果某個後端Endpoint在轉發時不可用,此次客戶端請求就會得到失敗的響應,相對於userspace模式來說更不可靠。此時應該透過為Pod設定readinessprobe(服務可用性健康檢查)來保證只有達到ready狀態的Endpoint才會被設定為Service的後端Endpoint。
-
ipvs模式:在Kubernetes 1.11版本中達到Stable階段,kube-proxy透過設定Linux Kernel的netlink介面設定IPVS規則,轉發效率和支援的吞吐率都是最高的。ipvs模式要求Linux Kernel啟用IPVS模組,如果作業系統未啟用IPVS核心模組,kube-proxy則會自動切換至iptables模式。同時,ipvs模式支援更多的負載均衡策略,如下所述。
- rr:round-robin,輪詢。
- lc:least connection,最小連線數。
- dh:destination hashing,目的地址雜湊。
- sh:source hashing,源地址雜湊。
- sed:shortest expected delay,最短期望延時。
- nq:never queue,永不排隊。
-
kernelspace模式:Windows Server上的代理模式。
會話保持機制
Service支援透過設定sessionAffinity實現基於客戶端IP的會話保持機制,即首次將某個客戶端來源IP發起的請求轉發到後端的某個Pod上,之後從相同的客戶端IP發起的請求都將被轉發到相同的後端Pod上,配置引數為service.spec.sessionAffinity
透過Service的負載均衡機制,Kubernetes實現了一種分散式應用的統一入口,免去了客戶端應用獲知後端服務例項列表和變化的複雜度。
Service的多埠設定
一個容器應用可以提供多個埠的服務,在Service的定義中也可以相應地設定多個埠號。
是同一個埠號使用的協議不同,如TCP和UDP,也需要設定為多個埠號來提供不同的服務
將外部服務定義為Service
將Service暴露到叢集外部
Kubernetes為Service建立的ClusterIP地址是對後端Pod列表的一層抽象,對於叢集外部來說並沒有意義,但有許多Service是需要對叢集外部提供服務的,Kubernetes提供了多種機制將Service暴露出去,供叢集外部的客戶端訪問。這可以透過Service資源物件的型別欄位“type”進行設定。
目前Service的型別如下。
- ClusterIP:Kubernetes預設會自動設定Service的虛擬IP地址,僅可被叢集內部的客戶端應用訪問。當然,使用者也可手工指定一個ClusterIP地址,不過需要確保該IP在Kubernetes叢集設定的ClusterIP地址範圍內(透過kube-apiserver服務的啟動引數--service-cluster-ip-range設定),並且沒有被其他Service使用。
- NodePort:將Service的埠號對映到每個Node的一個埠號上,這樣叢集中的任意Node都可以作為Service的訪問入口地址,即NodeIP:NodePort。
- LoadBalancer:將Service對映到一個已存在的負載均衡器的IP地址上,通常在公有云環境中使用。
- ExternalName:將Service對映為一個外部域名地址,透過externalName欄位進行設定。
Kubernetes的服務發現機制
服務發現機制指客戶端應用在一個Kubernetes叢集中如何獲知後端服務的訪問地址。Kubernetes提供了兩種機制供客戶端應用以固定的方式獲取後端服務的訪問地址:環境變數方式和DNS方式。
Headless Service的概念和應用
在某些應用場景中,客戶端應用不需要透過Kubernetes內建Service實現的負載均衡功能,或者需要自行完成對服務後端各例項的服務發現機制,或者需要自行實現負載均衡功能,此時可以透過建立一種特殊的名為“Headless”的服務來實現。
Headless Service的概念是這種服務沒有入口訪問地址(無ClusterIP地址),kube-proxy不會為其建立負載轉發規則,而服務名(DNS域名)的解析機制取決於該Headless Service是否設定了Label Selector。
如果Headless Service設定了Label Selector,Kubernetes則將根據Label Selector查詢後端Pod列表,自動建立Endpoint列表,將服務名(DNS域名)的解析機制設定為:當客戶端訪問該服務名時,得到的是全部Endpoint列表(而不是一個確定的IP地址)。
端點分片與服務拓撲
我們知道,Service的後端是一組Endpoint列表,為客戶端應用提供了極大的便利。但是隨著叢集規模的擴大及Service數量的增加,特別是Service後端Endpoint數量的增加,kube-proxy需要維護的負載分發規則(例如iptables規則或ipvs規則)的數量也會急劇增加,導致後續對Service後端Endpoint的新增、刪除等更新操作的成本急劇上升。
舉例來說,假設在Kubernetes叢集中有10000個Endpoint執行在大約5000個Node上,則對單個Pod的更新將需要總計約5GB的資料傳輸,這不僅對叢集內的網路頻寬浪費巨大,而且對Master的衝擊非常大,會影響Kubernetes叢集的整體效能,在Deployment不斷進行滾動升級操作的情況下尤為突出。
Kubernetes從1.16版本開始引入端點分片(Endpoint Slices)機制,包括一個新的EndpointSlice資源物件和一個新的EndpointSlice控制器,在1.17版本時達到Beta階段。EndpointSlice透過對Endpoint進行分片管理來實現降低Master和各Node之間的網路傳輸資料量及提高整體效能的目標。對於Deployment的滾動升級,可以實現僅更新部分Node上的Endpoint資訊,Master與Node之間的資料傳輸量可以減少100倍左右,能夠大大提高管理效率。EndpointSlice根據Endpoint所在Node的拓撲資訊進行分片管理
Endpoint Slices要實現的第2個目標是為基於Node拓撲的服務路由提供支援,這需要與服務拓撲(Service Topology)機制共同實現。
預設情況下,在由EndpointSlice控制器建立的EndpointSlice中最多包含100個Endpoint,如需修改,則可以透過kube-controller-manager服務的啟動引數--max-endpoints-per-slice設定,但上限不能超過1000。
服務拓撲(Service Topology)
服務拓撲機制從Kubernetes 1.17版本開始引入,目前為Alpha階段,目標是實現基於Node拓撲的流量路由,例如將傳送到某個服務的流量優先路由到與客戶端相同Node的Endpoint上,或者路由到與客戶端相同Zone的那些Node的Endpoint上。
在預設情況下,傳送到一個Service的流量會被均勻轉發到每個後端Endpoint,但無法根據更復雜的拓撲資訊設定複雜的路由策略。服務拓撲機制的引入就是為了實現基於Node拓撲的服務路由,允許Service建立者根據來源Node和目標Node的標籤來定義流量路由策略。
透過對來源(source)Node和目標(destination)Node標籤的匹配,使用者可以根據業務需求對Node進行分組,設定有意義的指標值來標識“較近”或者“較遠”的屬性。例如,對於公有云環境來說,通常有區域(Zone或Region)的劃分,雲平臺傾向於把服務流量限制在同一個區域內,這通常是因為跨區域網路流量會收取額外的費用。另一個例子是把流量路由到由DaemonSet管理的當前Node的Pod上。又如希望把流量保持在相同機架內的Node上,以獲得更低的網路延時。
服務拓撲機制需要透過設定kube-apiserver和kube-proxy服務的啟動引數--feature-gates="ServiceTopology=true,EndpointSlice=true"進行啟用(需要同時啟用EndpointSlice功能),然後就可以在Service資源物件上透過定義topologyKeys欄位來控制到Service的流量路由了。
topologyKeys欄位設定的是一組Node標籤列表,按順序匹配Node完成流量的路由轉發,流量會被轉發到標籤匹配成功的Node上。如果按第1個標籤找不到匹配的Node,就嘗試匹配第2個標籤,以此類推。如果全部標籤都沒有匹配的Node,則請求將被拒絕,就像Service沒有後端Endpoint一樣。