七、Kubernetes資源物件之Service基礎
一、Service資源及其實現模型
1、service的資源概述
Service是Kubernetes的核心資源型別之一,通常被看作微服務的一種實現。它事實上是一種抽象:通過規則定義出由多個Pod物件組合而成的邏輯集合,以及訪問這組Pod的策略。Service關聯Pod資源的規則要藉助標籤選擇器完成。
作為一款容器編排系統,託管在Kubernetes之上、以Pod形式執行的應用程序的生命週期通常受控於Deployment或StatefulSet一類的控制器,由於節點故障或驅離等原因導致Pod物件中斷後,會由控制器自動建立的新物件所取代,而擴縮容或更新操作更是會帶來Pod物件的群體變動。因為編排系統需要確保服務在編排操作導致的應用Pod動態變動的過程中始終可訪問,所以Kubernetes提出了滿足這一關鍵需求的解決方案,即核心資源型別——Service。
app1的Pod作為客戶端訪問app2相關的Pod應用時,IP的變動或應用規模的縮減會導致客戶端訪問錯誤,而Pod規模的擴容又會使客戶端無法有效使用新增的Pod物件,影響達成規模擴充套件的目的。
Service資源基於標籤選擇器把篩選出的一組Pod物件定義成一個邏輯組合,並通過自己的IP地址和埠將請求分發給該組內的Pod物件,如下圖所示。Service向客戶端隱藏了真實的處理使用者請求的Pod資源,使得客戶端的請求看上去是由Service直接處理並進行響應。
Service物件的IP地址(可稱為ClusterIP或ServiceIP)是虛擬IP地址,由Kubernetes系統在Service物件建立時在專用網路(Service Network)地址中自動分配或由使用者手動指定,並且在Service物件的生命週期中保持不變。Service基於埠過濾到達其IP地址的客戶端請求,並根據定義將請求轉發至其後端的Pod物件的相應埠之上,因此這種代理機制也稱為“埠代理”或四層代理,工作於TCP/IP協議棧的傳輸層。 Service物件會通過API Server持續監視(watch)標籤選擇器匹配到的後端Pod物件,並實時跟蹤這些Pod物件的變動情況,例如IP地址變動以及Pod物件的增加或刪除等。不過,Service並不直接連線至Pod物件,它們之間還有一箇中間層——Endpoints資源物件,該資源物件是一個由IP地址和埠組成的列表,這些IP地址和埠則來自由Service的標籤選擇器匹配到的Pod物件。這也是很多場景中會使用“Service的後端端點”這一術語的原因。預設情況下,建立Service資源物件時,其關聯的Endpoints物件會被自動建立。
本質上來講,一個Service物件對應於工作節點核心之中的一組iptables或/和ipvs規則,這些規則能夠將到達Service物件的ClusterIP的流量排程轉發至相應Endpoint物件指向的IP地址和埠之上。核心中的iptables或ipvs規則的作用域僅為其所在工作節點的一個主機,因而生效於叢集範圍內的Service物件就需要在每個工作節點上都生成相關規則,從而確保任一節點上發往該Service物件請求的流量都能被正確轉發。
每個工作節點的kube-proxy元件通過API Server持續監控著各Service及其關聯的Pod物件,並將Service物件的建立或變動實時反映至當前工作節點上相應的iptables或ipvs規則上。客戶端、Service及Pod物件的關係如下圖所示。
提示 Netfilter是Linux核心中用於管理網路報文的框架,它具有網路地址轉換(NAT)、報文改動和報文過濾等防火牆功能,使用者可藉助使用者空間的iptables等工具按需自由定製規則使用其各項功能。
ipvs是藉助於Netfilter實現的網路請求報文排程框架,支援rr、wrr、lc、wlc、sh、sed和nq等10餘種排程演算法,使用者空間的命令列工具是ipvsadm,用於管理工作於ipvs之上的排程規則。
2.1 userpace代理模式
userspace是指Linux作業系統的使用者空間。在這種模型中,kube-proxy負責跟蹤API Server上Service和Endpoints物件的變動(建立或移除),並據此調整Service資源的定義。對於每個Service物件,它會隨機開啟一個本地埠(運行於使用者空間的kube-proxy程序負責監聽),任何到達此代理埠的連線請求都將被代理至當前Service資源後端的各Pod物件,至於哪個Pod物件會被選中則取決於當前Service資源的排程方式,預設排程演算法是輪詢(round-robin)。userspace代理模型工作邏輯如圖所示。另外,此類Service物件還會建立iptables規則以捕獲任何到達ClusterIP和埠的流量。在Kubernetes 1.1版本之前,userspace是預設的代理模型。
2.2 iptables代理模式
建Service物件的操作會觸發叢集中的每個kube-proxy並將其轉換為定義在所屬節點上的iptables規則,用於轉發工作介面接收到的、與此Service資源ClusterIP和埠相關的流量。客戶端發來請求將直接由相關的iptables規則進行目標地址轉換(DNAT)後根據演算法排程並轉發至叢集內的Pod物件之上,而無須再經由kube-proxy程序進行處理,因而稱為iptables代理模型,如圖所示。對於每個Endpoints物件,Service資源會為其建立iptables規則並指向其iptables地址和埠,而流量轉發到多個Endpoint物件之上的預設排程機制是隨機演算法。iptables代理模型由Kubernetes v1.1版本引入,並於v1.2版本成為預設的型別。
2.3 ipvs代理模式
Kubernetes自v1.9版本起引入ipvs代理模型,且自v1.11版本起成為預設設定。在此種模型中,kube-proxy跟蹤API Server上Service和Endpoints物件的變動,並據此來呼叫netlink介面建立或變更ipvs(NAT)規則,如圖所示。它與iptables規則的不同之處僅在於客戶端請求流量的排程功能由ipvs實現,餘下的其他功能仍由iptables完成。
3、service資源型別
無論哪一種代理模型,Service資源都可統一根據其工作邏輯分為ClusterIP、NodePort、LoadBalancer和ExternalName這4種類型。
(1)ClusterIP 通過叢集內部IP地址暴露服務,ClusterIP地址僅在叢集內部可達,因而無法被叢集外部的客戶端訪問。此為預設的Service型別。
(2)NodePort NodePort型別是對ClusterIP型別Service資源的擴充套件,它支援通過特定的節點埠接入叢集外部的請求流量,並分發給後端的Server Pod處理和響應。因此,這種型別的Service既可以被叢集內部客戶端通過ClusterIP直接訪問,也可以通過套接字<NodeIP>: <NodePort>與叢集外部客戶端進行通訊,如圖所示。顯然,若叢集外部的請求報文首先到的節點並非Service排程的目標Server Pod所在的節點,該請求必然因需要額外的轉發過程(躍點)和更多的處理步驟而產生更多延遲。
(3)LoadBalancer 這種型別的Service依賴於部署在IaaS雲端計算服務之上並且能夠呼叫其API介面建立軟體負載均衡器的Kubernetes叢集環境。LoadBalancer Service構建在NodePort型別的基礎上,通過雲服務商提供的軟負載均衡器將服務暴露到叢集外部,因此它也會具有NodePort和ClusterIP。簡言之,建立LoadBalancer型別的Service物件時會在叢集上建立一個NodePort型別的Service,並額外觸發Kubernetes呼叫底層的IaaS服務的API建立一個軟體負載均衡器,而叢集外部的請求流量會先路由至該負載均衡器,並由該負載均衡器排程至各節點上該Service物件的NodePort,如圖所示。該Service型別的優勢在於,它能夠把來自叢集外部客戶端的請求排程至所有節點(或部分節點)的NodePort之上,而不是讓客戶端自行決定連線哪個節點,也避免了因客戶端指定的節點故障而導致的服務不可用。
二、應用service資源
Service是Kubernetes核心API群組(core)中的標準資源型別之一。Service資源配置規範中常用的欄位及意義如下所示。
apiVersion: v1 kind: Service metadata: name: … namespace: … spec: type <string> # Service型別,預設為ClusterIP(NodePort、ClusterIP、LoadBalancer、ExternalName) selector <map[string]string> # 等值型別的標籤選擇器,內含“與”邏輯 ports: # Service的埠物件列表 - name <string> # 埠名稱 protocol <string> # 協議,目前僅支援TCP、UDP和SCTP,預設為TCP port <integer> # Service的埠號 targetPort <string> # 後端目標程序的埠號或名稱,名稱需由Pod規範定義 nodePort <integer> # 節點埠號,僅適用於NodePort和LoadBalancer型別 clusterIP <string> # Service的叢集IP,建議由系統自動分配 externalTrafficPolicy <string> # 外部流量策略處理方式,Local表示由當前節點處理, # Cluster表示向叢集範圍內排程 loadBalancerIP <string> # 外部負載均衡器使用的IP地址,僅適用於LoadBlancer externalName <string> # 外部服務名稱,該名稱將作為Service的DNS CNAME值
1、應用ClusrerIP Service資源
建立Service物件的常用方法有兩種:一是利用此前曾使用過的kubectl create service命令建立,另一個則是利用資源配置清單建立。Service資源物件的期望狀態定義在spec欄位中,較為常用的內嵌欄位為selector和ports,用於定義標籤選擇器和服務埠。下面的配置清單是定義在services-clusterip-demo.yaml中的一個Service資源示例:
apiVersion: v1 kind: Service metadata: name: demoapp-svc namespace: default spec: selector: app: demoapp ports: - name: http #埠名稱標識 protocol: TCP #協議,支援TCP\UDP\SCTP,預設為TCP port: 80 #Service自身的埠號 targetPort: 80 #目標埠號,即endpoint上定義的埠號
Service資源的spec.selector僅支援以對映(字典)格式定義的等值型別的標籤選擇器,例如上面示例中的app: demoapp。定義服務埠的欄位spec.ports的值則是一個物件列表,它主要定義Service物件自身的埠與目標後端埠的對映關係。我們可以將示例中的Service物件創建於叢集中,通過其詳細描述瞭解其特性,如下面的命令及結果所示。
root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl apply -f services-clusterip-demo.yaml service/demoapp-svc created root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl get services -n default NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE demoapp-svc ClusterIP 10.68.106.128 <none> 80/TCP 50s kubernetes ClusterIP 10.68.0.1 <none> 443/TCP 3d13h root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl describe services demoapp-svc -n default Name: demoapp-svc Namespace: default Labels: <none> Annotations: <none> Selector: app=demoapp Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.68.106.128 IPs: 10.68.106.128 Port: http 80/TCP TargetPort: 80/TCP Endpoints: <none> #沒有與標籤app=demoapp匹配的pod物件 Session Affinity: None Events: <none>
Service物件自身只是iptables或ipvs規則,它並不能處理客戶端的服務請求,而是需要把請求報文通過目標地址轉換(DNAT)後轉發至後端某個Server Pod,這意味著沒有可用的後端端點的Service物件是無法響應客戶端任何服務請求的,如下面從叢集節點上發起的請求命令結果所示。
root@k8s-master01:/apps/k8s-yaml/srv-case# curl 10.96.97.89 curl: (28) Failed to connect to 10.96.97.89 port 80: Connection timed out
下面使用命令式命令手動建立一個與該Service物件具有相同標籤選擇器的Deployment物件demoapp,它預設會自動建立一個擁有標籤app: demoapp的Pod物件。
#新增一個有app=demoapp標籤的deployments root@k8s-master01:/apps/k8s-yaml/srv-case# vim nginx-demoapp.yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demoapp labels: app: demoapp spec: replicas: 1 selector: matchLabels: app: demoapp template: metadata: labels: app: demoapp spec: containers: - name: nginx image: harbor.ywx.net/k8s-baseimages/demoapp:v1.0 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 #執行該yaml檔案 root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl apply -f nginx-demoapp.yaml deployment.apps/nginx-demoapp created root@k8s-master01:~# kubectl get pod -n default -o wide NAME READY STATUS RESTARTS AGE IP NODE nginx-demoapp-5b5cb85747-wnl7z 1/1 Running 0 6m53s 172.20.135.163 172.168.33.212 #驗證pod是否有app=demoapp標籤 root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl get pods -n default -l app=demoapp NAME READY STATUS RESTARTS AGE nginx-demoapp-5b5cb85747-wnl7z 1/1 Running 0 2m5s #驗證service把帶有app=demoapp的pod新增到endpoints root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl describe services demoapp-svc -n default Name: demoapp-svc Namespace: default Labels: <none> Annotations: <none> Selector: app=demoapp Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.68.106.128 IPs: 10.68.106.128 Port: http 80/TCP TargetPort: 80/TCP Endpoints: 172.20.135.163:80 #帶有app=demoapp的pod已經新增到了endpoints Session Affinity: None Events: <none>
Service物件demoapp-svc通過API Server獲知這種匹配變動後,會立即建立一個以該Pod物件的IP和埠為列表項的名為demoapp-svc的Endpoints物件,而該Service物件詳細描述資訊中的Endpoint欄位便以此列表項為值,如下面的命令結果所示。
root@k8s-master01:~# kubectl get endpoints demoapp-svc NAME ENDPOINTS AGE demoapp-svc 172.20.135.163:80 16m root@k8s-master01:~# kubectl describe services demoapp-svc Name: demoapp-svc Namespace: default Labels: <none> Annotations: <none> Selector: app=demoapp Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.68.106.128 IPs: 10.68.106.128 Port: http 80/TCP TargetPort: 80/TCP Endpoints: 172.20.135.163:80 Session Affinity: None Events: <none>
擴充套件Deployment物件demoapp的應用規模引起的變動也將立即反映到相關的Endpoint和Service物件之上,例如將deployments/demoapp物件的副本擴充套件至3個,再來驗證services/demoapp-svc的端點資訊,如下面的命令及結果所示。
root@k8s-master01:~# kubectl scale deployment nginx-demoapp --replicas=3 deployment.apps/nginx-demoapp scaled root@k8s-master01:~# kubectl get pods -n default -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-demoapp-54cc8d5bff-jls24 1/1 Running 0 49s 172.20.135.167 172.168.33.212 nginx-demoapp-54cc8d5bff-qf9j8 1/1 Running 0 101s 172.20.85.250 172.168.33.210 nginx-demoapp-54cc8d5bff-zgsmh 1/1 Running 0 49s 172.20.135.166 172.168.33.212 root@k8s-master01:~# kubectl describe services demoapp-svc -n default Name: demoapp-svc Namespace: default Labels: <none> Annotations: <none> Selector: app=demoapp Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.68.106.128 IPs: 10.68.106.128 Port: http 80/TCP TargetPort: 80/TCP Endpoints: 172.20.135.166:80,172.20.135.167:80,172.20.85.250:80 Session Affinity: None Events: <none>
接下來可於叢集中的某節點上再次向服務物件demoapp-svc發起訪問請求以進行測試,多次的訪問請求還可評估負載均衡演算法的排程效果,如下面的命令及結果所示。
root@k8s-master01:~# while true; do curl -s 10.68.106.128/hostname; sleep 2;done ServerName: nginx-demoapp-54cc8d5bff-jls24 ServerName: nginx-demoapp-54cc8d5bff-zgsmh ServerName: nginx-demoapp-54cc8d5bff-qf9j8
2、應用NodePort Service資源
部署Kubernetes集群系統時會預留一個埠範圍,專用於分配給需要用到NodePort的Service物件,該埠範圍預設為30000~32767。與Cluster型別的Service資源的一個顯著不同之處在於,NodePort型別的Service資源需要顯式定義.spec.type欄位值為NodePort,必要時還可以手動指定具體的節點埠號。例如下面的配置清單(services-nodeport-demo.yaml)中定義的Service資源物件demoapp-nodeport-svc,它使用了NodePort型別,且人為指定了32223這個節點埠。
kind: Service apiVersion: v1 metadata: name: demoapp-nodeport-svc spec: type: NodePort selector: app: demoapp #選擇帶有app=demoapp標籤的pod加入該svc的endpoints ports: - name: http protocol: TCP port: 80 targetPort: 80 nodePort: 32223
實踐中,並不鼓勵使用者自定義節點埠,除非能事先確定它不會與某個現存的Service資源產生衝突。無論如何,只要沒有特別需要,留給系統自動配置總是較好的選擇。將配置清單中定義的Service物件demoapp-nodeport-svc創建於叢集之上,以便通過詳細描述瞭解其狀態細節。
root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl apply -f service-nodeport-demo.yaml service/demoapp-nodeport-svc created root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl get svc -n default NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE demoapp-nodeport-svc NodePort 10.68.76.207 <none> 80:32223/TCP 27s demoapp-svc ClusterIP 10.68.106.128 <none> 80/TCP 31m kubernetes ClusterIP 10.68.0.1 <none> 443/TCP 3d14h root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl describe svc demoapp-nodeport-svc Name: demoapp-nodeport-svc Namespace: default Labels: <none> Annotations: <none> Selector: app=demoapp Type: NodePort IP Family Policy: SingleStack IP Families: IPv4 IP: 10.68.76.207 IPs: 10.68.76.207 Port: http 80/TCP TargetPort: 80/TCP NodePort: http 32223/TCP Endpoints: 172.20.135.166:80,172.20.135.167:80,172.20.85.250:80 Session Affinity: None External Traffic Policy: Cluster Events: <none> #注意:nodeport模式會在所有節點上開啟32223埠
命令結果顯示,該Service物件用於排程叢集外部流量時使用預設的Cluster策略,該策略優先考慮負載均衡效果,哪怕目標Pod物件位於另外的節點之上而帶來額外的網路躍點,因而針對該NodePort的請求將會被分散排程至該Serivce物件關聯的所有端點之上。可以在叢集外的某節點上對任一工作節點的NodePort埠發起HTTP請求以進行測試。以節點k8s-node03為例,我們以如下命令向它的IP地址172.168.32.206的32223埠發起多次請求。
root@k8s-master01:/apps/k8s-yaml/srv-case# while true; do curl -s 172.168.33.212:32223; sleep 2;done iKubernetes demoapp v1.0 !! ClientIP: 172.20.135.128, ServerName: nginx-demoapp-54cc8d5bff-qf9j8, ServerIP: 172.20.85.250! iKubernetes demoapp v1.0 !! ClientIP: 172.168.33.212, ServerName: nginx-demoapp-54cc8d5bff-jls24, ServerIP: 172.20.135.167! iKubernetes demoapp v1.0 !! ClientIP: 172.168.33.212, ServerName: nginx-demoapp-54cc8d5bff-zgsmh, ServerIP: 172.20.135.166!
上面命令的結果顯示出外部客戶端的請求被排程至該Service物件的每一個後端Pod之上,而這些Pod物件可能會分散於叢集中的不同節點。命令結果還顯示,請求報文的客戶端IP地址是最先接收到請求報文的節點上用於叢集內部通訊的IP地址,而非外部客戶端地址,這也能夠在Pod物件的應用訪問日誌中得到進一步驗證,如下所示。
root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl logs nginx-demoapp-54cc8d5bff-zgsmh * Running on http://0.0.0.0:80/ (Press CTRL+C to quit) 172.20.32.128 - - [03/Oct/2021 02:29:54] "GET /hostname HTTP/1.1" 200 - 172.168.33.212 - - [03/Oct/2021 02:37:26] "GET / HTTP/1.1" 200 - 172.168.33.212 - - [03/Oct/2021 02:37:32] "GET / HTTP/1.1" 200 - #第一個報文172.20.32.128為叢集內部地址
叢集外部客戶端對NodePort發起的請求報文源地址並非叢集內部地址,而請求報文又可能被收到報文的節點轉發至叢集中的另一個節點上的Pod物件,因此,為避免X節點直接將響應報文傳送給外部客戶端,Y節點需要先將收到的報文的源地址轉為請求報文的目標IP(自身的節點IP)後再進行後續處理過程。這樣才能確保Server Pod的響應報文必須由最先接收到請求報文的節點進行響應,因此NodePort型別的Service物件會對請求報文同時進行源地址轉換(SNAT)和目標地址轉換(DNAT)操作。
另一個外部流量策略Local則僅會將流量排程至請求的目標節點本地執行的Pod物件之上,以減少網路躍點,降低網路延遲,但當請求報文指向的節點本地不存在目標Service相關的Pod物件時將直接丟棄該報文。下面先把demoapp-nodeport-svc的外部流量策略修改為Local,而後再進行訪問測試。簡單起見,這裡使用kubectl patch命令來修改Service物件的流量策略。
root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl patch services/demoapp-nodeport-svc -p '{"spec": {"externalTrafficPolicy": "Local"}}' service/demoapp-nodeport-svc patched #或者直接修改yaml檔案中externalTrafficPolicy root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl describe svc demoapp-nodeport-svc Name: demoapp-nodeport-svc Namespace: default Labels: <none> Annotations: <none> Selector: app=demoapp Type: NodePort IP Family Policy: SingleStack IP Families: IPv4 IP: 10.68.76.207 IPs: 10.68.76.207 Port: http 80/TCP TargetPort: 80/TCP NodePort: http 32223/TCP Endpoints: 172.20.135.166:80,172.20.135.167:80,172.20.85.250:80 Session Affinity: None External Traffic Policy: Local #本地有service相關的pod就直接轉發,沒有則直接丟棄報文 Events: <none>
-p選項中指定的補丁是一個JSON格式的配置清單片段,它引用了spec.externalTrafficPolicy欄位,併為其賦一個新的值。配置完成後,我們再次發起測試請求時會看到,請求都被排程給了目標節點本地執行的Pod物件。另外,Local策略下無須在叢集中轉發流量至其他節點,也就不用再對請求報文進行源地址轉換,Server Pod所看到的客戶端IP就是外部客戶端的真實地址。
root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl get pod -n default -o wide NAME READY STATUS RESTARTS AGE IP NODE nginx-demoapp-54cc8d5bff-jls24 1/1 Running 0 21m 172.20.135.167 172.168.33.212 nginx-demoapp-54cc8d5bff-qf9j8 1/1 Running 0 22m 172.20.85.250 172.168.33.210 nginx-demoapp-54cc8d5bff-zgsmh 1/1 Running 0 21m 172.20.135.166 172.168.33.212
訪問:k8s-node03,在k8s-node03上有servicex相關的pod節點
root@k8s-master01:/apps/k8s-yaml/srv-case# while true; do curl -s 172.168.33.212:32223; sleep 2 ;done iKubernetes demoapp v1.0 !! ClientIP: 172.168.33.207, ServerName: nginx-demoapp-54cc8d5bff-jls24, ServerIP: 172.20.135.167! iKubernetes demoapp v1.0 !! ClientIP: 172.168.33.207, ServerName: nginx-demoapp-54cc8d5bff-zgsmh, ServerIP: 172.20.135.166! iKubernetes demoapp v1.0 !! ClientIP: 172.168.33.207, ServerName: nginx-demoapp-54cc8d5bff-jls24, ServerIP: 172.20.135.167!
訪問:k8s-node02,在k8s-node02上沒有servicex相關的pod節點
root@k8s-master01:/apps/k8s-yaml/srv-case# while true; do curl -s 172.168.33.211:32223; sleep 2 ;done
因為k8s-node02上沒有services的pod資源,當請求報文指向的節點本地不存在目標Service相關的Pod物件時將直接丟棄該報文。
3、應用LoadBalancer Service資源
NodePort型別的Service資源雖然能夠在叢集外部訪問,但外部客戶端必須事先得知NodePort和叢集中至少一個節點IP地址,一旦被選定的節點發生故障,客戶端還得自行選擇請求訪問其他的節點,因而一個有著固定IP地址的固定接入端點將是更好的選擇。此外,叢集節點很可能是某IaaS雲環境中僅具有私有IP地址的虛擬主機,這類地址對網際網路客戶端不可達,為此類節點接入流量也要依賴於叢集外部具有公網IP地址的負載均衡器,由其負責接入並排程外部客戶端的服務請求至叢集節點相應的NodePort之上。
IaaS雲端計算環境通常提供了LBaaS(Load Balancer as a Service)服務,它允許租戶動態地在自己的網路建立一個負載均衡裝置。部署在此類環境之上的Kubernetes叢集可藉助於CCM(Cloud Controller Manager)在建立LoadBalancer型別的Service資源時呼叫IaaS的相應API,按需創建出一個軟體負載均衡器。但CCM不會為那些非LoadBalancer型別的Service物件建立負載均衡器,而且當用戶將LoadBalancer型別的Service調整為其他型別時也將刪除此前建立的負載均衡器。
注意
kubeadm在部署Kubernetes叢集時並不會預設部署CCM,有需要的使用者需要自行部署。
kind: Service apiVersion: v1 metadata: name: demoapp-loadbalancer-svc spec: type: LoadBalancer selector: app: demoapp ports: - name: http protocol: TCP port: 80 targetPort: 80
Service物件的loadBalancerIP負責承接外部發來的流量,該IP地址通常由雲服務商系統動態配置,或者藉助.spec.loadBalancerIP欄位顯式指定,但有些雲服務商不支援使用者設定該IP地址,這種情況下,即便提供了也會被忽略。外部負載均衡器的流量會直接排程至Service後端的Pod物件之上,而如何排程流量則取決於雲服務商,有些環境可能還需要為Service資源的配置定義添加註解,必要時請自行參考雲服務商文件說明。另外,LoadBalancer Service還支援使用.spec. loadBalancerSourceRanges欄位指定負載均衡器允許的客戶端來源的地址範圍。
4、外部IP
外部IP地址可結合ClusterIP、NodePort或LoadBalancer任一型別的Service資源使用,而到達外部IP的請求流量會直接由相關聯的Service排程轉發至相應的後端Pod物件進行處理。假設示例Kubernetes叢集中的k8s-node01節點上擁有一個可被路由到的IP地址172.168.33.210,我們期望能夠將demoapp的服務通過該外部IP地址釋出到叢集外部,則可以使用下列配置清單(services-externalip-demo.yaml)中的Service資源實現。
kind: Service apiVersion: v1 metadata: name: demoapp-externalip-svc namespace: default spec: type: ClusterIP selector: app: demoapp ports: - name: http protocol: TCP port: 80 targetPort: 80 externalIPs: - 172.168.33.210 #叢集外部使用者可以通過172.168.33.210訪問叢集服務
三、Service的Endpoint資源
在資訊科技領域,端點是指通過LAN或WAN連線的能夠用於網路通訊的硬體裝置,它在廣義上可以指代任何與網路連線的裝置。在Kubernetes語境中,端點通常代表Pod或節點上能夠建立網路通訊的套接字,並由專用的資源型別Endpoint進行定義和跟蹤。
1、Endpoint與容器探針
kind: Service apiVersion: v1 metadata: name: services-readiness-demo namespace: default spec: selector: app: demoapp-with-readiness ports: - name: http protocol: TCP port: 80 targetPort: 80 --- apiVersion: apps/v1 kind: Deployment # 定義Deployment物件,它使用Pod模板建立Pod物件 metadata: name: demoapp2 spec: replicas: 2 # 該Deployment物件要求滿足的Pod物件數量 selector: # Deployment物件的標籤選擇器,用於篩選Pod物件並完成計數 matchLabels: app: demoapp-with-readiness template: # 由Deployment物件使用的Pod模板,用於建立足額的Pod物件 metadata: labels: app: demoapp-with-readiness spec: containers: - name: demoapp #image: ikubernetes/demoapp:v1.0 image: harbor.ywx.net/k8s-baseimages/demoapp:v1.0 name: demoapp imagePullPolicy: IfNotPresent readinessProbe: httpGet: # 定義探針型別和探測方式 path: '/readyz' port: 80 initialDelaySeconds: 15 # 初次檢測延遲時長 periodSeconds: 10 # 檢測週期
上面配置清單中定義Endpoint物件services-readiness-demo會篩選出Deployment物件demoapp2建立的兩個Pod物件,將它們的IP地址和服務埠建立為端點物件。但延遲15秒啟動的容器探針會導致這兩個Pod物件至少要在15秒以後才能轉為“就緒”狀態,這意味著在上面配置清單中的Service資源建立後至少15秒之內無可用後端端點,例如下面的資源建立和Endpoint資源監視命令結果中,在20秒之後,Endpoint資源services-readiness-demo才得到第一個可用的後端端點IP。
root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl apply -f services-readiness-demo.yaml service/services-readiness-demo created deployment.apps/demoapp2 created root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl get endpoints services-readiness-demo -w NAME ENDPOINTS AGE services-readiness-demo 33s services-readiness-demo 172.20.135.169:80 40s services-readiness-demo 172.20.135.168:80,172.20.135.169:80 40s root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl describe endpoints services-readiness-demo Name: services-readiness-demo Namespace: default Labels: <none> Annotations: endpoints.kubernetes.io/last-change-trigger-time: 2021-10-03T11:07:31+08:00 Subsets: Addresses: 172.20.135.168,172.20.135.169 NotReadyAddresses: <none> Ports: Name Port Protocol ---- ---- -------- http 80 TCP Events: <none> root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl get endpoints/services-readiness-demo -o yaml apiVersion: v1 kind: Endpoints metadata: annotations: endpoints.kubernetes.io/last-change-trigger-time: "2021-10-03T11:07:31+08:00" creationTimestamp: "2021-10-03T03:06:51Z" name: services-readiness-demo namespace: default resourceVersion: "621584" uid: ef4fd249-07c4-4a2b-92f0-258a2df24302 subsets: - addresses: - ip: 172.20.135.168 nodeName: 172.168.33.212 targetRef: kind: Pod name: demoapp2-5c8d4df55d-cxq4l namespace: default resourceVersion: "621581" uid: 25b0c06e-3016-4528-91f8-eec87a8417b9 - ip: 172.20.135.169 nodeName: 172.168.33.212 targetRef: kind: Pod name: demoapp2-5c8d4df55d-bpzs6 namespace: default resourceVersion: "621576" uid: 8b632422-66f0-482a-a31f-b4532362c367 ports: - name: http port: 80 protocol: TCP #無 NotReadyAddresses狀態的IP
因任何原因導致的後端端點就緒狀態檢測失敗,都會觸發Endpoint物件將該端點的IP地址從subsets.addresses欄位移至subsets.notReadyAddresses欄位。例如,我們使用如下命令人為地將地址172.20.135.168的Pod物件中的容器就緒狀態檢測設定為失敗,以進行驗證。
root@k8s-master01:/apps/k8s-yaml/srv-case# curl -s -X POST -d 'readyz=FAIL' 172.20.135.168/readyz
等待至少3個檢測週期共30秒之後,獲取Endpoint物件services-readiness-demo的資源清單的命令將返回類似如下資訊。
root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl describe endpoints services-readiness-demo Name: services-readiness-demo Namespace: default Labels: <none> Annotations: endpoints.kubernetes.io/last-change-trigger-time: 2021-10-03T11:10:51+08:00 Subsets: Addresses: 172.20.135.169 NotReadyAddresses: 172.20.135.168 #172.20.135.168被設定為NotReadyAddresses狀態 Ports: Name Port Protocol ---- ---- -------- http 80 TCP Events: <none> root@k8s-master01:/apps/k8s-yaml/srv-case# kubectl get endpoints/services-readiness-demo -o yaml apiVersion: v1 kind: Endpoints metadata: annotations: endpoints.kubernetes.io/last-change-trigger-time: "2021-10-03T11:10:51+08:00" creationTimestamp: "2021-10-03T03:06:51Z" name: services-readiness-demo namespace: default resourceVersion: "621990" uid: ef4fd249-07c4-4a2b-92f0-258a2df24302 subsets: - addresses: - ip: 172.20.135.169 nodeName: 172.168.33.212 targetRef: kind: Pod name: demoapp2-5c8d4df55d-bpzs6 namespace: default resourceVersion: "621576" uid: 8b632422-66f0-482a-a31f-b4532362c367 notReadyAddresses: #172.20.135.168被設定為notReadyAddresses - ip: 172.20.135.168 nodeName: 172.168.33.212 targetRef: kind: Pod name: demoapp2-5c8d4df55d-cxq4l namespace: default resourceVersion: "621987" uid: 25b0c06e-3016-4528-91f8-eec87a8417b9 ports: - name: http port: 80 protocol: TCP
2、自定義Endpoint資源
除了藉助Service物件的標籤選擇器自動關聯後端端點外,Kubernetes也支援自定義Endpoint物件,使用者可通過配置清單建立具有固定數量端點的Endpoint物件,而呼叫這類Endpoint物件的同名Service物件無須再使用標籤選擇器。Endpoint資源的API規範如下。
apiVersion: v1 kind: Endpoint metadata: # 物件元資料 name: namespace: subsets: # 端點物件的列表 - addresses: # 處於“就緒”狀態的端點地址物件列表 - hostname <string> # 端點主機名 ip <string> # 端點的IP地址,必選欄位 nodeName <string> # 節點主機名 targetRef: # 提供了該端點的物件引用 apiVersion <string> # 被引用物件所屬的API群組及版本 kind <string> # 被引用物件的資源型別,多為Pod name <string> # 物件名稱 namespace <string> # 物件所屬的名稱空間 fieldPath <string> # 被引用的物件的欄位,在未引用整個物件時使用,通常僅引用 # 指定Pod物件中的單容器,例如spec.containers[1] uid <string> # 物件的識別符號 notReadyAddresses: # 處於“未就緒”狀態的端點地址物件列表,格式與address相同 ports: # 埠物件列表 - name <string> # 埠名稱 port <integer> # 埠號,必選欄位 protocol <string> # 協議型別,僅支援UDP、TCP和SCTP,預設為TCP appProtocol <string> # 應用層協議
自定義Endpoint常將那些不是由編排程式編排的應用定義為Kubernetes系統的Service物件,從而讓客戶端像訪問叢集上的Pod應用一樣請求外部服務。例如,假設要把Kubernetes叢集外部一個可經由172.168.32.51:3306或172.168.32.52:3306任一端點訪問的MySQL資料庫服務引入叢集中,便可使用如下清單中的配置完成。
apiVersion: v1 kind: Endpoints metadata: name: mysql-external #name必須與service的name一樣,才能被service呼叫 namespace: default subsets: #引入叢集的外部地址和埠 - addresses: - ip: 172.29.9.51 - ip: 172.29.9.52 ports: - name: mysql port: 3306 protocol: TCP --- apiVersion: v1 kind: Service metadata: name: mysql-external #name必須與endpoint的name一樣,才能被service呼叫 namespace: default spec: type: ClusterIP ports: - name: mysql port: 3306 targetPort: 3306 protocol: TCP #kubernetes叢集可以通過訪問該service來呼叫外部的mysql。
Endpoint資源提供了在Kubernetes叢集上跟蹤端點的簡單途徑,但對於有著大量端點的Service來說,將所有的網路端點資訊都儲存在單個Endpoint資源中,會對Kubernetes控制平面元件產生較大的負面影響,且每次端點資源變動也會導致大量的網路流量。EndpointSlice(端點切片)通過將一個服務相關的所有端點按固定大小(預設為100個)切割為多個分片,提供了一種更具伸縮性和可擴充套件性的端點替代方案。 EndpointSlice由引用的端點資源組成,類似於Endpoint,它可由使用者手動建立,也可由EndpointSlice控制器根據使用者在建立Service資源時指定的標籤選擇器篩選叢集上的Pod物件自動建立。單個EndpointSlice資源預設不能超過100個端點,小於該數量時,EndpointSlice與Endpoint存在1:1的對映關係且效能相同。EndpointSlice控制器會盡可能地填滿每一個EndpointSlice資源,但不會主動進行重新平衡,新增的端點會嘗試新增到現有的EndpointSlice資源上,若超出現有任何EndpointSlice物件的可用的空餘空間,則將建立新的EndpointSlice,而非分散填充。
EndpointSlice自Kubernetes 1.17版本開始升級為Beta版,隸屬於discovery.k8s.io這一API群組。EndpointSlice控制器會為每個Endpoint資源自動生成一個EndpointSlice資源。例如,下面的命令列出了kube-system名稱空間中的所有EndpointSlice資源,kube-dns-mbdj5來自於對kube-dns這一Endpoint資源的自動轉換。
~$ kubectl get endpointslice -n kube-system NAME ADDRESSTYPE PORTS ENDPOINTS AGE kube-dns-mbdj5 IPv4 53,9153,53 10.244.0.6,10.244.0.7 13d
EndpointSlice資源根據其關聯的Service與埠劃分成組,每個組隸屬於同一個Service。
I have a dream so I study hard!!!