一篇文章為你圖解Kubernetes網(wǎng)絡(luò)通信原理
名詞解釋
1、網(wǎng)絡(luò)的命名空間:Linux在網(wǎng)絡(luò)棧中引入網(wǎng)絡(luò)命名空間,將獨(dú)立的網(wǎng)絡(luò)協(xié)議棧隔離到不同的命令空間中,彼此間無(wú)法通信;docker利用這一特性,實(shí)現(xiàn)不容器間的網(wǎng)絡(luò)隔離。
2、Veth設(shè)備對(duì):也叫虛擬網(wǎng)絡(luò)接口對(duì)。Veth設(shè)備對(duì)的引入是為了實(shí)現(xiàn)在不同網(wǎng)絡(luò)命名空間的通信。
3、Iptables/Netfilter:Netfilter負(fù)責(zé)在內(nèi)核中執(zhí)行各種掛接的規(guī)則(過(guò)濾、修改、丟棄等),運(yùn)行在內(nèi)核 模式中;Iptables模式是在用戶模式下運(yùn)行的進(jìn)程,負(fù)責(zé)協(xié)助維護(hù)內(nèi)核中Netfilter的各種規(guī)則表;通過(guò)二者的配合來(lái)實(shí)現(xiàn)整個(gè)Linux網(wǎng)絡(luò)協(xié)議棧中靈活的數(shù)據(jù)包處理機(jī)制。
4、網(wǎng)橋:網(wǎng)橋是一個(gè)二層網(wǎng)絡(luò)設(shè)備,通過(guò)網(wǎng)橋可以將linux支持的不同的端口連接起來(lái),并實(shí)現(xiàn)類似交換機(jī)那樣的多對(duì)多的通信。
5、路由:Linux系統(tǒng)包含一個(gè)完整的路由功能,當(dāng)IP層在處理數(shù)據(jù)發(fā)送或轉(zhuǎn)發(fā)的時(shí)候,會(huì)使用路由表來(lái)決定發(fā)往哪里。
令人頭大的網(wǎng)絡(luò)模型
Kubernetes對(duì)集群內(nèi)部的網(wǎng)絡(luò)進(jìn)行了重新抽象,以實(shí)現(xiàn)整個(gè)集群網(wǎng)絡(luò)扁平化。我們可以理解網(wǎng)絡(luò)模型時(shí),可以完全抽離物理節(jié)點(diǎn)去理解,我們用圖說(shuō)話,先有基本印象。

