一文搞懂什麼是kubernetes Service

2022-08-06 12:00:55

1.什麼是Service?

在kubernets中,Pod是應用程式的載體,Pod你可以想象成就是容器,為動態的一組Pod提供一個固定的存取入口,它是以一種叫ClusterIP地址來進行標識,而ClusterIP就位於我們叢集網路(Cluster Network)當中,我們可以通過Pod的IP地址來進行存取,但是會遇到問題:

  1. 動態Pod的IP地址不是固定的,一旦Pod異常退出、節點故障,則會發生Pod重建,一旦發生重建使用者端則會存取失敗;
  2. Pod如果擴容多個,會造成使用者端無法有效使用新增的Pod,如果Pod進行縮容則會造成使用者端存取錯誤;
  3. 官方檔案: https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/

1.2 Service能幹什麼?

  1. 為了解決這個問題,K8s提供了Service資源,Service為動態的一組Pod提供一個固定的存取入口;這個固定的存取入口可以理解為是一組應用的前端的負載均衡器;就像LVS或Nginx為一組ReadyServer做負載均衡器是一樣的,你是看不見的,可以理解為Service就是負載均衡器,但是這種負載均衡器比傳統的負載均衡器要強大;Service資源通過"標籤選擇器Label Selector"把篩選出來的符合條件的一組Pod物件定義成一個邏輯集合,而後Service對外提供自己的IP和埠。

  2. 當用戶端請求Service的IP和埠時,Service將請求排程給標籤匹配的所有的Pod,Service向用戶端隱藏了真實處理請求的Pod資源,使得使用者端的請求看上去是由Service直接處理並進行響應。

  3. Service物件的IP地址(Cluster IP或Service IP)是虛擬IP地址,由kubernetes系統在Service物件建立時在專有網路(Service Network)地址中自動分配或由使用者手動指定,其次Service是基於埠過濾,並根據事先定義好的規則將請求轉發至其後端Pod對應的埠上,因此這種代理機制也稱為"埠代理"或"四層代理"工作在TCP/IP協定棧的傳輸層;

1.3 Service的作用?

  1. 暴露流量,讓使用者可以通過ServiceIP+ServicePort存取後端的Pod應用;
  2. 負載均衡: 提供基於4層的TCP/IP負載均衡,並不提供HTTP/HTTPS等負載均衡;
  3. 服務發現: 當發現新增Pod則自動加入至Service的後端,如發現Pod異常則會踢出Service後端;

1.4 Service的工作邏輯;

  1. Service持續監視API-Server,監視Service標籤選擇器所匹配的後端的Pod,並實時跟蹤這些Pod物件的變動情況,例如IP地址的變化、Pod物件增加或減少;
  2. Service並不直接與Pod建立關聯關係,他們之間還有一箇中間層Endpoints,Endpoints物件是一個由IP地址和埠組成的列表,這些IP地址和埠來自於Service標籤選擇器所匹配到的Pod,預設情況下,建立Service資源時,關聯的Endpoints物件會被自動建立;

1.5 Endpoint資源

  1. 建立一個Service的時候會自動建立一個與Service同名的Endpoints,事實上,Service不但能夠把標籤選擇器選中的Pod識別為自己的後端端點,還能夠對後端埠做就緒狀態檢測。如果後端Pod是就緒的就把它加入到後端可用端點列表中,反之踢出;
  2. 這個功能不是Service來做的,而是Service藉助一箇中間元件,Endpoints也是kubernetes一個標準的資源型別;
  3. Service會自動去管理Endpoints,Endpoints真正能發揮作用的是Endpoint控制器。一旦建立一個Service,需要為Service指定的基本屬性是"Label Selector"隨後Service控制器會根據這個標籤選擇器建立一個同名的Endpoints資源,是由Sercice控制器請求建立一個同名的Endpoints資源,隨後Endpoints控制器就會介入,因為有一個自己的資源需要被建立。Endpoints控制器就會使用Endpoints的標籤選擇器與Service一模一樣,繼承Service的,Endpoints控制器會根據"標籤選擇器"去查詢多少個符合篩選的Pod。最重要的是還會檢查Pod的就緒狀態,真正去繫結的不是由Service做的,而是由Endpoints做的。Service只負責排程,如果關聯到了。Service只是把Endpoint幫查詢到的所有處於就緒狀態的後端Pod告訴Service,於是成了Service的後端端點;

