這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
- Service
- kube-proxy
- NodePort
k8s的pod可以有多個副本,但是在訪問pod時,會有幾個問題:
- 用戶端需要知道各個pod的地址
- 某一node上的pod如果故障,用戶端需要感知
為瞭解決這個問題,k8s引入了service的概念,用以指導用戶端的流量。
Service
以下面的my-nginx為例。
pod和service的定義檔案如下:
[root@localhost k8s]# cat run-my-nginx.yamlapiVersion: extensions/v1beta1kind: Deploymentmetadata: name: my-nginxspec: replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80[root@localhost k8s]# cat run-my-nginx-service.yamlapiVersion: v1kind: Servicemetadata: name: my-nginx labels: run: my-nginxspec: ports: - port: 80 protocol: TCP selector: run: my-nginx
pod my-nginx定義的replicas為2即2個副本,連接埠號碼為80;service my-nginx定義的selector為run: my-nginx
,即該service選中所有label為run: my-nginx
的pod;定義的port為80。
使用kubectl create -f xx.yml建立後,可以在叢集上看到2個pod,地址分別為10.244.1.10/10.244.2.10;可以看到1個service,IP/Port為10.11.97.177/80,其對接的Endpoints為10.244.1.10:80,10.244.2.10:80,即2個pod的服務地址,這三個URL在叢集內任一節點都可以使用curl訪問。
[root@localhost k8s]# kubectl get pods -n default -o wideNAME READY STATUS RESTARTS AGE IP NODEmy-nginx-379829228-3n755 1/1 Running 0 21h 10.244.1.10 note2my-nginx-379829228-xh214 1/1 Running 0 21h 10.244.2.10 node1[root@localhost ~]#[root@localhost ~]# kubectl describe svc my-nginxName: my-nginxNamespace: defaultLabels: run=my-nginxSelector: run=my-nginxType: ClusterIPIP: 10.11.97.177Port: <unset> 80/TCPEndpoints: 10.244.1.10:80,10.244.2.10:80Session Affinity: None
但是,如果你去查看叢集各節點的IP資訊,是找不到10.11.97.177這個IP的,那麼curl是如何通過這個(Virtual)IP地址訪問到後端的Endpoints呢?
答案在這裡。
kube-proxy
k8s支援2種proxy模式,userspace和iptables。從v1.2版本開始,預設採用iptables proxy。那麼這兩種模式有什麼不同嗎?
1、userspace
顧名思義,userspace即使用者空間。為什麼這麼叫呢?看下面的圖。
kube-proxy會為每個service隨機監聽一個連接埠(proxy port ),並增加一條iptables規則:所以到clusterIP:Port 的報文都redirect到proxy port;kube-proxy從它監聽的proxy port收到報文後,走round robin(預設)或者session affinity(會話親和力,即同一client IP都走同一鏈路給同一pod服務),分發給對應的pod。
顯然userspace會造成所有報文都走一遍使用者態,效能不高,現在k8s已經不再使用了。
2、iptables
我們回過頭來看看userspace,既然使用者態會增加效能損耗,那麼有沒有辦法不走呢?實際上使用者態也只是一個報文LB,通過iptables完全可以搞定。k8s下面這張圖很清晰的說明了iptables方式與userspace方式的不同:kube-proxy只是作為controller,而不是server,真正服務的是核心的netfilter,體現在使用者態則是iptables。
kube-proxy的iptables方式也支援round robin(預設)和session affinity。
那麼iptables是怎麼做到LB,而且還能round-robin呢?我們通過iptables-save來看my-nginx這個服務在某一個node上的iptables規則。
-A KUBE-SERVICES -d 10.11.97.177/32 -p tcp -m comment --comment "default/my-nginx: cluster IP" -m tcp --dport 80 -j KUBE-SVC-BEPXDJBUHFCSYIC3-A KUBE-SVC-BEPXDJBUHFCSYIC3 -m comment --comment "default/my-nginx:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-U4UWLP4OR3LOJBXU-A KUBE-SVC-BEPXDJBUHFCSYIC3 -m comment --comment "default/my-nginx:" -j KUBE-SEP-QHRWSLKOO5YUPI7O-A KUBE-SEP-U4UWLP4OR3LOJBXU -s 10.244.1.10/32 -m comment --comment "default/my-nginx:" -j KUBE-MARK-MASQ-A KUBE-SEP-U4UWLP4OR3LOJBXU -p tcp -m comment --comment "default/my-nginx:" -m tcp -j DNAT --to-destination 10.244.1.10:80-A KUBE-SEP-QHRWSLKOO5YUPI7O -s 10.244.2.10/32 -m comment --comment "default/my-nginx:" -j KUBE-MARK-MASQ-A KUBE-SEP-QHRWSLKOO5YUPI7O -p tcp -m comment --comment "default/my-nginx:" -m tcp -j DNAT --to-destination 10.244.2.10:80
第1條規則,終於看到這個virtual IP了。node上不需要有這個ip地址,iptables在看到目的地址為virutal ip的符合規則tcp報文,會走KUBE-SVC-BEPXDJBUHFCSYIC3規則。
第2/3條規則,KUBE-SVC-BEPXDJBUHFCSYIC3鏈實現了將報文按50%的統計機率隨機匹配到2條規則(round-robin)。
第4/5和5/6為成對的2組規則,將報文轉給了真正的服務pod。
至此,從物理node收到目的地址為10.11.97.177、連接埠號碼為80的報文開始,到pod my-nginx收到報文並響應,描述了一個完整的鏈路。可以看到,整個報文鏈路上沒有經過任何使用者態進程,效率和穩定性都比較高。
NodePort
上面的例子裡,由於10.11.97.177其實還是在叢集內有效地址,由於實際上並不存在這個地址,當從叢集外訪問時會訪問失敗,這時需要將service暴漏出去。k8s給出的一個方案是NodePort,用戶端根據NodePort+叢集內任一物理節點的IP,就可以訪問k8s的service了。這又是怎麼做到的呢?
答案還是iptables。我們來看下面這個sock-shop的例子,其建立方法見k8s.io,不再贅述。
[root@localhost k8s]# kubectl describe svc front-end -n sock-shopName: front-endNamespace: sock-shopLabels: name=front-endSelector: name=front-endType: NodePortIP: 10.15.9.0Port: <unset> 80/TCPNodePort: <unset> 30001/TCPEndpoints: 10.244.2.5:8079Session Affinity: None
在任一node上查看iptables-save:
-A KUBE-NODEPORTS -p tcp -m comment --comment "sock-shop/front-end:" -m tcp --dport 30001 -j KUBE-MARK-MASQ-A KUBE-NODEPORTS -p tcp -m comment --comment "sock-shop/front-end:" -m tcp --dport 30001 -j KUBE-SVC-LFMD53S3EZEAOUSJ-A KUBE-SERVICES -d 10.15.9.0/32 -p tcp -m comment --comment "sock-shop/front-end: cluster IP" -m tcp --dport 80 -j KUBE-SVC-LFMD53S3EZEAOUSJ-A KUBE-SVC-LFMD53S3EZEAOUSJ -m comment --comment "sock-shop/front-end:" -j KUBE-SEP-SM6TGF2R62ADFGQA-A KUBE-SEP-SM6TGF2R62ADFGQA -s 10.244.2.5/32 -m comment --comment "sock-shop/front-end:" -j KUBE-MARK-MASQ-A KUBE-SEP-SM6TGF2R62ADFGQA -p tcp -m comment --comment "sock-shop/front-end:" -m tcp -j DNAT --to-destination 10.244.2.5:8079
聰明如你,一定已經看明白了吧。
不過kube-proxy的iptables有個缺陷,即當pod故障時無法自動重試,需要依賴readiness probes,主要思想就是建立一個探測容器,當檢測到後端pod掛了的時候,更新iptables。