其中,重點(diǎn)講解以下幾個(gè)關(guān)鍵抽象概念。
一個(gè)Service
Service是Kubernetes為為屏蔽這些后端實(shí)例(Pod)的動(dòng)態(tài)變化和對(duì)多實(shí)例的負(fù)載均衡而引入的資源對(duì)象。Service通常與deployment綁定,定義了服務(wù)的訪問(wèn)入口地址,應(yīng)用(Pod)可以通過(guò)這個(gè)入口地址訪問(wèn)其背后的一組由Pod副本組成的集群實(shí)例。Service與其后端Pod副本集群之間則是通過(guò)Label Selector來(lái)實(shí)現(xiàn)映射。
Service的類型(Type)決定了Service如何對(duì)外提供服務(wù),根據(jù)類型不同,服務(wù)可以只在Kubernetes cluster中可見(jiàn),也可以暴露到集群外部。Service有三種類型,ClusterIP,NodePort和LoadBalancer。具體的使用場(chǎng)景會(huì)在下文中進(jìn)行闡述。
在測(cè)試環(huán)境查看:
- $ kubectl get svc --selector app=nginx
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
- nginx ClusterIP 172.19.0.166 <none> 80/TCP 1m
- $ kubectl describe svc nginx
- Name: nginx
- Namespace: default
- Labels: app=nginx
- Annotations: <none>
- Selector: app=nginx
- Type: ClusterIP
- IP: 172.19.0.166
- Port: <unset> 80/TCP
- TargetPort: 80/TCP
- Endpoints: 172.16.2.125:80,172.16.2.229:80
- Session Affinity: None
- Events: <none>
上述信息中該svc后端代理了2個(gè)Pod實(shí)例:172.16.2.125:80,172.16.2.229:80
二個(gè)IP
Kubernetes為描述其網(wǎng)絡(luò)模型的IP對(duì)象,抽象出Cluster IP和Pod IP的概念。
PodIP是Kubernetes集群中每個(gè)Pod的IP地址。它是Docker Engine 根據(jù)docker0網(wǎng)橋的IP地址段進(jìn)行分配的,是一個(gè)虛擬的二層網(wǎng)絡(luò)。Kubernetes中Pod間能夠彼此直接通訊,Pod里的容器訪問(wèn)另外一個(gè)Pod里的容器,是通過(guò)Pod IP所在進(jìn)行通信。
Cluster IP僅作用于Service,其沒(méi)有實(shí)體對(duì)象所對(duì)應(yīng),因此Cluster IP無(wú)法被ping通。它的作用是為Service后端的實(shí)例提供統(tǒng)一的訪問(wèn)入口。當(dāng)訪問(wèn)ClusterIP時(shí),請(qǐng)求將被轉(zhuǎn)發(fā)到后端的實(shí)例上,默認(rèn)是輪詢方式。Cluster IP和Service一樣由kube-proxy組件維護(hù),其實(shí)現(xiàn)方式主要有兩種,iptables和IPVS。在1.8版本后kubeproxy開(kāi)始支持IPVS方式。在上例中,SVC的信息中包含了Cluster IP。
這里未列出nodeip概念,由于其本身是物理機(jī)的網(wǎng)卡IP。因此可理解為nodeip就是物理機(jī)IP。
三個(gè)Port
在Kubernetes中,涉及容器,Pod,Service,集群各等多個(gè)層級(jí)的對(duì)象間的通信,為在網(wǎng)絡(luò)模型中區(qū)分各層級(jí)的通信端口,這里對(duì)Port進(jìn)行了抽象。
Port
該P(yáng)ort非一般意義上的TCP/IP中的Port概念,它是特指Kubernetes中Service的port,是Service間的訪問(wèn)端口,例如Mysql的Service默認(rèn)3306端口。它僅對(duì)進(jìn)群內(nèi)容器提供訪問(wèn)權(quán)限,而無(wú)法從集群外部通過(guò)該端口訪問(wèn)服務(wù)。
nodePort
nodePort為外部機(jī)器提供了訪問(wèn)集群內(nèi)服務(wù)的方式。比如一個(gè)Web應(yīng)用需要被其他用戶訪問(wèn),那么需要配置type=NodePort,而且配置nodePort=30001,那么其他機(jī)器就可以通過(guò)瀏覽器訪問(wèn)scheme://node:30001訪問(wèn)到該服務(wù),例如http://node:30001。
targetPort
targetPort是容器的端口(最根本的端口入口),與制作容器時(shí)暴露的端口一致(DockerFile中EXPOSE),例如docker.io官方的nginx暴露的是80端口。
舉一個(gè)例子來(lái)看如何配置Service的port:
- kind: Service
- apiVersion: v1
- metadata:
- name: mallh5-service
- namespace: abcdocker
- spec:
- selector:
- app: mallh5web
- type: NodePort
- ports:
- - protocol: TCP
- port: 3017
- targetPort: 5003
- nodePort: 31122
這里舉出了一個(gè)service的yaml,其部署在abcdocker的namespace中。這里配置了nodePort,因此其類型Type就是NodePort,注意大小寫(xiě)。若沒(méi)有配置nodePort,那這里需要填寫(xiě)ClusterIP,即表示只支持集群內(nèi)部服務(wù)訪問(wèn)。
集群內(nèi)部通信
單節(jié)點(diǎn)通信
集群?jiǎn)喂?jié)點(diǎn)內(nèi)的通信,主要包括兩種情況,同一個(gè)pod內(nèi)的多容器間通信以及同一節(jié)點(diǎn)不同pod間的通信。由于不涉及跨節(jié)點(diǎn)訪問(wèn),因此流量不會(huì)經(jīng)過(guò)物理網(wǎng)卡進(jìn)行轉(zhuǎn)發(fā)。
通過(guò)查看路由表,也能窺見(jiàn)一二:
- root@node-1:/opt/bin# route -n
- Kernel IP routing table
- Destination Gateway Genmask Flags Metric Ref Use Iface
- 0.0.0.0 172.23.100.1 0.0.0.0 UG 0 0 0 eth0
- 10.1.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel.1 #flannel 網(wǎng)絡(luò)內(nèi)跨節(jié)點(diǎn)的通信會(huì)交給 flannel.1 處理
- 10.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 docker0 #flannel 網(wǎng)絡(luò)內(nèi)節(jié)點(diǎn)內(nèi)的通信會(huì)走 docker0
1 Pod內(nèi)通信
如下圖所示:

