Kubernetes(k8s)kube-proxy、Service詳解

語言: CN / TW / HK

一、kube-proxy簡介

kube-proxy負責為Service提供cluster內部的服務發現和負載均衡,它執行在每個Node計算節點上,負責Pod網路代理, 它會定時從etcd服務獲取到service資訊來做相應的策略,維護網路規則和四層負載均衡工作。在K8s叢集中微服務的負載均衡是由Kube-proxy實現的,它是K8s叢集內部的負載均衡器,也是一個分散式代理伺服器,在K8s的每個節點上都有一個,這一設計體現了它的伸縮性優勢,需要訪問服務的節點越多,提供負載均衡能力的Kube-proxy就越多,高可用節點也隨之增多。

service是一組pod的服務抽象,相當於一組pod的LB,負責將請求分發給對應的pod。service會為這個LB提供一個IP,一般稱為cluster IP。kube-proxy的作用主要是負責service的實現,具體來說,就是實現了內部從pod到service和外部的從node port向service的訪問。

簡單來說: - kube-proxy其實就是管理service的訪問入口,包括叢集內Pod到Service的訪問和叢集外訪問service。 - kube-proxy管理sevice的Endpoints,該service對外暴露一個Virtual IP,也成為Cluster IP, 叢集內通過訪問這個Cluster IP:Port就能訪問到叢集內對應的serivce下的Pod。 - service是通過Selector選擇的一組Pods的服務抽象,其實就是一個微服務,提供了服務的LB和反向代理的能力,而kube-proxy的主要作用就是負責service的實現。 - service另外一個重要作用是,一個服務後端的Pods可能會隨著生存滅亡而發生IP的改變,service的出現,給服務提供了一個固定的IP,而無視後端Endpoint的變化。

二、Service 簡介

Kubernetes Service定義了這樣一種抽象: Service是一種可以訪問 Pod邏輯分組的策略, Service通常是通過 Label Selector訪問 Pod組。

Service能夠提供負載均衡的能力,但是在使用上有以下限制:只提供 4 層負載均衡能力,而沒有 7 層功能,但有時我們可能需要更多的匹配規則來轉發請求,這點上 4 層負載均衡是不支援的。

三、Service 型別

Service在 K8s中有以下四種類型:

1)ClusterIp(叢集內部使用)

預設型別,自動分配一個僅Cluster內部可以訪問的虛擬IP(VIP)。

2)NodePort(對外暴露應用)

在ClusterIP基礎上為Service在每臺機器上繫結一個埠,這樣就可以通過NodeIP:NodePort訪問來訪問該服務。 埠範圍:30000~32767

3)LoadBalancer(對外暴露應用,適用於公有云)

在NodePort的基礎上,藉助Cloud Provider建立一個外部負載均衡器,並將請求轉發到NodePort。

4)ExternalName

建立一個dns別名指到service name上,主要是防止service name發生變化,要配合dns外掛使用。通過返回 CNAME 和它的值,可以將服務對映到 externalName 欄位的內容。這隻有 Kubernetes 1.7或更高版本的kube-dns才支援(我這裡是Kubernetes 1.22.1版本)。

四、Service 工作流程

  1. 客戶端訪問節點時通過 iptables實現的
  2. iptables規則是通過 kube-proxy寫入的
  3. apiserver通過監控 kube-proxy去進行對服務和端點的監控
  4. kube-proxy通過 pod的標籤( lables)去判斷這個斷點資訊是否寫入到 Endpoints裡

五、Endpoints簡介

endpoint是k8s叢集中的一個資源物件,儲存在etcd中,用來記錄一個service對應的所有pod的訪問地址。service配置selector,endpoint controller才會自動建立對應的endpoint物件;否則,不會生成endpoint物件。

【例如】k8s叢集中建立一個名為hello的service,就會生成一個同名的endpoint物件,ENDPOINTS就是service關聯的pod的ip地址和埠。

1)工作流程

