kube-proxy工作原理

kube-proxy & service必要說(shuō)明

說(shuō)到kube-proxy,就不得不提到k8s中service,下面對(duì)它們兩做簡(jiǎn)單說(shuō)明:

  • kube-proxy其實(shí)就是管理service的訪問(wèn)入口,包括集群內(nèi)Pod到Service的訪問(wèn)和集群外訪問(wèn)service。
  • kube-proxy管理sevice的Endpoints,該service對(duì)外暴露一個(gè)Virtual IP,也成為Cluster IP, 集群內(nèi)通過(guò)訪問(wèn)這個(gè)Cluster IP:Port就能訪問(wèn)到集群內(nèi)對(duì)應(yīng)的serivce下的Pod。
  • service是通過(guò)Selector選擇的一組Pods的服務(wù)抽象,其實(shí)就是一個(gè)微服務(wù),提供了服務(wù)的LB和反向代理的能力,而kube-proxy的主要作用就是負(fù)責(zé)service的實(shí)現(xiàn)。
  • service另外一個(gè)重要作用是,一個(gè)服務(wù)后端的Pods可能會(huì)隨著生存滅亡而發(fā)生IP的改變,service的出現(xiàn),給服務(wù)提供了一個(gè)固定的IP,而無(wú)視后端Endpoint的變化。

服務(wù)發(fā)現(xiàn)

k8s提供了兩種方式進(jìn)行服務(wù)發(fā)現(xiàn):

  • 環(huán)境變量: 當(dāng)你創(chuàng)建一個(gè)Pod的時(shí)候,kubelet會(huì)在該P(yáng)od中注入集群內(nèi)所有Service的相關(guān)環(huán)境變量。需要注意的是,要想一個(gè)Pod中注入某個(gè)Service的環(huán)境變量,則必須Service要先比該P(yáng)od創(chuàng)建。這一點(diǎn),幾乎使得這種方式進(jìn)行服務(wù)發(fā)現(xiàn)不可用。

    比如,一個(gè)ServiceName為redis-master的Service,對(duì)應(yīng)的ClusterIP:Port為10.0.0.11:6379,則其對(duì)應(yīng)的環(huán)境變量為:

        REDIS_MASTER_SERVICE_HOST=10.0.0.11
        REDIS_MASTER_SERVICE_PORT=6379
        REDIS_MASTER_PORT=tcp://10.0.0.11:6379
        REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
        REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
        REDIS_MASTER_PORT_6379_TCP_PORT=6379
        REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11 
    
  • DNS:這也是k8s官方強(qiáng)烈推薦的方式。可以通過(guò)cluster add-on的方式輕松的創(chuàng)建KubeDNS來(lái)對(duì)集群內(nèi)的Service進(jìn)行服務(wù)發(fā)現(xiàn)。更多關(guān)于KubeDNS的內(nèi)容,請(qǐng)查看我的博文:Kubernetes DNS Service技術(shù)研究 ,在此不再贅述。

發(fā)布(暴露)服務(wù)

k8s原生的,一個(gè)Service的ServiceType決定了其發(fā)布服務(wù)的方式。

  • ClusterIP:這是k8s默認(rèn)的ServiceType。通過(guò)集群內(nèi)的ClusterIP在內(nèi)部發(fā)布服務(wù)。
  • NodePort:這種方式是常用的,用來(lái)對(duì)集群外暴露Service,你可以通過(guò)訪問(wèn)集群內(nèi)的每個(gè)NodeIP:NodePort的方式,訪問(wèn)到對(duì)應(yīng)Service后端的Endpoint。
  • LoadBalancer: 這也是用來(lái)對(duì)集群外暴露服務(wù)的,不同的是這需要Cloud Provider的支持,比如AWS等。
  • ExternalName:這個(gè)也是在集群內(nèi)發(fā)布服務(wù)用的,需要借助KubeDNS(version >= 1.7)的支持,就是用KubeDNS將該service和ExternalName做一個(gè)Map,KubeDNS返回一個(gè)CNAME記錄。

kube-proxy內(nèi)部原理

kube-proxy當(dāng)前實(shí)現(xiàn)了兩種proxyMode:userspace和iptables。其中userspace mode是v1.0及之前版本的默認(rèn)模式,從v1.1版本中開(kāi)始增加了iptables mode,在v1.2版本中正式替代userspace模式成為默認(rèn)模式。