1.6 Servcie的實現;

  1. 在kubernetes中,Service只是一個抽象的概念,真正起作用實現負載均衡規則的其實是kube-proxy這個程序,它在每一個節點都需要執行一個kube-proxy,用來完成負載均衡規則的建立;

1.7 Kube-Proxy代理模式

1.7.1UserSpace

  1. UserSpace模式下,kube-proxy為ServiceIP建立一個監聽埠,當用戶向ServiceIP發起請求;首先按請求會被Iptables規則攔截,然後重定向到kube-proxy對應的埠上,然後kube-proxy根據排程演演算法挑選一個Pod,將請求排程到該Pod上;
  2. 該模式流量經過核心空間後,會送往使用者空間Kube-Proxy程序,而後又被送回核心空間,發往排程分配的目標後端Pod;效率太差。報文先到核心空間再回到使用者空間,因為報文在使用者空間來回切換兩次以上,
    1.7.2iptables
  3. iptables模式下,kube-proxy為Service後端的所有Pod建立對應的iptables規則,當用戶向ServiceIP發起請求;首先iptables會攔截使用者請求,然後直接將請求排程給後端的Pod;問題是一個Service會建立大量的Iptables規則,且不支援更高階的排程演演算法;
    1.7.3IPVS
    ipvs模式和iptables類似,kube-proxy為Service後端所有的Pod建立對應的IPVS規則, 一個Service只生成一條規則,所以規模較大的場景應使用IPVS。其次IPVS支援更多的高階演演算法;

2.Service的型別

2.1 Service資源規範

apiVersion: v1       # API的版本
kind: Service        # 資源型別定義為Service
metadata:           
  name: ...          # Serivce的名稱
  namespace: ...     # 預設的default
  labels:
     key1: value1   # 標籤 key:value格式;
     key2: value2
spec:
  type <string>      # Service型別,預設為ClusterIP;
  selector <map[string]string> #  標籤選擇器
  ports:                       #  ClusterIP:ServicePort
  targetPort: <string>  #後端目標程序的埠號或名稱。
  nodePort: <integer>  # 節點埠號,僅適用於NodePort和loadbalancer型別。  "建議動態選擇30000-32767" 
 clusterIP <string> # Service的叢集IP,建議由系統自動分配
 externalTrafficPolicy <string> # 外部流量策略處理方式,local表示由當前節點處理,cluster表示向叢集範圍排程
 loadBalancerIP <string> # 外部負載均衡器使用的IP地址,僅適用於loadbalancer,前提是你的公有云得支援你自己指定;
 externalName <string> # 外部服務名稱,該名稱作為Service的DNS CNAME值

2.2 ClusterIP

ClusterIP: 通過叢集內部IP暴露服務,選擇ServiceIP只能夠在叢集內部存取,這也是預設的Service型別;該地址僅在叢集內部可見、可達。無法被叢集外部使用者端存取;而且是預設型別,建立的任何Service預設就是ClusterIP型別,而且只能接受叢集內部使用者端的存取。

2.2.1 ClusterIP範例;

root@kubernetes-master01:~# cat services-clusterip-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-clusterip
  namespace: default
  labels:
     app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namespace: default
spec:
  clusterIP:
  selector:     # 標籤選擇器 
    app: nginx   
  ports:
  - name: http  # 埠名稱
    protocol: TCP   # 協定型別,目前支援TCP、UDP、SCTP預設為TCP
    port: 80   # Service的埠號
    targetPort: 80  # 後端目標程序的埠號
root@kubernetes-master01:~# kubectl apply -f services-clusterip-nginx.yaml 
pod/nginx-clusterip created
service/nginx-svc created

2.2.1.1檢視Service;

root@kubernetes-master01:~# kubectl get svc
nginx-svc    ClusterIP   10.106.70.164    <none>        80/TCP    3m

2.2.1.2 可以看到後端就一個Pod;