一個 Service 由一組 backend Pod 組成。這些 Pod 通過 endpoints 暴露出來。 Service Selector 將持續評估,結果被 POST 到一個名稱為 Service-hello 的 Endpoint 物件上。 當 Pod 終止後,它會自動從 Endpoint 中移除,新的能夠匹配上 Service Selector 的 Pod 將自動地被新增到 Endpoint 中。 檢查該 Endpoint,注意到 IP 地址與建立的 Pod 是相同的。現在,能夠從叢集中任意節點上使用 curl 命令請求 hello Service \:\

2)示例

1、deployment-hello.yaml

bash $ cat << EOF > deployment-hello.yaml apiVersion: apps/v1 kind: Deployment metadata: name: hello spec: replicas: 3 selector: matchLabels: run: hello template: metadata: labels: run: hello spec: containers: - name: nginx image: nginx:1.17.1 EOF 2、service-hello.yaml

bash $ cat << EOF > service-hello.yaml apiVersion: v1 kind: Service metadata: name: service-hello labels: name: service-hello spec: type: NodePort #這裡代表是NodePort型別的,另外還有ingress,LoadBalancer ports: - port: 80 targetPort: 8080 protocol: TCP nodePort: 31111 # 所有的節點都會開放此埠30000--32767,此埠供外部呼叫。 selector: run: hello EOF 3、檢視驗證

```bash $ kubectl apply -f deployment-hello.yaml $ kubectl apply -f service-hello.yaml

檢視pod,如果本地沒有映象,可能等待的時候比較長,一定要等到所有pod都在執行中才行。

$ kubectl get pod -o wide|grep hello-*

檢視service

$ kubectl get service service-hello -o wide

檢視service詳情

$ kubectl describe service service-hello

檢視pointer

$ kubectl get endpoints service-hello ```

六、Service, Endpoints與Pod的關係

Kube-proxy程序獲取每個Service的Endpoints,實現Service的負載均衡功能。

Service的負載均衡轉發規則

訪問Service的請求,不論是Cluster IP+TargetPort的方式;還是用Node節點IP+NodePort的方式,都被Node節點的Iptables規則重定向到Kube-proxy監聽Service服務代理埠。kube-proxy接收到Service的訪問請求後,根據負載策略,轉發到後端的Pod。

七、Service的資源清單檔案詳解

```yaml apiVersion: v1 kind: Service metadata:

元資料

name: string #Service名稱 namespace: string #名稱空間,不指定時預設為default名稱空間 labels: #自定義標籤屬性列表
- name: string annotations: #自定義註解屬性列表
- name: string spec:

詳細描述

selector: [] #這裡選擇器一定要選擇容器的標籤,也就是pod的標籤 #selector: # app: web #Label Selector配置,選擇具有指定label標籤的pod作為管理範圍 type: string #service的型別,指定service的訪問方式,預設ClusterIP #ClusterIP:虛擬的服務ip地址,用於k8s叢集內部的pod訪問,在Node上kube-porxy通過設定的iptables規則進行轉發 #NodePort:使用宿主機埠,能夠訪問各Node的外部客戶端通過Node的IP和埠就能訪問伺服器 #LoadBalancer:使用外部負載均衡器完成到伺服器的負載分發, #需要在spec.status.loadBalancer欄位指定外部負載均衡伺服器的IP,並同時定義nodePort和clusterIP用於公有云環境。 clusterIP: string #虛擬服務IP地址,當type=ClusterIP時,如不指定,則系統會自動進行分配,也可以手動指定。當type=loadBalancer,需要指定 sessionAffinity: string #是否支援session,可選值為ClietIP,預設值為空 #ClientIP表示將同一個客戶端(根據客戶端IP地址決定)的訪問請求都轉發到同一個後端Pod ports: #service需要暴露的埠列表
- name: string #埠名稱 protocol: string #埠協議,支援TCP或UDP,預設TCP port: int #服務監聽的埠號 targetPort: int #需要轉發到後端的埠號 nodePort: int #當type=NodePort時,指定對映到物理機的埠號 status: #當type=LoadBalancer時,設定外部負載均衡的地址,用於公有云環境
loadBalancer: #外部負載均衡器
ingress: #外部負載均衡器 ip: string #外部負載均衡器的IP地址 hostname: string #外部負載均衡器的機主機 ```