userspace mode userspace是在用戶(hù)空間,通過(guò)kube-proxy來(lái)實(shí)現(xiàn)service的代理服務(wù)。廢話(huà)不多說(shuō),其原理如下如圖所示:

image.png

可見(jiàn),這種mode最大的問(wèn)題是,service的請(qǐng)求會(huì)先從用戶(hù)空間進(jìn)入內(nèi)核iptables,然后再回到用戶(hù)空間,由kube-proxy完成后端Endpoints的選擇和代理工作,這樣流量從用戶(hù)空間進(jìn)出內(nèi)核帶來(lái)的性能損耗是不可接受的。這也是k8s v1.0及之前版本中對(duì)kube-proxy質(zhì)疑最大的一點(diǎn),因此社區(qū)就開(kāi)始研究iptables mode。

Example

$ kubectl get service
NAME LABELS SELECTOR IP(S) PORT(S)
kubernetes component=apiserver,provider=kubernetes <none> 10.254.0.1 443/TCP
ssh-service1 name=ssh,role=service ssh-service=true 10.254.132.107 2222/TCP

$ kubectl describe service ssh-service1 
Name:           ssh-service1 Namespace:     default Labels:         name=ssh,role=service Selector:     ssh-service=true Type:          LoadBalancer IP:            10.254.132.107 Port:            <unnamed>   2222/TCP NodePort:      <unnamed>   30239/TCP Endpoints:        <none>
Session Affinity:   None
No events.

NodePort的工作原理與ClusterIP大致相同,發(fā)送到某個(gè)NodeIP:NodePort的請(qǐng)求,通過(guò)iptables重定向到kube-proxy對(duì)應(yīng)的端口(Node上的隨機(jī)端口)上,然后由kube-proxy再將請(qǐng)求發(fā)送到其中的一個(gè)Pod:TargetPort。

這里,假如Node的ip為10.0.0.5,則對(duì)應(yīng)的iptables如下:

$ sudo iptables -S -t nat
...
-A KUBE-NODEPORT-CONTAINER -p tcp -m comment --comment "default/ssh-service1:" -m tcp --dport 30239 -j REDIRECT --to-ports 36463
-A KUBE-NODEPORT-HOST -p tcp -m comment --comment "default/ssh-service1:" -m tcp --dport 30239 -j DNAT --to-destination 10.0.0.5:36463
-A KUBE-PORTALS-CONTAINER -d 10.254.132.107/32 -p tcp -m comment --comment "default/ssh-service1:" -m tcp --dport 2222 -j REDIRECT --to-ports 36463
-A KUBE-PORTALS-HOST -d 10.254.132.107/32 -p tcp -m comment --comment "default/ssh-service1:" -m tcp --dport 2222 -j DNAT --to-destination 10.0.0.5:36463 

可見(jiàn):訪問(wèn)10.0.0.5:30239端口會(huì)被轉(zhuǎn)發(fā)到node上的36463端口(隨機(jī)監(jiān)聽(tīng)端口)。而且在訪問(wèn)clusterIP 10.254.132.107的2222端口時(shí),也會(huì)把請(qǐng)求轉(zhuǎn)發(fā)到本地的36463端口。 36463端口實(shí)際被kube-proxy所監(jiān)聽(tīng),將流量進(jìn)行導(dǎo)向到后端的pod上。

iptables mode 另一種mode是iptables,它完全利用內(nèi)核iptables來(lái)實(shí)現(xiàn)service的代理和LB。是v1.2及之后版本默認(rèn)模式,其原理圖如下所示:

[圖片上傳失敗...(image-860c56-1659062517902)]

iptables mode因?yàn)槭褂胕ptable NAT來(lái)完成轉(zhuǎn)發(fā),也存在不可忽視的性能損耗。另外,如果集群中存在上萬(wàn)的Service/Endpoint,那么Node上的iptables rules將會(huì)非常龐大,性能還會(huì)再打折扣。

這也導(dǎo)致,目前大部分企業(yè)用k8s上生產(chǎn)時(shí),都不會(huì)直接用kube-proxy作為服務(wù)代理,而是通過(guò)自己開(kāi)發(fā)或者通過(guò)Ingress Controller來(lái)集成HAProxy, Nginx來(lái)代替kube-proxy。

