引: 之前寫過一篇文章介紹如何管理linux設(shè)備上的bridge(網(wǎng)橋)和docker bridge, 今天我們來看看k8s的網(wǎng)絡(luò)模型幕屹。
我們先來看圖示例,下面則個是k8s的網(wǎng)絡(luò)模型圖邪铲。
我們知道,在k8s里面最小的管理單元是pod无拗,一個主機(jī)可以跑多個pod带到,一個pod里面可以跑多個容器。
如上面所示英染,一個pod里面所有的容器共享一個網(wǎng)絡(luò)命名空間(network namespace)揽惹,所以被饿,pod里面的容器之間通信,可以直接通過localhost來完成搪搏,pod里面的容器之間通過localhost+端口的方式來通信(這和應(yīng)用程序在宿主機(jī)的通信方式是一樣的)狭握。
那么pod和pod之間的通信呢?通常來說慕嚷,我們給應(yīng)用程序定死端口會給應(yīng)用程序水平擴(kuò)展帶來很多不便哥牍,所以k8s不會使用定死端口這樣的方法,而是采用其他方法來解決pod之間尋址的問題
每個pod都會有一個自己的ip喝检,可以將Pod像VM或物理主機(jī)一樣對待。這樣pod和pod之間的通信就不需要像容器一樣撼泛,通過內(nèi)外端口映射來通信了挠说,這樣就避免了端口沖突的問題。
特殊的情況下(比如運維做網(wǎng)絡(luò)檢測或者程序調(diào)試)愿题,可以在pod所在的宿主機(jī)想向pod的ip+端口發(fā)起請求损俭,這些請求會轉(zhuǎn)發(fā)到pod的端口,但是pod本身它自己是不知道端口的存在的潘酗。
因此杆兵,k8s的網(wǎng)絡(luò)遵循以下原則:
- 一個節(jié)點的pod和其他節(jié)點的pod通信不需要通過做網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT)
- 一個節(jié)點上所有的agent控制程序(如deamon和kubelet)可以和這個節(jié)點上的pod通信
- 節(jié)點主機(jī)網(wǎng)絡(luò)中的Pod可以與其他所有節(jié)點上的所有Pod通信,而無需NAT
把上面這個pod替換成容器也是成立的仔夺,因為pod里面的容器和pod共享網(wǎng)絡(luò)琐脏。
基本上的原則就是,k8s的里面的pod可以自由的和集群里面的任何其他pod通信(即使他們是部署在不同的宿主機(jī))缸兔,而且pod直接的通信是直接使用pod自己的ip來通信日裙,他們不知道宿主機(jī)的ip,所以惰蜜,對于pod之間來說昂拂,宿主機(jī)的網(wǎng)絡(luò)信息是透明的,好像不存在一樣抛猖。
然后格侯,定了這幾個原則之后,具體的實現(xiàn)k8s的這個網(wǎng)絡(luò)模型有好多種實現(xiàn)财著,我們這里介紹的是Flannel联四,是其中最簡單的一種實現(xiàn)。
Flannel實現(xiàn)pod之間的通信瓢宦,是通過一種覆蓋網(wǎng)絡(luò)(overlay network)碎连,把數(shù)據(jù)包封裝在另外一個網(wǎng)絡(luò)來做轉(zhuǎn)發(fā),這個覆蓋網(wǎng)絡(luò)可以給每一個pod分配一個獨立的ip地址驮履,使他們看起來都是一臺具有獨立ip的物理主機(jī)一樣鱼辙。
下面這個就是k8s用覆蓋網(wǎng)絡(luò)來實現(xiàn)的一個例子:
可以看到有3個node廉嚼,在多個node上建立一個覆蓋網(wǎng)絡(luò),子網(wǎng)網(wǎng)段是100.95.0.0/16,然后倒戏,最終到容器級別怠噪,每個容器在這個網(wǎng)段里面獲取到一個獨立的ip。而宿主機(jī)所在的局域網(wǎng)絡(luò)的網(wǎng)段是172.20.32.0/19
看這兩個網(wǎng)段杜跷,就知道傍念,fannel給這個集群創(chuàng)建了一個更大的網(wǎng)絡(luò)給pod使用,可以容納的主機(jī)數(shù)量達(dá)到65535(2^16)個葛闷。
對于每個宿主機(jī)憋槐,fannel給每個了一個小一點的網(wǎng)絡(luò)100.96.x.0/24,提供給每個這個宿主機(jī)的每一個pod使用淑趾,也就是說阳仔,每一個宿主機(jī)可以有256(2^8)個pod。docker默認(rèn)的網(wǎng)橋docker0用的就是這個網(wǎng)絡(luò)扣泊,也就是所有的docker通過docker0來使用這個網(wǎng)絡(luò)近范。即使說,對于容器來說延蟹,都是通過docker0這個橋來通信评矩,和我們平常單機(jī)的容器是一樣的(如果你不給創(chuàng)建的容器指定網(wǎng)絡(luò)的話,默認(rèn)用的是docker0阱飘,參考我以前寫的關(guān)于docker bridge的文章)
那么斥杜,對于同一個host里面的容器通信,我們上面說了是通過這個臺宿主機(jī)的里面的docker0這個網(wǎng)橋來通信俯萌。那對于跨宿主機(jī)果录,也即是兩個宿主機(jī)之間的容器是怎么通信的呢?fannel使用了宿主機(jī)操作系統(tǒng)的kernel route和UDP(這是其中一種實現(xiàn))包封裝來完成咐熙。下圖演示了這個通信過程:
如圖所示弱恒,100.96.1.2(container-1) 要和100.96.2.3(container-2)通信,兩個容器分別處于不同的宿主機(jī)棋恼。
假設(shè)有一個包是從100.96.1.2發(fā)出去給100.96.2.3返弹,它會先經(jīng)過docker0,因為docker0這個橋是所有容器的網(wǎng)關(guān)爪飘。 然后這個包會經(jīng)過route table處理义起,轉(zhuǎn)發(fā)出去到局域網(wǎng)172.20.32.0/19. 而這個route table的對應(yīng)處理這類包的規(guī)則又是從哪里來的呢?它們是由fannel的一個守護(hù)程序flanneld創(chuàng)建的师崎。
每一臺宿主機(jī)都會跑一個flannel的deamon的進(jìn)程默终,這個進(jìn)程的程序會往宿主機(jī)的route table里面寫入特定的路由規(guī)則,這個規(guī)則大概是這樣的。
Node1的route table
admin@ip-172-20-33-102:~$ ip route
default via 172.20.32.1 dev eth0
100.96.0.0/16 dev flannel0 proto kernel scope link src 100.96.1.0
100.96.1.0/24 dev docker0 proto kernel scope link src 100.96.1.1
172.20.32.0/19 dev eth0 proto kernel scope link src 172.20.33.102
圖例的數(shù)據(jù)包發(fā)出去的目標(biāo)地址是100.96.2.3,它屬于網(wǎng)段100.96.0.0/16,這個目標(biāo)地址命中第二條規(guī)則齐蔽,也就是這個包會發(fā)到flannel0這個設(shè)備(dev)两疚,這flannel0
是一個TUN設(shè)備。是在內(nèi)核里面的一個虛擬網(wǎng)絡(luò)設(shè)備(虛擬網(wǎng)卡)含滴。
在內(nèi)核(kernel)里面诱渤,有兩種虛擬網(wǎng)卡設(shè)備,分別是TUN和TAP,其中TAP處理的是第二層(數(shù)據(jù)鏈路層)的幀谈况,而TUN處理的是第三層(網(wǎng)絡(luò)層)的ip包勺美。
應(yīng)用程序可以綁定到TUN和TAP設(shè)備,內(nèi)核會把數(shù)據(jù)通過TUN或者TAP設(shè)備發(fā)送給這些程序碑韵,反過來赡茸,應(yīng)用程序也可以通過TUN和TAP向內(nèi)核寫入數(shù)據(jù),進(jìn)而由內(nèi)核的路由處理這些發(fā)出去的數(shù)據(jù)包泼诱。
那么上面這個flannel0
就是一個這樣的TUN設(shè)備坛掠。這個設(shè)備連到的是一個flannel的守護(hù)進(jìn)程程序flanneld
而這個flanneld
是干嘛的呢?它可以接受所有發(fā)往flannel0
這個設(shè)備的數(shù)據(jù)包治筒,然后做數(shù)據(jù)封裝處理,它的封裝的邏輯也很簡單舷蒲,就是根據(jù)目標(biāo)地址
耸袜,找到這個這地址對應(yīng)的在整個flannel網(wǎng)絡(luò)里面對應(yīng)物理ip和端口
(這里是Node2對應(yīng)的物理ip),然后增加一個包頭牲平,增加的包頭里面目標(biāo)地址
為這個實際的物理ip和端口
(當(dāng)然源地址也改成了局域網(wǎng)絡(luò)的ip)堤框,將原來的數(shù)據(jù)包嵌入在新的數(shù)據(jù)包中,然后再把這個封裝后的包扔回去給內(nèi)核纵柿,內(nèi)核根據(jù)目標(biāo)地址去路由規(guī)則匹配規(guī)則蜈抓,發(fā)現(xiàn)目標(biāo)地址ip是172.20.54.98,端口是8285. 根據(jù)ip匹配不到任何特定的規(guī)則,就用第一條default(默認(rèn))的規(guī)則昂儒,通過eth0這個物理網(wǎng)卡沟使,把數(shù)據(jù)包發(fā)給局域網(wǎng)(這里是UDP廣播出去)
當(dāng)Node2的收到這個包后,然后根據(jù)端口8285
發(fā)現(xiàn)他的目標(biāo)地址原來是發(fā)給flanneld的渊跋,然后就直接交給flanneld這程序腊嗡,flanneld收到包后,把包頭去掉拾酝,發(fā)現(xiàn)原來目標(biāo)地址是100.96.2.3燕少,然后就交換flannel0,flannel0把這個解開后的原包交給內(nèi)核蒿囤,內(nèi)核發(fā)現(xiàn)它的目標(biāo)地址是100.96.2.3客们,應(yīng)該交給docker0來處理。(圖例里面畫的是直接由flannel0交給docker0,沒有圖示出內(nèi)核底挫,實際上flannel0是一個TUN設(shè)備恒傻,是跑在內(nèi)核的,數(shù)據(jù)經(jīng)過它后可以交給內(nèi)核凄敢,由內(nèi)核根據(jù)路由決定進(jìn)一步怎么forward)
以上就是這個通信的過程碌冶,那么這里有一個問題: flanneld是怎么知道100.96.2.3對應(yīng)的目標(biāo)地址是172.20.54.98:8285的呢?
這是因為flanneld維護(hù)了一個映射關(guān)系涝缝,它沒創(chuàng)造一個虛擬的容器ip(分配給容器新ip的時候)扑庞,它就知道這個容器的ip實際上是在哪臺宿主機(jī)上,然后把這個映射關(guān)系存儲起來拒逮,在k8s里面flanneld存儲的這個映射關(guān)系放在etd上罐氨,這就是為什么flanneld為什么知道這個怎么去封裝這些包了,下面就是etcd里面的數(shù)據(jù)的:
admin@ip-172-20-33-102:~$ etcdctl ls /coreos.com/network/subnets
/coreos.com/network/subnets/100.96.1.0-24
/coreos.com/network/subnets/100.96.2.0-24
/coreos.com/network/subnets/100.96.3.0-24
admin@ip-172-20-33-102:~$ etcdctl get /coreos.com/network/subnets/100.96.2.0-24
{"PublicIP":"172.20.54.98"}
看上面這個數(shù)據(jù)滩援,etcd里面存儲的100.96.2.0-24這個網(wǎng)段的容器是放在172.20.54.98這臺宿主機(jī)上的栅隐。
那么還有一個問題,端口8285又是怎么知道的?
這個很簡單玩徊,flanneld的默認(rèn)監(jiān)聽的端口就是這個8285端口租悄,flanneld啟動的時候,就監(jiān)聽了UDP端口8285. 所以發(fā)給Node2:8285的所有UDP數(shù)據(jù)包會恩袱,flanneld這個進(jìn)程會直接處理泣棋,如何去掉包頭就還原出來原來的包了,還原后交給TUN設(shè)備flannel0,由flannel0交給內(nèi)核畔塔,內(nèi)核根據(jù)Node2的路由規(guī)則交給docker0(Node2的路由規(guī)則和node1是基本上一樣的潭辈,除了第三位的網(wǎng)段標(biāo)識不一樣,一個是100.96.1一個是100.92.2):
admin@ip-172-20-54-98:~$ ip route
default via 172.20.32.1 dev eth0
100.96.0.0/16 dev flannel0 proto kernel scope link src 100.96.2.0
100.96.2.0/24 dev docker0 proto kernel scope link src 100.96.2.1
172.20.32.0/19 dev eth0 proto kernel scope link src 172.20.54.98
看Node2的這個規(guī)則澈吨,flannld去掉包頭解出來的原包的目標(biāo)ip是100.96.2.3,由flannel0交回去給kennel把敢,kennel發(fā)現(xiàn)命中第三條規(guī)則,所以會把這個包叫給docker0,繼而就進(jìn)入了docker0這個橋的子網(wǎng)了谅辣,接下去就是docker的事情了修赞,參考以前寫的文章。
最后一個問題屈藐,怎么配置docker去使用100.96.x.0/24這個子網(wǎng)呢榔组,如果是手工創(chuàng)建容器的話,這個也是非常簡單的联逻,參考以前寫的關(guān)于docker bridge的這篇文章搓扯,但是在k8s里面,是通過配置來實現(xiàn)的:
flanneld會把子網(wǎng)信息寫到一個配置文件/run/flannel/subnet.env
里
admin@ip-172-20-33-102:~$ cat /run/flannel/subnet.env
FLANNEL_NETWORK=100.96.0.0/16
FLANNEL_SUBNET=100.96.1.1/24
FLANNEL_MTU=8973
FLANNEL_IPMASQ=true
docker會使用這個配置的環(huán)境變了來作為它的bridge的配置
dockerd --bip=$FLANNEL_SUBNET --mtu=$FLANNEL_MTU
以上包归,就是k8s如何使用flannel網(wǎng)絡(luò)來跨機(jī)器通信的原理锨推,總體來講,由于flanneld這個守護(hù)神干了所有的臟活累活(其實已經(jīng)是k8s的網(wǎng)絡(luò)實現(xiàn)里面最簡單的一種了),使得pod和容器能夠連接另外一個pod或者容器變得非常簡單换可,就像連一個大局域網(wǎng)里面任意以太主機(jī)一樣椎椰,他們只需要知道對方的虛擬ip就可以直接通信了,不需要做
NAT等復(fù)雜的規(guī)則處理沾鳄。
那么性能怎么樣慨飘?
新版本的flannel不推薦在生產(chǎn)環(huán)境使用UDP的包封裝這種實現(xiàn)。只用它來做測試和調(diào)試用译荞,因為它的性能表現(xiàn)和其他的實現(xiàn)比差一些瓤的。
看上面這個圖解,一個upd包需要來回在用戶空間(user space)和內(nèi)核空間(kennel space)復(fù)制3次吞歼,這會大大增加網(wǎng)絡(luò)開銷圈膏。
官方的文檔里面可以看到其他的包轉(zhuǎn)發(fā)實現(xiàn)方式,可以進(jìn)一步閱讀,其中host-gw的性能比較好篙骡,它是在第二層去做數(shù)據(jù)包處理稽坤。