這種情況下,同一個(gè)pod內(nèi)共享網(wǎng)絡(luò)命名空間,容器之間通過(guò)訪問(wèn)127.0.0.1:(端口)即可。圖中的veth*即指veth對(duì)的一端(另一端未標(biāo)注,但實(shí)際上是成對(duì)出現(xiàn)),該veth對(duì)是由Docker Daemon掛載在docker0網(wǎng)橋上,另一端添加到容器所屬的網(wǎng)絡(luò)命名空間,圖上顯示是容器中的eth0。
圖中演示了bridge模式下的容器間通信。docker1向docker2發(fā)送請(qǐng)求,docker1,docker2均與docker0建立了veth對(duì)進(jìn)行通訊。
當(dāng)請(qǐng)求經(jīng)過(guò)docker0時(shí),由于容器和docker0同屬于一個(gè)子網(wǎng),因此請(qǐng)求經(jīng)過(guò)docker2與docker0的veth*對(duì),轉(zhuǎn)發(fā)到docker2,該過(guò)程并未跨節(jié)點(diǎn),因此不經(jīng)過(guò)eth0。
2 Pod間通信
同節(jié)點(diǎn)pod間通信
由于Pod內(nèi)共享網(wǎng)絡(luò)命名空間(由pause容器創(chuàng)建),所以本質(zhì)上也是同節(jié)點(diǎn)容器間的通信。同時(shí),同一Node中Pod的默認(rèn)路由都是docker0的地址,由于它們關(guān)聯(lián)在同一個(gè)docker0網(wǎng)橋上,地址網(wǎng)段相同,所有它們之間應(yīng)當(dāng)是能直接通信的。來(lái)看看實(shí)際上這一過(guò)程如何實(shí)現(xiàn)。如上圖,Pod1中容器1和容器2共享網(wǎng)絡(luò)命名空間,因此對(duì)pod外的請(qǐng)求通過(guò)pod1和Docker0網(wǎng)橋的veth對(duì)(圖中掛在eth0和ethx上)實(shí)現(xiàn)。

訪問(wèn)另一個(gè)pod內(nèi)的容器,其請(qǐng)求的地址是PodIP而非容器的ip,實(shí)際上也是同一個(gè)子網(wǎng)間通信,直接經(jīng)過(guò)veth對(duì)轉(zhuǎn)發(fā)即可。
跨節(jié)點(diǎn)通信
CNI:容器網(wǎng)絡(luò)接口
CNI 是一種標(biāo)準(zhǔn),它旨在為容器平臺(tái)提供網(wǎng)絡(luò)的標(biāo)準(zhǔn)化。不同的容器平臺(tái)(比如目前的 kubernetes、mesos 和 rkt)能夠通過(guò)相同的接口調(diào)用不同的網(wǎng)絡(luò)組件。
目前kubernetes支持的CNI組件種類很多,例如:bridge calico calico-ipam dhcp flannel host-local ipvlan loopback macvlan portmap ptp sample tuning vlan。在docker中,主流的跨主機(jī)通信方案主要有一下幾種:
1)基于隧道的overlay網(wǎng)絡(luò):按隧道類型來(lái)說(shuō),不同的公司或者組織有不同的實(shí)現(xiàn)方案。docker原生的overlay網(wǎng)絡(luò)就是基于vxlan隧道實(shí)現(xiàn)的。ovn則需要通過(guò)geneve或者stt隧道來(lái)實(shí)現(xiàn)的。flannel最新版本也開(kāi)始默認(rèn)基于vxlan實(shí)現(xiàn)overlay網(wǎng)絡(luò)。
2)基于包封裝的overlay網(wǎng)絡(luò):基于UDP封裝等數(shù)據(jù)包包裝方式,在docker集群上實(shí)現(xiàn)跨主機(jī)網(wǎng)絡(luò)。典型實(shí)現(xiàn)方案有weave、flannel的早期版本。
3)基于三層實(shí)現(xiàn)SDN網(wǎng)絡(luò):基于三層協(xié)議和路由,直接在三層上實(shí)現(xiàn)跨主機(jī)網(wǎng)絡(luò),并且通過(guò)iptables實(shí)現(xiàn)網(wǎng)絡(luò)的安全隔離。典型的方案為Project Calico。同時(shí)對(duì)不支持三層路由的環(huán)境,Project Calico還提供了基于IPIP封裝的跨主機(jī)網(wǎng)絡(luò)實(shí)現(xiàn)
通信方式