八、kubernetes中的四種port

1)nodePort

nodePort是外部訪問k8s叢集中service的埠,通過nodeIP: nodePort可以從外部訪問到某個service。

2)port

port是k8s叢集內部訪問service的埠,即通過clusterIP: port可以訪問到某個service。

3)targetPort

targetPort是pod的埠,從port和nodePort來的流量經過kube-proxy流入到後端pod的targetPort上,最後進入容器。

4)containerPort

containerPort是pod內部容器的埠,targetPort對映到containerPort。

九、kubernetes服務發現

Kubernetes提供了兩種方式進行服務發現, 即環境變數和DNS, 簡單說明如下:

1)環境變數

當你建立一個Pod的時候,kubelet會在該Pod中注入叢集內所有Service的相關環境變數。

【注意】要想一個Pod中注入某個Service的環境變數,則必須Service要比該Pod先建立。這一點,幾乎使得這種方式進行服務發現不可用。比如,一個ServiceName為redis-master的Service,對應的ClusterIP:Port為172.16.50.11:6379,則其對應的環境變數為:

REDIS_MASTER_SERVICE_HOST=172.16.50.11 REDIS_MASTER_SERVICE_PORT=6379 REDIS_MASTER_PORT=tcp://172.16.50.11:6379 REDIS_MASTER_PORT_6379_TCP=tcp://172.16.50.11:6379 REDIS_MASTER_PORT_6379_TCP_PROTO=tcp REDIS_MASTER_PORT_6379_TCP_PORT=6379 REDIS_MASTER_PORT_6379_TCP_ADDR=172.16.50.11

2) DNS

這是k8s官方強烈推薦的方式!!! 可以通過cluster add-on方式輕鬆的建立KubeDNS來對叢集內的Service進行服務發現。

十、Service代理模式

k8s群集中的每個節點都執行一個kube-proxy的元件,kube-proxy其實是一個代理層負責實現service。

Kubernetes v1.2之前預設是userspace,v1.2之後預設是iptables模式,iptables模式效能和可靠性更好,但是iptables模式依賴健康檢查,在沒有健康檢查的情況下如果一個pod不響應,iptables模式不會切換另一個pod上。

1)userspace模式

客戶端訪問ServiceIP(clusterIP)請求會先從使用者空間到核心中的iptables,然後回到使用者空間kube-proxy,kube-proxy負責代理工作。

缺點:

可見,userspace這種mode最大的問題是,service的請求會先從使用者空間進入核心iptables,然後再回到使用者空間,由kube-proxy完成後端Endpoints的選擇和代理工作,這樣流量從使用者空間進出核心帶來的效能損耗是不可接受的。這也是k8s v1.0及之前版本中對kube-proxy質疑最大的一點,因此社群就開始研究iptables mode。

詳細工作流程:

userspace這種模式下,kube-proxy 持續監聽 Service 以及 Endpoints 物件的變化;對每個 Service,它都為其在本地節點開放一個埠,作為其服務代理埠;發往該埠的請求會採用一定的策略轉發給與該服務對應的後端 Pod 實體。kube-proxy 同時會在本地節點設定 iptables 規則,配置一個 Virtual IP,把發往 Virtual IP 的請求重定向到與該 Virtual IP 對應的服務代理埠上。其工作流程大體如下:

【分析】該模式請求在到達 iptables 進行處理時就會進入核心,而 kube-proxy 監聽則是在使用者態, 請求就形成了從使用者態到核心態再返回到使用者態的傳遞過程, 一定程度降低了服務效能。

2)iptables模式(預設模式)

該模式完全利用核心iptables來實現service的代理和LB, 這是K8s在v1.2及之後版本預設模式. 工作原理如下:

