前一篇文章赖临,介紹了Docker容器的網(wǎng)絡(luò)模型惯豆。
容器是要被K8S編排卑硫、管理的踏施。而K8S又有自己的網(wǎng)絡(luò)模型幌羞。我們繼續(xù)學(xué)習(xí)斋陪、實驗瘤运,來理解K8S是怎么樣處理網(wǎng)絡(luò)流量的翰撑。
實驗之前罩旋,先分清楚K8S里面的三種IP地址啊央,和三種端口。
三種IP地址:
Cluster IP:K8S在創(chuàng)建Service時涨醋,生成的虛擬IP地址瓜饥。需要與Service Port合到一起,成為一個有效的通信端口浴骂。該IP地址用于集群內(nèi)部的訪問乓土。
Node IP:集群節(jié)點的IP地址。節(jié)點可能是物理機溯警,也可能是虛擬機趣苏。該地址真實存在于物理網(wǎng)絡(luò)。集群外部梯轻,可以通過該地址訪問到集群內(nèi)的節(jié)點食磕。
Pod IP:K8S創(chuàng)建Pod時,為該Pod分配的IP地址喳挑。該地址在集群外不可見彬伦。具體的IP地址網(wǎng)段、以及Pod之間通信的方式蟀悦,取決于集群創(chuàng)建時選用的CNI模型媚朦。本文選用了Flannel做為CNI,但不涉及CNI的分析日戈。
三種端口:
-
port:是集群Service偵聽的端口询张,與前面的Cluster IP合到一起,即Cluster IP:port浙炼,提供了集群內(nèi)部訪問Service的入口份氧。在K8S的yaml文件里面,port就是Service Port的縮寫弯屈。這是K8S創(chuàng)建Service時蜗帜,默認(rèn)的方式。
個人感覺资厉,這個名字其實挺容易讓人混淆的厅缺,還不如直接用關(guān)鍵字ServicePort來的清楚。
nodePort:是在節(jié)點上偵聽的端口宴偿。通過Node IP:nodePort的形式湘捎,提供了集群外部訪問集群中Service的入口。
targetPort:是在Pod上偵聽的端口窄刘,比如運行Nginx的Pod窥妇,會在80端口上監(jiān)聽HTTP請求。所有對Service Port和Node Port的訪問娩践,最后都會被轉(zhuǎn)發(fā)到Target Port來處理活翩。
下面烹骨,開始我們的實驗。
一. 安裝K8S集群
如果還沒有集群的話材泄,可以參考我的另一篇文章沮焕,先去把環(huán)境搭建好:基于Ubuntu安裝Kubernetes集群指南。
二. 創(chuàng)建Deployment
隨便找個目錄脸爱,執(zhí)行如下命令遇汞,創(chuàng)建一個yaml文件:
$ vi nginx_deployment.yaml
輸入這些內(nèi)容后,按:wq保存簿废、退出空入,得到nginx_deployment.yaml文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
這個Deployment將會生成2個副本的Pod,每個Pod里面都運行nginx族檬,Pod開放80端口歪赢。
然后用該yaml文件,去創(chuàng)建K8S資源:
$ kubectl apply -f nginx_deployment.yaml
deployment.apps/nginx-deployment configured
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-55f598f8d-49l2v 1/1 Running 0 87m 10.244.0.2 ycwang-ubuntu <none> <none>
nginx-deployment-55f598f8d-pxp6x 1/1 Running 0 87m 10.244.1.4 ycwang-ubuntu-worker <none> <none>
可以看到单料,兩個Pod已經(jīng)在運行埋凯,并且它們有各自的IP。
此時扫尖,通過Pod IP即可訪問Nginx:
$ wget 10.244.0.2
Connecting to 10.244.0.2:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 615 [text/html]
Saving to: ‘index.html’
index.html 100%[===============================================================================================================================>] 615 --.-KB/s in 0s
(62.7 MB/s) - ‘index.html’ saved [615/615]
$ wget 10.244.1.4
Connecting to 10.244.1.4:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 615 [text/html]
Saving to: ‘index.html.1’
index.html.1 100%[===============================================================================================================================>] 615 --.-KB/s in 0s
(84.2 MB/s) - ‘index.html.1’ saved [615/615]
但從集群外白对,是無法訪問這兩個IP地址的。
三. 創(chuàng)建Service
生成一個新的yaml文件换怖,用來創(chuàng)建Service資源甩恼。
$ vi nginx_svc.yaml
輸入這些內(nèi)容后,按:wq保存沉颂、退出条摸。
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
run: nginx-svc
spec:
ports:
- port: 8080
targetPort: 80
protocol: TCP
selector:
app: nginx
這個Service使用app=nginx標(biāo)簽選擇器,來選擇對應(yīng)的Pod铸屉,做為Service的后端钉蒲。Service的類型是默認(rèn)的Service Port,所以不必寫出來彻坛。
Service監(jiān)聽在8080端口顷啼,集群內(nèi)部可以通過ClusterIP:8080進行訪問,并把流量轉(zhuǎn)發(fā)到Pod的80端口進行實際的業(yè)務(wù)處理昌屉。
執(zhí)行命令钙蒙,用該yaml文件去創(chuàng)建Service:
$ kubectl apply -f nginx_svc.yaml
service/nginx-svc created
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 145m
nginx-svc ClusterIP 10.104.112.175 <none> 8080/TCP 3s
可以看到,該Service已經(jīng)創(chuàng)建成功怠益。并且出現(xiàn)了一個新的IP地址:10.104.112.175仪搔。這就是我們前面介紹的第一種IP地址:Cluster IP瘾婿。
通過該虛擬的IP地址蜻牢,加上指定的8080端口烤咧,即可實現(xiàn)集群內(nèi)部對Service的訪問:
$ wget 10.104.112.175:8080
Connecting to 10.104.112.175:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 615 [text/html]
Saving to: ‘index.html’
index.html 100%[===============================================================================================================================>] 615 --.-KB/s in 0s
(60.4 MB/s) - ‘index.html’ saved [615/615]
那么,K8S是如何訪問這個不存在的虛擬IP地址的呢抢呆?
四. 分析Cluster IP訪問流程
先看一下這個Service的信息:
$ kubectl describe service nginx-svc
Name: nginx-svc
Namespace: default
Labels: run=nginx-svc
Annotations: <none>
Selector: app=nginx
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.104.112.175
IPs: 10.104.112.175
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.244.0.2:80,10.244.1.4:80
Session Affinity: None
Events: <none>
這個Service對應(yīng)了后端的兩個Pod:10.244.0.2:80,10.244.1.4:80煮嫌。
就是說,對于10.104.112.175:8080的訪問抱虐,最后會被轉(zhuǎn)發(fā)到10.244.0.2:80或者10.244.1.4:80昌阿。
這要感謝iptables在后面默默的干活。
查看目前iptables的情況:
$ sudo iptables-save | grep 10.104.112.175
......
-A KUBE-SERVICES -d 10.104.112.175/32 -p tcp -m comment --comment "default/nginx-svc cluster IP" -m tcp --dport 8080 -j KUBE-SVC-HL5LMXD5JFHQZ6LN
......
對于10.104.112.175的訪問恳邀,會被跳轉(zhuǎn)到規(guī)則:KUBE-SVC-HL5LMXD5JFHQZ6LN懦冰。
繼續(xù)查看這條規(guī)則:
$ sudo iptables-save | grep KUBE-SVC-HL5LMXD5JFHQZ6LN
-A KUBE-SVC-HL5LMXD5JFHQZ6LN -m comment --comment "default/nginx-svc -> 10.244.0.2:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-T5AFCZ323NYPWW2A
-A KUBE-SVC-HL5LMXD5JFHQZ6LN -m comment --comment "default/nginx-svc -> 10.244.1.4:80" -j KUBE-SEP-RQ66ZV5Y2RYOH2X3
iptables把對10.104.112.175的訪問,采用輪詢的負(fù)載均衡策略谣沸,依次轉(zhuǎn)發(fā)給:10.244.0.2:80和10.244.1.4:80刷钢。
從而實現(xiàn)了在集群內(nèi)部對Cluster IP:port的訪問,并自帶了負(fù)載均衡功能乳附。
另外内地,這些iptables規(guī)則的增刪改都是由運行在每個節(jié)點的kube-proxy來實現(xiàn)的。
五. 創(chuàng)建NodePort類型的Service
現(xiàn)在赋除,我們把Service的類型改成NodePort阱缓。
$ vi nginx_svc.yaml
輸入如下內(nèi)容,并按:wq保存举农、退出:
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
run: nginx-svc
spec:
type: NodePort
ports:
- port: 8080
targetPort: 80
nodePort: 30000
protocol: TCP
selector:
app: nginx
在這個yaml文件荆针,把Service的類型指定為NodePort,并在每個Node上并蝗,偵聽30000端口祭犯。對30000端口的訪問,最后會被轉(zhuǎn)發(fā)到Pod的80端口滚停。
先把之前的Service和Deployment都刪掉沃粗,再用新的yaml文件重新創(chuàng)建:
$ kubectl delete -f nginx_svc.yaml
service "nginx-svc" deleted
$ kubectl delete -f nginx_deployment.yaml
deployment.apps "nginx-deployment" deleted
$ kubectl apply -f nginx_deployment.yaml
deployment.apps/nginx-deployment created
$ kubectl apply -f nginx_svc.yaml
service/nginx-svc created
查看Service和Pod的具體信息:
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3h41m
nginx-svc NodePort 10.110.65.131 <none> 8080:30000/TCP 10s
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-55f598f8d-ls786 1/1 Running 0 11m 10.244.1.6 ycwang-ubuntu-worker <none> <none>
nginx-deployment-55f598f8d-pj2xk 1/1 Running 0 11m 10.244.0.3 ycwang-ubuntu <none> <none>
確認(rèn)都在正常運行了。
此時键畴,可以在集群內(nèi)最盅,通過Node IP:NodePort進行訪問,此節(jié)點的IP是192.168.111.128:
$ wget 192.168.111.128:30000
Connecting to 192.168.111.128:30000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 615 [text/html]
Saving to: ‘index.html’
index.html 100%[===============================================================================================================================>] 615 --.-KB/s in 0s
(87.4 MB/s) - ‘index.html’ saved [615/615]
也可以在集群外的Windows機器起惕,通過Node IP:NodePort進行訪問:
$ curl 192.168.111.128:30000
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 615 100 615 0 0 207k 0 --:--:-- --:--:-- --:--:-- 300k
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a >nginx.org</a>.<br/>
Commercial support is available at
<a >nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
六. 分析Node IP:NodePort訪問流程
我們繼續(xù)探究涡贱,訪問Node IP:NodePort是怎么被轉(zhuǎn)到Pod上面去的?
答案依然是iptables:
$ sudo iptables-save | grep 30000
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx-svc" -m tcp --dport 30000 -j KUBE-EXT-HL5LMXD5JFHQZ6LN
對30000端口的訪問惹想,會被跳轉(zhuǎn)到規(guī)則:KUBE-EXT-HL5LMXD5JFHQZ6LN问词。
而KUBE-EXT-HL5LMXD5JFHQZ6LN,又被跳轉(zhuǎn)到:KUBE-SVC-HL5LMXD5JFHQZ6LN
$ sudo iptables-save | grep KUBE-EXT-HL5LMXD5JFHQZ6LN
-A KUBE-EXT-HL5LMXD5JFHQZ6LN -j KUBE-SVC-HL5LMXD5JFHQZ6LN
KUBE-SVC-HL5LMXD5JFHQZ6LN這條規(guī)則的具體內(nèi)容:
$ sudo iptables-save | grep KUBE-SVC-HL5LMXD5JFHQZ6LN
-A KUBE-SERVICES -d 10.110.65.131/32 -p tcp -m comment --comment "default/nginx-svc cluster IP" -m tcp --dport 8080 -j KUBE-SVC-HL5LMXD5JFHQZ6LN
-A KUBE-SVC-HL5LMXD5JFHQZ6LN -m comment --comment "default/nginx-svc -> 10.244.0.3:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-PU7AOSZG6OVFMASF
-A KUBE-SVC-HL5LMXD5JFHQZ6LN -m comment --comment "default/nginx-svc -> 10.244.1.6:80" -j KUBE-SEP-OZ4KTOWKCOJKYUPL
跟Cluster IP的做法一樣嘀粱,iptables把對Node IP:NodePort的訪問激挪,采用輪詢的負(fù)載均衡策略辰狡,依次轉(zhuǎn)發(fā)給:10.244.0.3:80和10.244.1.6:80這兩個Endpoints。
K8S里面的網(wǎng)絡(luò)訪問流程差不多就這樣了垄分。它采用了一個很巧妙的設(shè)計宛篇,去中心化、讓每個節(jié)點都承擔(dān)了負(fù)載均衡的功能薄湿。
補充點題外話叫倍,在Node IP:NodePort這種模式下,直接訪問節(jié)點還是會有點問題的豺瘤。
因為客戶需要指定某個Node進行訪問吆倦。這樣會帶來單點問題;而且坐求,客戶按理不應(yīng)該知道逼庞、也不需要知道具體的Node和它的IP。
所以瞻赶,在實際應(yīng)用中赛糟,可以在K8S集群外部,搭建一個負(fù)載均衡器砸逊¤的希客戶訪問此負(fù)載均衡器,再由該負(fù)載均衡器把流量分發(fā)到各個Node上师逸。很多云廠商也已經(jīng)帶了這樣的功能司倚。
但是,既然外部有了支持各種負(fù)載均衡算法的職業(yè)選手篓像,把流量分發(fā)到各個Node上动知。如果Node收到后,再次用iptables進行負(fù)載均衡员辩,就沒有什么意義了盒粮。不清楚Google為什么要這么設(shè)計?
是不是可以考慮在K8S里面內(nèi)置一個負(fù)載均衡的模塊奠滑,專門運行在某個Node上丹皱。在NodePort模式下,可以選擇啟用該模塊宋税,由它來專門提供客戶訪問的入口并做負(fù)載均衡摊崭,然后此刻各個Node上的iptables負(fù)載均衡可以禁用了?期待各路高人高見……
BTW一下杰赛,既然都說到了負(fù)載均衡呢簸,捆綁推銷一下我的另一篇文章吧:負(fù)載均衡算法的實現(xiàn)。