Example iptables的方式則是利用了linux的iptables的nat轉(zhuǎn)發(fā)進(jìn)行實(shí)現(xiàn)。

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" 

mysql-service對(duì)應(yīng)的nodePort暴露出來(lái)的端口為30964,對(duì)應(yīng)的cluster IP(10.254.162.44)的端口為3306,進(jìn)一步對(duì)應(yīng)于后端的pod的端口為3306。

mysql-service后端代理了兩個(gè)pod,ip分別是192.168.125.129和192.168.125.131。先來(lái)看一下iptables。

$iptables -S -t nat
...
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/mysql-service:" -m tcp --dport 30964 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/mysql-service:" -m tcp --dport 30964 -j KUBE-SVC-67RL4FN6JRUPOJYM
-A KUBE-SEP-ID6YWIT3F6WNZ47P -s 192.168.125.129/32 -m comment --comment "default/mysql-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-ID6YWIT3F6WNZ47P -p tcp -m comment --comment "default/mysql-service:" -m tcp -j DNAT --to-destination 192.168.125.129:3306
-A KUBE-SEP-IN2YML2VIFH5RO2T -s 192.168.125.131/32 -m comment --comment "default/mysql-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-IN2YML2VIFH5RO2T -p tcp -m comment --comment "default/mysql-service:" -m tcp -j DNAT --to-destination 192.168.125.131:3306
-A KUBE-SERVICES -d 10.254.162.44/32 -p tcp -m comment --comment "default/mysql-service: cluster IP" -m tcp --dport 3306 -j KUBE-SVC-67RL4FN6JRUPOJYM
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment "default/mysql-service:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ID6YWIT3F6WNZ47P
-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment "default/mysql-service:" -j KUBE-SEP-IN2YML2VIFH5RO2T 

首先如果是通過(guò)node的30964端口訪問(wèn),則會(huì)進(jìn)入到以下鏈:

-A KUBE-NODEPORTS -p tcp -m comment --comment "default/mysql-service:" -m tcp --dport 30964 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/mysql-service:" -m tcp --dport 30964 -j KUBE-SVC-67RL4FN6JRUPOJYM 

然后進(jìn)一步跳轉(zhuǎn)到KUBE-SVC-67RL4FN6JRUPOJYM的鏈:

-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment "default/mysql-service:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ID6YWIT3F6WNZ47P
-A KUBE-SVC-67RL4FN6JRUPOJYM -m comment --comment "default/mysql-service:" -j KUBE-SEP-IN2YML2VIFH5RO2T 

這里利用了iptables的–probability的特性,使連接有50%的概率進(jìn)入到KUBE-SEP-ID6YWIT3F6WNZ47P鏈,50%的概率進(jìn)入到KUBE-SEP-IN2YML2VIFH5RO2T鏈。

KUBE-SEP-ID6YWIT3F6WNZ47P的鏈的具體作用就是將請(qǐng)求通過(guò)DNAT發(fā)送到192.168.125.129的3306端口。

-A KUBE-SEP-ID6YWIT3F6WNZ47P -s 192.168.125.129/32 -m comment --comment "default/mysql-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-ID6YWIT3F6WNZ47P -p tcp -m comment --comment "default/mysql-service:" -m tcp -j DNAT --to-destination 192.168.125.129:3306 

同理KUBE-SEP-IN2YML2VIFH5RO2T的作用是通過(guò)DNAT發(fā)送到192.168.125.131的3306端口。

-A KUBE-SEP-IN2YML2VIFH5RO2T -s 192.168.125.131/32 -m comment --comment "default/mysql-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-IN2YML2VIFH5RO2T -p tcp -m comment --comment "default/mysql-service:" -m tcp -j DNAT --to-destination 192.168.125.131:3306 

分析完nodePort的工作方式,接下里說(shuō)一下clusterIP的訪問(wèn)方式。 對(duì)于直接訪問(wèn)cluster IP(10.254.162.44)的3306端口會(huì)直接跳轉(zhuǎn)到KUBE-SVC-67RL4FN6JRUPOJYM。

-A KUBE-SERVICES -d 10.254.162.44/32 -p tcp -m comment --comment "default/mysql-service: cluster IP" -m tcp --dport 3306 -j KUBE-SVC-67RL4FN6JRUPOJYM 

接下來(lái)的跳轉(zhuǎn)方式同NodePort方式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容