層層推進(jìn)!Kubernetes 網(wǎng)絡(luò)原理圖解,我用這招!
名詞解釋
1、網(wǎng)絡(luò)的命名空間:Linux 在網(wǎng)絡(luò)棧中引入網(wǎng)絡(luò)命名空間,將獨(dú)立的網(wǎng)絡(luò)協(xié)議棧隔離到不同的命名空間中,彼此間無法通信;Docker 利用這一特性,實(shí)現(xiàn)不容器間的網(wǎng)絡(luò)隔離。
2、Veth 設(shè)備對:也叫虛擬網(wǎng)絡(luò)接口對。Veth設(shè)備對的引入是為了實(shí)現(xiàn)在不同網(wǎng)絡(luò)命名空間的通信。
3、Iptables/Netfilter:Netfilter 負(fù)責(zé)在內(nèi)核中執(zhí)行各種掛接的規(guī)則(過濾、修改、丟棄等),運(yùn)行在內(nèi)核 模式中;Iptables模式是在用戶模式下運(yùn)行的進(jìn)程,負(fù)責(zé)協(xié)助維護(hù)內(nèi)核中 Netfilter 的各種規(guī)則表;通過二者的配合來實(shí)現(xiàn)整個 Linux 網(wǎng)絡(luò)協(xié)議棧中靈活的數(shù)據(jù)包處理機(jī)制。
4、網(wǎng)橋:網(wǎng)橋是一個二層網(wǎng)絡(luò)設(shè)備,通過網(wǎng)橋可以將 linux 支持的不同的端口連接起來,并實(shí)現(xiàn)類似交換機(jī)那樣的多對多的通信。
5、路由:Linux 系統(tǒng)包含一個完整的路由功能,當(dāng)IP層在處理數(shù)據(jù)發(fā)送或轉(zhuǎn)發(fā)的時(shí)候,會使用路由表來決定發(fā)往哪里。
令人頭大的網(wǎng)絡(luò)模型
Kubernetes對集群內(nèi)部的網(wǎng)絡(luò)進(jìn)行了重新抽象,以實(shí)現(xiàn)整個集群網(wǎng)絡(luò)扁平化。我們可以理解網(wǎng)絡(luò)模型時(shí),可以完全抽離物理節(jié)點(diǎn)去理解,我們用圖說話,先有基本印象。
其中,重點(diǎn)講解以下幾個關(guān)鍵抽象概念。
一個 Service
Service 是 Kubernetes 為屏蔽這些后端實(shí)例(Pod)的動態(tài)變化和對多實(shí)例的負(fù)載均衡而引入的資源對象。Service 通常與 deployment 綁定,定義了服務(wù)的訪問入口地址,應(yīng)用(Pod)可以通過這個入口地址訪問其背后的一組由 Pod 副本組成的集群實(shí)例。Service 與其后端 Pod 副本集群之間則是通過 Label Selector 來實(shí)現(xiàn)映射。
Service的類型(Type)決定了 Service 如何對外提供服務(wù),根據(jù)類型不同,服務(wù)可以只在Kubernetes cluster中可見,也可以暴露到集群外部。Service有三種類型,ClusterIP,NodePort 和 LoadBalancer。具體的使用場景會在下文中進(jìn)行闡述。
在測試環(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個Pod實(shí)例:172.16.2.125:80,172.16.2.229:80
二個 IP
Kubernetes 為描述其網(wǎng)絡(luò)模型的 IP 對象,抽象出 Cluster IP和Pod IP的概念。
Pod IP 是 Kubernetes 集群中每個 Pod 的 IP 地址。它是 Docker Engine 根據(jù) docker0網(wǎng)橋的IP地址段進(jìn)行分配的,是一個虛擬的二層網(wǎng)絡(luò)。Kubernetes 中 Pod 間能夠彼此直接通訊,Pod 里的容器訪問另外一個Pod里的容器,是通過Pod IP所在進(jìn)行通信。
Cluster IP僅作用于 Service,其沒有實(shí)體對象所對應(yīng),因此 Cluster IP 無法被ping通。它的作用是為 Service 后端的實(shí)例提供統(tǒng)一的訪問入口。當(dāng)訪問 Cluster IP 時(shí),請求將被轉(zhuǎn)發(fā)到后端的實(shí)例上,默認(rèn)是輪詢方式。Cluster IP 和 Service一樣由 kube-proxy 組件維護(hù),其實(shí)現(xiàn)方式主要有兩種,iptables 和 IPVS。在 1.8 版本后 kubeproxy 開始支持IPVS 方式。在上例中,SVC的信息中包含了Cluster IP。
這里未列出 node ip 概念,由于其本身是物理機(jī)的網(wǎng)卡IP。因此可理解為nodeip就是物理機(jī)IP。
三個 Port
在 Kubernetes 中,涉及容器,Pod,Service,集群各等多個層級的對象間的通信,為在網(wǎng)絡(luò)模型中區(qū)分各層級的通信端口,這里對Port進(jìn)行了抽象。
Port
該P(yáng)ort非一般意義上的TCP/IP中的Port概念,它是特指Kubernetes中Service的port,是Service間的訪問端口,例如Mysql的Service默認(rèn)3306端口。它僅對進(jìn)群內(nèi)容器提供訪問權(quán)限,而無法從集群外部通過該端口訪問服務(wù)。
nodePort
nodePort為外部機(jī)器提供了訪問集群內(nèi)服務(wù)的方式。比如一個Web應(yīng)用需要被其他用戶訪問,那么需要配置type=NodePort,而且配置nodePort=30001,那么其他機(jī)器就可以通過瀏覽器訪問scheme://node:30001訪問到該服務(wù),例如http://node:30001。
targetPort
targetPort是容器的端口(最根本的端口入口),與制作容器時(shí)暴露的端口一致(DockerFile中EXPOSE),例如 http://docker.io 官方的 nginx 暴露的是80端口。
舉一個例子來看如何配置 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
這里舉出了一個service的yaml,其部署在abcdocker的namespace中。這里配置了nodePort,因此其類型Type就是NodePort,注意大小寫。若沒有配置nodePort,那這里需要填寫ClusterIP,即表示只支持集群內(nèi)部服務(wù)訪問。
集群內(nèi)部通信
單節(jié)點(diǎn)通信
集群單節(jié)點(diǎn)內(nèi)的通信,主要包括兩種情況,同一個 pod 內(nèi)的多容器間通信以及同一節(jié)點(diǎn)不同 pod 間的通信。由于不涉及跨節(jié)點(diǎn)訪問,因此流量不會經(jīng)過物理網(wǎng)卡進(jìn)行轉(zhuǎn)發(fā)。
通過查看路由表,也能窺見一二:
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)的通信會交給 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)的通信會走 docker0
1 Pod 內(nèi)通信
如下圖所示:
這種情況下,同一個pod內(nèi)共享網(wǎng)絡(luò)命名空間,容器之間通過訪問 127.0.0.1:(端口)即可。圖中的 veth* 即指veth對的一端(另一端未標(biāo)注,但實(shí)際上是成對出現(xiàn)),該veth對是由 Docker Daemon 掛載在 docker0 網(wǎng)橋上,另一端添加到容器所屬的網(wǎng)絡(luò)命名空間,圖上顯示是容器中的eth0。
圖中演示了 bridge 模式下的容器間通信。docker1 向 docker2 發(fā)送請求,docker1,docker2 均與 docker0 建立了 veth 對進(jìn)行通訊。
當(dāng)請求經(jīng)過 docker0 時(shí),由于容器和 docker0 同屬于一個子網(wǎng),因此請求經(jīng)過 docker2與docker0的veth*對,轉(zhuǎn)發(fā)到docker2,該過程并未跨節(jié)點(diǎn),因此不經(jīng)過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)在同一個 docker0 網(wǎng)橋上,地址網(wǎng)段相同,所有它們之間應(yīng)當(dāng)是能直接通信的。來看看實(shí)際上這一過程如何實(shí)現(xiàn)。如上圖,Pod1 中容器 1和容器 2 共享網(wǎng)絡(luò)命名空間,因此對pod 外的請求通過 pod1 和 Docker0 網(wǎng)橋的 veth對(圖中掛在eth0和ethx上)實(shí)現(xiàn)。
訪問另一個pod內(nèi)的容器,其請求的地址是PodIP而非容器的ip,實(shí)際上也是同一個子網(wǎng)間通信,直接經(jīng)過veth對轉(zhuǎn)發(fā)即可。
跨節(jié)點(diǎn)通信
CNI:容器網(wǎng)絡(luò)接口
CNI 是一種標(biāo)準(zhǔn),它旨在為容器平臺提供網(wǎng)絡(luò)的標(biāo)準(zhǔn)化。不同的容器平臺(比如目前的 kubernetes、mesos 和 rkt)能夠通過相同的接口調(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ò):按隧道類型來說,不同的公司或者組織有不同的實(shí)現(xiàn)方案。docker原生的overlay網(wǎng)絡(luò)就是基于vxlan隧道實(shí)現(xiàn)的。ovn則需要通過geneve或者stt隧道來實(shí)現(xiàn)的。flannel最新版本也開始默認(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ò),并且通過iptables實(shí)現(xiàn)網(wǎng)絡(luò)的安全隔離。典型的方案為Project Calico。同時(shí)對不支持三層路由的環(huán)境,Project Calico還提供了基于IPIP封裝的跨主機(jī)網(wǎng)絡(luò)實(shí)現(xiàn)
通信方式
集群內(nèi)跨節(jié)點(diǎn)通信涉及到不同的子網(wǎng)間通信,僅靠docker0無法實(shí)現(xiàn),這里需要借助CNI網(wǎng)絡(luò)插件來實(shí)現(xiàn)。圖中展示了使用flannel實(shí)現(xiàn)跨節(jié)點(diǎn)通信的方式。
簡單說來,flannel的用戶態(tài)進(jìn)程flanneld會為每個node節(jié)點(diǎn)創(chuàng)建一個flannel.1的網(wǎng)橋,根據(jù)etcd或apiserver的全局統(tǒng)一的集群信息為每個node分配全局唯一的網(wǎng)段,避免地址沖突。同時(shí)會為docker0和flannel.1創(chuàng)建veth對,docker0將報(bào)文丟給flannel.1,。
Flanneld維護(hù)了一份全局node的網(wǎng)絡(luò)表,通過flannel.1接收到請求后,根據(jù)node表,將請求二次封裝為UDP包,扔給eth0,由eth0出口進(jìn)入物理網(wǎng)路發(fā)送給目的node。
在另一端以相反的流程。Flanneld解包并發(fā)往docker0,進(jìn)而發(fā)往目的Pod中的容器。
外部訪問集群
從集群外訪問集群有多種方式,比如loadbalancer,Ingress,nodeport,nodeport和loadbalancer是service的兩個基本類型,是將service直接對外暴露的方式,ingress則是提供了七層負(fù)載均衡,其基本原理將外部流量轉(zhuǎn)發(fā)到內(nèi)部的service,再轉(zhuǎn)發(fā)到后端endpoints,在平時(shí)的使用中,我們可以依據(jù)具體的業(yè)務(wù)需求選用不同的方式。這里主要介紹nodeport和ingress方式。
Nodeport
通過將 Service 的類型設(shè)置為 NodePort,就可以在 Cluster 中的主機(jī)上通過一個指定端口暴露服務(wù)。注意通過 Cluster 中每臺主機(jī)上的該指定端口都可以訪問到該服務(wù),發(fā)送到該主機(jī)端口的請求會被 Kubernetes 路由到提供服務(wù)的 Pod 上。采用這種服務(wù)類型,可以在 Kubernetes cluster 網(wǎng)絡(luò)外通過主機(jī) IP:端口的方式訪問到服務(wù)。
這里給出一個 influxdb 的例子,我們也可以針對這個模板去修改成其他的類型:
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。外部客戶端只看到 http://foo.bar.com 這個服務(wù)器,屏蔽了內(nèi)部多個 Service 的實(shí)現(xiàn)方式。采用這種方式,簡化了客戶端的訪問,并增加了后端實(shí)現(xiàn)和部署的靈活性,可以在不影響客戶端的情況下對后端的服務(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
這里我們定義了一個ingress模板,定義通過 http://test.name.com 來訪問服務(wù),在虛擬主機(jī)http://test.name.com下面定義了兩個Path,其中/test被分發(fā)到后端服務(wù)s1,/name被分發(fā)到后端服務(wù)s2。
集群中可以定義多個ingress,來完成不同服務(wù)的轉(zhuǎn)發(fā),這里需要一個ingress controller來管理集群中的Ingress規(guī)則。Ingress Contronler 通過與 Kubernetes API 交互,動態(tài)的去感知集群中 Ingress 規(guī)則變化,然后讀取它,按照自定義的規(guī)則,規(guī)則就是寫明了哪個域名對應(yīng)哪個service,生成一段 Nginx 配置,再寫到 Nginx-ingress-control的 Pod 里,這個 Ingress Contronler 的 pod 里面運(yùn)行著一個nginx服務(wù),控制器會把生成的nginx配置寫入 /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é)及展望
本文針對 Kubernetes 的網(wǎng)絡(luò)模型,從一個 service,二個IP,三個 port 出發(fā)進(jìn)行圖解。詳解 Kubernetes 集群內(nèi)及集群外部訪問方式。后續(xù)還將針對各網(wǎng)絡(luò)細(xì)節(jié)進(jìn)行深入分析,敬請關(guān)注。