集群內(nèi)跨節(jié)點(diǎn)通信涉及到不同的子網(wǎng)間通信,僅靠docker0無(wú)法實(shí)現(xiàn),這里需要借助CNI網(wǎng)絡(luò)插件來(lái)實(shí)現(xiàn)。圖中展示了使用flannel實(shí)現(xiàn)跨節(jié)點(diǎn)通信的方式。
簡(jiǎn)單說(shuō)來(lái),flannel的用戶態(tài)進(jìn)程flanneld會(huì)為每個(gè)node節(jié)點(diǎn)創(chuàng)建一個(gè)flannel.1的網(wǎng)橋,根據(jù)etcd或apiserver的全局統(tǒng)一的集群信息為每個(gè)node分配全局唯一的網(wǎng)段,避免地址沖突。同時(shí)會(huì)為docker0和flannel.1創(chuàng)建veth對(duì),docker0將報(bào)文丟給flannel.1,。
Flanneld維護(hù)了一份全局node的網(wǎng)絡(luò)表,通過(guò)flannel.1接收到請(qǐng)求后,根據(jù)node表,將請(qǐng)求二次封裝為UDP包,扔給eth0,由eth0出口進(jìn)入物理網(wǎng)路發(fā)送給目的node。
在另一端以相反的流程。Flanneld解包并發(fā)往docker0,進(jìn)而發(fā)往目的Pod中的容器。
外部訪問(wèn)集群
從集群外訪問(wèn)集群有多種方式,比如loadbalancer,Ingress,nodeport,nodeport和loadbalancer是service的兩個(gè)基本類型,是將service直接對(duì)外暴露的方式,ingress則是提供了七層負(fù)載均衡,其基本原理將外部流量轉(zhuǎn)發(fā)到內(nèi)部的service,再轉(zhuǎn)發(fā)到后端endpoints,在平時(shí)的使用中,我們可以依據(jù)具體的業(yè)務(wù)需求選用不同的方式。這里主要介紹nodeport和ingress方式。
Nodeport
通過(guò)將Service的類型設(shè)置為NodePort,就可以在Cluster中的主機(jī)上通過(guò)一個(gè)指定端口暴露服務(wù)。注意通過(guò)Cluster中每臺(tái)主機(jī)上的該指定端口都可以訪問(wèn)到該服務(wù),發(fā)送到該主機(jī)端口的請(qǐng)求會(huì)被kubernetes路由到提供服務(wù)的Pod上。采用這種服務(wù)類型,可以在kubernetes cluster網(wǎng)絡(luò)外通過(guò)主機(jī)IP:端口的方式訪問(wèn)到服務(wù)。

這里給出一個(gè)influxdb的例子,我們也可以針對(duì)這個(gè)模板去修改成其他的類型:
- kind: Service
- apiVersion: v1
- metadata:
- name: influxdb
- spec:
- type: NodePort
- ports:
- - port: 8086
- nodePort: 31112
- selector:
- name: influxdb
Ingress

Ingress是推薦在生產(chǎn)環(huán)境使用的方式,它起到了七層負(fù)載均衡器和Http方向代理的作用,可以根據(jù)不同的url把入口流量分發(fā)到不同的后端Service。外部客戶端只看到foo.bar.com這個(gè)服務(wù)器,屏蔽了內(nèi)部多個(gè)Service的實(shí)現(xiàn)方式。采用這種方式,簡(jiǎn)化了客戶端的訪問(wèn),并增加了后端實(shí)現(xiàn)和部署的靈活性,可以在不影響客戶端的情況下對(duì)后端的服務(wù)部署進(jìn)行調(diào)整。
其部署的yaml可以參考如下模板:
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: test
- annotations:
- ingress.kubernetes.io/rewrite-target: /
- spec:
- rules:
- - host: test.name.com
- http:
- paths:
- - path: /test
- backend:
- serviceName: service-1
- servicePort: 8118
- - path: /name
- backend:
- serviceName: service-2
- servicePort: 8228
這里我們定義了一個(gè)ingress模板,定義通過(guò)test.name.com來(lái)訪問(wèn)服務(wù),在虛擬主機(jī)test.name.com下面定義了兩個(gè)Path,其中/test被分發(fā)到后端服務(wù)s1,/name被分發(fā)到后端服務(wù)s2。
集群中可以定義多個(gè)ingress,來(lái)完成不同服務(wù)的轉(zhuǎn)發(fā),這里需要一個(gè)ingress controller來(lái)管理集群中的Ingress規(guī)則。Ingress Contronler 通過(guò)與 Kubernetes API 交互,動(dòng)態(tài)的去感知集群中 Ingress 規(guī)則變化,然后讀取它,按照自定義的規(guī)則,規(guī)則就是寫(xiě)明了哪個(gè)域名對(duì)應(yīng)哪個(gè)service,生成一段 Nginx 配置,再寫(xiě)到 Nginx-ingress-control的 Pod 里,這個(gè) Ingress Contronler 的pod里面運(yùn)行著一個(gè)nginx服務(wù),控制器會(huì)把生成的nginx配置寫(xiě)入/etc/nginx.conf文件中,然后 reload使用配置生效。
Kubernetes提供的Ingress Controller模板如下:
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: test
- annotations:
- ingress.kubernetes.io/rewrite-target: /
- spec:
- rules:
- - host: foo.bar.com
- http:
- paths:
- - path: /foo
- backend:
- serviceName: s1
- servicePort: 80
- - path: /bar
- backend:
- serviceName: s2
- servicePort: 80
總結(jié)及展望
本文針對(duì)kubernetes的網(wǎng)絡(luò)模型,從一個(gè)service,二個(gè)IP,三個(gè)port出發(fā)進(jìn)行圖解。詳解kubernetes集群內(nèi)及集群外部訪問(wèn)方式。