iptables mode因為使用iptable NAT來完成轉發,也存在不可忽視的效能損耗。另外,如果叢集中存在上萬的Service/Endpoint,那麼Node上的iptables rules將會非常龐大,效能還會再打折扣。這也導致目前大部分企業用k8s上生產時,都不會直接用kube-proxy作為服務代理,而是通過自己開發或者通過Ingress Controller來整合HAProxy, Nginx來代替kube-proxy。

詳細工作流程:

iptables 模式與 userspace 相同,kube-proxy 持續監聽 Service 以及 Endpoints 物件的變化;但它並不在本地節點開啟反向代理服務,而是把反向代理全部交給 iptables 來實現;即 iptables 直接將對 VIP 的請求轉發給後端 Pod,通過 iptables 設定轉發策略。其工作流程大體如下:

【分析】 該模式相比 userspace 模式,克服了請求在使用者態-核心態反覆傳遞的問題,效能上有所提升,但使用 iptables NAT 來完成轉發,存在不可忽視的效能損耗,而且在大規模場景下,iptables 規則的條目會十分巨大,效能上還要再打折扣。

示例:

bash cat << EOF > mysql-service.yaml apiVersion: v1 kind: Service metadata: labels: name: mysql role: service name: mysql-service spec: ports: - port: 3306 targetPort: 3306 nodePort: 30964 type: NodePort selector: mysql-service: "true" name: mysql EOF $ kubectl apply -f mysql-service.yaml $ kubectl get svc

3)ipvs模型

在kubernetes 1.8以上的版本中,對於kube-proxy元件增加了除iptables模式和使用者模式之外還支援ipvs模式。kube-proxy ipvs 是基於 NAT 實現的,通過ipvs的NAT模式,對訪問k8s service的請求進行虛IP到POD IP的轉發。當建立一個 service 後,kubernetes 會在每個節點上建立一個網絡卡,同時幫你將 Service IP(VIP) 繫結上,此時相當於每個 Node 都是一個 ds,而其他任何 Node 上的 Pod,甚至是宿主機服務(比如 kube-apiserver 的 6443)都可能成為 rs;

詳細工作流程:

與iptables、userspace 模式一樣,kube-proxy 依然監聽Service以及Endpoints物件的變化, 不過它並不建立反向代理, 也不建立大量的 iptables 規則, 而是通過netlink 建立ipvs規則,並使用k8s Service與Endpoints資訊,對所在節點的ipvs規則進行定期同步; netlink 與 iptables 底層都是基於 netfilter 鉤子,但是 netlink 由於採用了 hash table 而且直接工作在核心態,在效能上比 iptables 更優。其工作流程大體如下:

【分析】ipvs 是目前 kube-proxy 所支援的最新代理模式,相比使用 iptables,使用 ipvs 具有更高的效能。


4)kube-proxy配置 ipvs模式(所有節點)

1、載入ip_vs相關核心模組

bash $ modprobe -- ip_vs $ modprobe -- ip_vs_sh $ modprobe -- ip_vs_rr $ modprobe -- ip_vs_wrr $ modprobe -- nf_conntrack_ipv4 所有節點驗證開啟了ipvs: bash $ lsmod |grep ip_vs

2、安裝ipvsadm工具

bash $ yum install ipset ipvsadm -y 3、編輯kube-proxy配置檔案,mode修改成ipvs

bash $ kubectl edit configmap -n kube-system kube-proxy

4、重啟kube-proxy 先檢視之前的kube-proxy bash $ kubectl get pod -n kube-system | grep kube-proxy

刪掉上面三個kube-proxy,重新拉起新的服務 bash $ kubectl get pod -n kube-system | grep kube-proxy |awk '{system("kubectl delete pod "$1" -n kube-system")}' 再檢視 bash $ kubectl get pod -n kube-system | grep kube-proxy

5、檢視

bash $ ipvsadm -Ln

關於Kubernetes(k8s)kube-proxy、Service的介紹,就先到這裡了,有疑問的小夥伴,歡迎給我留言哦~