root@kubernetes-master01:~# kubectl get pods --show-labels -l app=nginx -o wide
NAME              READY   STATUS    RESTARTS   AGE     IP            NODE                NOMINATED NODE   READINESS GATES   LABELS
nginx-clusterip   1/1     Running   0          8m58s   10.244.4.49   kubernetes-node01   <none>           <none>            app=nginx

2.2.1.3檢視Endpoint資源,Endpoints可以簡寫為ep;

root@kubernetes-master01:~# kubectl get endpoints
nginx-svc    10.244.4.49:80                                10m

2.2.1.4測試存取;只能在叢集內部存取,外部無法存取;

root@kubernetes-master01:~# curl -I  10.106.70.164
HTTP/1.1 200 OK
Server: nginx/1.21.5
Content-Type: text/html
Content-Length: 615
Connection: keep-alive
ETag: "61cb5be0-267"
Accept-Ranges: bytes

2.3 NodePort

NodePort:首先是一種ClusterIP型別,也就是說,NodePort是ClusterIP的擴充套件型別。NodePort型別的Service不僅僅能夠被叢集內部的使用者端可見,還能對外部使用者端可見。怎麼可見呢?它會與ClusterIP的功能之外在每個節點上使用一個相同的埠號"注意是在每個節點上使用一個相同的埠號"將外部流量引入到該Service上來。

2.3.1 NodePort範例;

root@kubernetes-master01:~# cat services-nodeport-nginx.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport-svc
  namespace: default
spec:
  type: NodePort
  clusterIP:
  selector:
    app: nginx
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80   # 後端Pod監聽什麼埠就寫什麼埠。要不然到達Service的請求轉發給Pod,Pod沒有那個埠也沒用。一定真正轉發到後端程式監聽的埠。如果沒有特殊情況的話,ServicePort和TargetPort保持一致。NodePort可以不用指定。
    nodePort:        # 正常情況下應由系統自己分配,預設是3000~32767
root@kubernetes-master01:~# kubectl apply -f services-nodeport-nginx.yaml 
service/nginx-nodeport-svc created

2.3.1.1檢視Service,意味著存取宿主機IP+nodeport埠就可以存取服務;

root@kubernetes-master01:~# kubectl get svc
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes           ClusterIP   10.96.0.1        <none>        443/TCP        36d
nginx-nodeport-svc   NodePort    10.111.124.121   <none>        80:32049/TCP   5s
nginx-svc            ClusterIP   10.106.70.164    <none>        80/TCP         34m

2.3.1.2 測試,這是windows的命令列,也是沒有問題;

C:\Users\海棠>curl -I  10.0.0.1xx:30824
HTTP/1.1 200 OK
Server: nginx/1.21.5
Content-Type: text/html
Content-Length: 615
Connection: keep-alive
ETag: "61cb5be0-267"
Accept-Ranges: bytes

2.4 LoadBalancer

loadBalancer: 這類Service依賴雲廠商,需要通過雲廠商呼叫API介面建立軟體負載均衡將服務暴露到叢集外部,當建立LoadBalancer型別的Service物件時,它會在叢集上自動建立一個NodePort型別的Service,叢集外部的請求流量會先路由至該負載均衡,並由該負載均衡排程至各個節點的NodePort;

2.4.1 LoadBalancer範例;

root@kubernetes-master01:~# cat  services-loadbalancer-nginx.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-loadbalancer-svc
  namespace: default
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
  loadBalancerIP: 1.2.3.4

2.4.1.2測試存取;

# 我們還是隻能是通過NodePort來存取,因為沒有LoadBalancer的IP;
# LoadBalancer其實就是一個增強的NodePort。而LoadBalancer沒有限制流量排程策略的。外部流量策略對loadbalancer依然使用,因為LoadBalancer首先是一個NodePort的Service。
C:\Users\冷雨夜>curl -I 10.0.0.1XX:31943
HTTP/1.1 200 OK
Server: nginx/1.21.5
Content-Type: text/html
Content-Length: 615
Connection: kep-alive
ETag: "61cb5be0-267"
Accept-Ranges: bytes

2.5 ExternalName

此型別不是用來定義如何存取叢集內服務的,而是把叢集外部的某些服務以DNS CANME方式對映到叢集內,從而讓叢集內的Pod資源能夠存取外部服務的一種實現方式。