我們先來看圖示例,下面則個是k8s的網路模型圖,
k8s的網路模型
我們知道,在k8s里面最小的管理單元是pod,一個主機可以跑多個pod,一個pod里面可以跑多個容器,
如上面所示,一個pod里面所有的容器共享一個網路命名空間(network namespace),所以,pod里面的容器之間通信,可以直接通過localhost來完成,pod里面的容器之間通過localhost+埠的方式來通信(這和應用程式在宿主機的通信方式是一樣的),
那么pod和pod之間的通信呢?通常來說,我們給應用程式定死埠會給應用程式水平擴展帶來很多不便,所以k8s不會使用定死埠這樣的方法,而是采用其他方法來解決pod之間尋址的問題
每個pod都會有一個自己的ip,可以將Pod像VM或物理主機一樣對待,這樣pod和pod之間的通信就不需要像容器一樣,通過內外埠映射來通信了,這樣就避免了埠沖突的問題,
特殊的情況下(比如運維做網路檢測或者程式除錯),可以在pod所在的宿主機想向pod的ip+埠發起請求,這些請求會轉發到pod的埠,但是pod本身它自己是不知道埠的存在的,
因此,k8s的網路遵循以下原則:
- 一個節點的pod和其他節點的pod通信不需要通過做網路地址轉換(NAT)
- 一個節點上所有的agent控制程式(如deamon和kubelet)可以和這個節點上的pod通信
- 節點主機網路中的Pod可以與其他所有節點上的所有Pod通信,而無需NAT
把上面這個pod替換成容器也是成立的,因為pod里面的容器和pod共享網路,
基本上的原則就是,k8s的里面的pod可以自由的和集群里面的任何其他pod通信(即使他們是部署在不同的宿主機),而且pod直接的通信是直接使用pod自己的ip來通信,他們不知道宿主機的ip,所以,對于pod之間來說,宿主機的網路資訊是透明的,好像不存在一樣,
然后,定了這幾個原則之后,具體的實作k8s的這個網路模型有好多種實作,我們這里介紹的是Flannel,是其中最簡單的一種實作,
Flannel實作pod之間的通信,是通過一種覆寫網路(overlay network),把資料包封裝在另外一個網路來做轉發,這個覆寫網路可以給每一個pod分配一個獨立的ip地址,使他們看起來都是一臺具有獨立ip的物理主機一樣,
下面這個就是k8s用覆寫網路來實作的一個例子:
flannel覆寫網路
可以看到有3個node,在多個node上建立一個覆寫網路,子網網段是100.95.0.0/16,然后,最終到容器級別,每個容器在這個網段里面獲取到一個獨立的ip,而宿主機所在的局域網路的網段是172.20.32.0/19
看這兩個網段,就知道,fannel給這個集群創建了一個更大的網路給pod使用,可以容納的主機數量達到65535(2^16)個,
對于每個宿主機,fannel給每個了一個小一點的網路100.96.x.0/24,提供給每個這個宿主機的每一個pod使用,也就是說,每一個宿主機可以有256(2^8)個pod,docker默認的網橋docker0用的就是這個網路,也就是所有的docker通過docker0來使用這個網路,即是說,對于容器來說,都是通過docker0這個橋來通信,和我們平常單機的容器是一樣的(如果你不給創建的容器指定網路的話,默認用的是docker0,參考這篇docker bridge的文章)
那么,對于同一個host里面的容器通信,我們上面說了是通過這個臺宿主機的里面的docker0這個網橋來通信,那對于跨宿主機,也即是兩個宿主機之間的容器是怎么通信的呢?fannel使用了宿主機作業系統的kernel route和UDP(這是其中一種實作)包封裝來完成,下圖演示了這個通信程序:
fannel網路中跨宿主機的容器通信
如圖所示,100.96.1.2(container-1) 要和100.96.2.3(container-2)通信,兩個容器分別處于不同的宿主機,
假設有一個包是從100.96.1.2發出去給100.96.2.3,它會先經過docker0,因為docker0這個橋是所有容器的網關, 然后這個包會經過route table處理,轉發出去到局域網172.20.32.0/19. 而這個route table的對應處理這類包的規則又是從哪里來的呢?它們是由fannel的一個守護程式flanneld創建的,
每一臺宿主機都會跑一個flannel的deamon的行程,這個行程的程式會往宿主機的route table里面寫入特定的路由規則,這個規則大概是這樣的,
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
圖例的資料包發出去的目標地址是100.96.2.3,它屬于網段100.96.0.0/16,這個目標地址命中第二條規則,也就是這個包會發到flannel0這個設備(dev),這flannel0是一個TUN設備,是在內核里面的一個虛擬網路設備(虛擬網卡),
在內核(kernel)里面,有兩種虛擬網卡設備,分別是TUN和TAP,其中TAP處理的是第二層(資料鏈路層)的幀,而TUN處理的是第三層(網路層)的ip包,
應用程式可以系結到TUN和TAP設備,內核會把資料通過TUN或者TAP設備發送給這些程式,反過來,應用程式也可以通過TUN和TAP向內核寫入資料,進而由內核的路由處理這些發出去的資料包,
那么上面這個flannel0就是一個這樣的TUN設備,這個設備連到的是一個flannel的守護行程程式flanneld
而這個flanneld是干嘛的呢?它可以接受所有發往flannel0這個設備的資料包,然后做資料封裝處理,它的封裝的邏輯也很簡單,就是根據目標地址,找到這個這地址對應的在整個flannel網路里面對應物理ip和埠(這里是Node2對應的物理ip),然后增加一個包頭,增加的包頭里面目標地址為這個實際的物理ip和埠(當然源地址也改成了局域網路的ip),將原來的資料包嵌入在新的資料包中,然后再把這個封裝后的包扔回去給內核,內核根據目標地址去路由規則匹配規則,發現目標地址ip是172.20.54.98,埠是8285. 根據ip匹配不到任何特定的規則,就用第一條default(默認)的規則,通過eth0這個物理網卡,把資料包發給局域網(這里是UDP廣播出去)
當Node2的收到這個包后,然后根據埠8285發現他的目標地址原來是發給flanneld的,然后就直接交給flanneld這程式,flanneld收到包后,把包頭去掉,發現原來目標地址是100.96.2.3,然后就交換flannel0,flannel0把這個解開后的原包交給內核,內核發現它的目標地址是100.96.2.3,應該交給docker0來處理,(圖例里面畫的是直接由flannel0交給docker0,沒有圖示出內核,實際上flannel0是一個TUN設備,是跑在內核的,資料經過它后可以交給內核,由內核根據路由決定進一步怎么forward)
以上就是這個通信的程序,那么這里有一個問題: flanneld是怎么知道100.96.2.3對應的目標地址是172.20.54.98:8285的呢?
這是因為flanneld維護了一個映射關系,每創造一個虛擬的容器ip(分配給容器新ip的時候),它就知道這個容器的ip實際上是在哪臺宿主機上,然后把這個映射關系存盤起來,在k8s里面flanneld存盤的這個映射關系放在etd上,這就是為什么flanneld為什么知道這個怎么去封裝這些包了,下面就是etcd里面的資料的:
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"}
看上面這個資料,etcd里面存盤的100.96.2.0-24這個網段的容器是放在172.20.54.98這臺宿主機上的,
那么還有一個問題,埠8285又是怎么知道的?
這個很簡單,flanneld的默認監聽的埠就是這個8285埠,flanneld啟動的時候,就監聽了UDP埠8285. 所以發給Node2:8285的所有UDP資料包,flanneld這個行程會直接處理,然后去掉包頭就還原出來原來的包了,還原后交給TUN設備flannel0,由flannel0交給內核,內核根據Node2的路由規則交給docker0(Node2的路由規則和node1是基本上一樣的,除了第三位的網段標識不一樣,一個是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的這個規則,flannld去掉包頭解出來的原包的目標ip是100.96.2.3,由flannel0交回去給kennel,kennel發現命中第三條規則,所以會把這個包叫給docker0,繼而就進入了docker0這個橋的子網了,接下去就是docker的事情了,參考以前寫的文章,
最后一個問題,怎么配置docker去使用100.96.x.0/24這個子網呢,如果是手工創建容器的話,這個也是非常簡單的,參考以前寫的關于docker bridge的這篇文章,但是在k8s里面,是通過配置來實作的:
flanneld會把子網資訊寫到一個組態檔/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會使用這個配置的環境變了來作為它的bridge的配置
dockerd --bip=$FLANNEL_SUBNET --mtu=$FLANNEL_MTU
以上,就是k8s如何使用flannel網路來跨機器通信的原理,總體來講,由于flanneld這個守護神干了所有的臟活累活(其實已經是k8s的網路實作里面最簡單的一種了),使得pod和容器能夠連接另外一個pod或者容器變得非常簡單,就像連一個大局域網里面任意以太主機一樣,他們只需要知道對方的虛擬ip就可以直接通信了,不需要自己去做NAT等復雜的規則處理,
那么性能怎么樣?
新版本的flannel不推薦在生產環境使用UDP的包封裝這種實作,只用它來做測驗和除錯用,因為它的性能表現和其他的實作比差一些,

flannel0 利用的TUN設備做包封裝原理
看上面這個圖解,一個upd包需要來回在用戶空間(user space)和內核空間(kennel space)復制3次,這會大大增加網路開銷,
官方的檔案里面可以看到其他的包轉發實作方式,可以進一步閱讀,其中host-gw的性能比較好,它是在第二層去做資料包處理,
本文作者:Noosphere
原文發表于我的博客和公眾號
知乎: https://zhuanlan.zhihu.com/p/140711132
個人博客:“Noosphere博客”,“智圈云公眾號”,了解相關資訊可以關注“智圈云”
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/182474.html
標籤:其他
上一篇:計算機網路-網路層
