1 概述linux
根據官網的描述,flannel是一個專爲kubernetes定製的三層網絡解決方案。它主要用於解決容器的跨主機通訊問題。首先咱們來簡單看一下,它是如何工做的。git
首先,flannel會利用Kubernetes API或者etcd用於存儲整個集羣的網絡配置,其中最主要的內容爲設置集羣的網絡地址空間,例如,設定整個集羣內全部容器的IP都取自網段「10.1.0.0/16」。接着,flannel會在每一個主機中運行flanneld做爲agent,它會爲所在主機從集羣的網絡地址空間中,獲取一個小的網段subnet,本主機內全部容器的IP地址都將從中分配。而後,flanneld再將本主機獲取的subnet以及用於主機間通訊的Public IP,一樣經過kubernetes API或者etcd存儲起來。最後,flannel利用各類backend mechanism,例如udp,vxlan等等,跨主機轉發容器間的網絡流量,完成容器間的跨主機通訊。下面,咱們以一個具體的例子來描述在flannel中,跨主機的容器間通訊是如何進行的。github
以下圖所示,集羣範圍內的網絡地址空間爲10.1.0.0/16,Machine A獲取的subnet爲10.1.15.0/24,而且其中的兩個容器的IP分別爲10.1.15.2/24和10.1.15.3/24,二者都在10.1.15.0/24這一子網範圍內,對於下方的Machine B同理。正則表達式
如今,咱們來簡單看一下,若是上方Machine A中IP地址爲10.1.15.2/24的容器要與下方Machine B中IP地址爲10.1.16.2/24的容器進行通訊,封包是如何進行轉發的。從上文可知,每一個主機的flanneld會將本身與所獲取subnet的關聯信息存入etcd中,例如,subnet 10.1.15.0/24所在主機可經過IP 192.168.0.100訪問,subnet 10.1.16.0/24可經過IP 192.168.0.200訪問。反之,每臺主機上的flanneld經過監聽etcd,也可以知道其餘的subnet與哪些主機相關聯。以下圖,Machine A上的flanneld經過監聽etcd已經知道subnet 10.1.16.0/24所在的主機能夠經過Public 192.168.0.200訪問,並且熟悉docker橋接模式的同窗確定知道,目的地址爲10.1.16.2/24的封包一旦到達Machine B,就能經過cni0網橋轉發到相應的pod,從而達到跨宿主機通訊的目的。docker
所以,flanneld只要想辦法將封包從Machine A轉發到Machine B就OK了,而上文中的backend就是用於完成這一任務。不過,達到這個目的的方法是多種多樣的,因此咱們也就有了不少種backend。在這裏咱們舉例介紹的是最簡單的一種方式hostgw:由於Machine A和Machine B處於同一個子網內,它們本來就能直接互相訪問。所以最簡單的方法是:在Machine A中的容器要訪問Machine B的容器時,咱們能夠將Machine B當作是網關,當有封包的目的地址在subnet 10.1.16.0/24範圍內時,就將其直接轉發至B便可。而這經過下圖中那條紅色標記的路由就能完成,對於Machine B同理可得。由此,在知足仍有subnet能夠分配的條件下,咱們能夠將上述方法擴展到任意數目位於同一子網內的主機。而任意主機若是想要訪問主機X中subnet爲S的容器,只要在本主機上添加一條目的地址爲R,網關爲X的路由便可。json
下面,我將以問題驅動的方式,來詳細分析flannel是如何運做的後端
2 節點初始化數組
首先,咱們最感興趣的是,當一個新的節點加入集羣時,它是如何初始化的。對此,咱們可能會有如下幾個疑問:bash
1) 若主機有多張網卡和多個IP,如何選擇其中的一張網卡和一個IP用於集羣主機間的通訊網絡
2) 主機如何獲取屬於本身的subnet並維護
3) 咱們如何在集羣中有新的節點加入時,獲取對應的subnet和Public IP,並經過配置backend進行訪問
2.1 網卡及對外IP選擇
對於第一個問題,事實上咱們能夠在flanneld的啓動參數中經過"--iface"或者"--iface-regex"進行指定。其中"--iface"的內容能夠是完整的網卡名或IP地址,而"--iface-regex"則是用正則表達式表示的網卡名或IP地址,而且兩個參數都能指定多個實例。flannel將以以下的優先級順序來選取:
1) 若是"--iface"和"----iface-regex"都未指定時,則直接選取默認路由所使用的輸出網卡
2) 若是"--iface"參數不爲空,則依次遍歷其中的各個實例,直到找到和該網卡名或IP匹配的實例爲止
3) 若是"--iface-regex"參數不爲空,操做方式和2)相同,惟一不一樣的是使用正則表達式去匹配
最後,對於集羣間交互的Public IP,咱們一樣能夠經過啓動參數"--public-ip"進行指定。不然,將使用上文中獲取的網卡的IP做爲Public IP。
2.2 獲取subnet
在獲取subnet以前,咱們首先要建立一個SubnetManager,它在具體的代碼實現中,表現爲一個接口,以下所示:
type Manager interface { GetNetworkConfig(ctx context.Context) (*Config, error) AcquireLease(ctx context.Context, attrs *LeaseAttrs) (*Lease, error) RenewLease(ctx context.Context, lease *Lease) error WatchLease(ctx context.Context, sn ip.IP4Net, cursor interface{}) (LeaseWatchResult, error) WatchLeases(ctx context.Context, cursor interface{}) (LeaseWatchResult, error) Name() string }
從接口中各個函數的名字,咱們大概就能猜出SubnetManager的做用是什麼了。可是,爲何獲取subnet的函數叫AcquireLease,而不叫AcquireSubnet呢?實際上,每臺主機都是租借了一個subnet,若是到了必定時間不進行更新,那麼該subnet就會過時從而從新分配給其餘的主機,即主機和subnet的關聯信息會從etcd中消失(在本文中咱們將默認選擇etcd做爲SubnetManager的後端存儲)。所以,lease就是一條subnet和所屬主機的關聯信息,而且具備時效性,須要按期更新。下面咱們來看看,每臺主機都是如何獲取lease的:
1) 首先,咱們調用GetNetworkConfig(),它會訪問etcd獲取集羣網絡配置並封裝在結構Config中返回,Config結構以下所示。其中的Network字段對應的集羣網絡地址空間是在flannel啓動前,必須寫入etcd中的,例如"10.1.0.0/16"。
type Config struct { Network ip.IP4Net SubnetMin ip.IP4 SubnetMax ip.IP4 SubnetLen uint BackendType string `json:"-"` Backend json.RawMessage `json:",omitempty"` }
對於其餘字段的含義及默認值以下:
(1) SubnetLen表示每一個主機分配的subnet大小,咱們能夠在初始化時對其指定,不然使用默認配置。在默認配置的狀況下,若是集羣的網絡地址空間大於/24,則SubnetLen配置爲24,不然它比集羣網絡地址空間小1,例如集羣的大小爲/25,則SubnetLen的大小爲/26
(2) SubnetMin是集羣網絡地址空間中最小的可分配的subnet,能夠手動指定,不然默認配置爲集羣網絡地址空間中第一個可分配的subnet。例如對於"10.1.0.0/16",當SubnetLen爲24時,第一個可分配的subnet爲"10.1.1.0/24"。
(3) SubnetMax表示最大可分配的subnet,對於"10.1.0.0/16",當subnetLen爲24時,SubnetMax爲"10.1.255.0/24"
(4) BackendType爲使用的backend的類型,如未指定,則默認爲「udp」
(5) Backend中會包含backend的附加信息,例如backend爲vxlan時,其中會存儲vtep設備的mac地址
2) 在獲取了集羣的網絡配置以後,接下來咱們就調用SubnetManager中的AcquireLease()獲取本主機的subnet。其中的參數類型LeaseAttrs以下所示:
type LeaseAttrs struct { PublicIP ip.IP4 BackendType string `json:",omitempty"` BackendData json.RawMessage `json:",omitempty"` }
顯然,其中最重要的字段就是PublicIP,它實質上是標識了一臺主機。在獲取subnet以前,咱們先要從etcd中獲取當前全部已經存在的lease信息----leases,以備後用。下面咱們將對不一樣狀況下lease的獲取進行討論:
(1) 事實上,這可能並非咱們第一次在這臺機器上啓動flannel,所以,頗有可能,在此以前,這臺機器已經獲取了lease。已知一臺主機實際上是由它的Public IP標識的,因此咱們能夠用Public IP做爲關鍵字匹配leases中全部lease的Public IP。若匹配成功,則檢查相應的lease是否和當前的集羣網絡配置兼容:檢查的內容包括IP是否落在SubnetMin和SubnetMax內,以及subnet大小是否和SubnetLen相等。若兼容,則用新的LeaseAttrs和ttl更新該lease,表示成功獲取本機的lease,不然只能將該lease刪除。
(2) 當初始化SubnetManager時,會先試圖解析以前flannel獲取了lease後留下的配置文件(該文件的建立,會在下文描述),從中讀取出以前獲取的subnet。若是讀取到的subnet不爲空,則一樣利用該subnet去匹配leases中全部lease的subnet。若匹配成功,則一樣檢查lease是否和當前的集羣網絡配置兼容。若兼容則更新lease,表示成功獲取本機的lease,不然將其刪除。若是該subnet並不能從leases中找到,可是它和當前的集羣網絡配置兼容的話,能夠直接將它和LeaseAttrs封裝爲lease,寫入etcd。
(3) 若此時還未獲取到lease,那麼咱們有必要本身建立一個新的了。建立的方法很簡單,從SubnetMin遍歷到SubnetMax,將其中和leases中已有的subnet都不重合者加入一個集合中。再從該集合隨機選擇一個,做爲本主機的subnet便可。最後,將subnet和LeaseAttrs封裝爲一個lease寫入etcd。由此,該主機獲取了本身的subnet。
最後,咱們將有關的集羣網絡和subnet的配置信息寫入文件/run/flannel/subnet.env(可經過命令行參數"--subnet-file"手動指定)中,寫入的信息以下所示,包括:集羣網絡地址空間FLANNEL_NETWORK,獲取的子網信息FLANNEL_SUBNET等等
cat /var/run/flannel/subnet.env FLANNEL_NETWORK=10.1.0.0/16 FLANNEL_SUBNET=10.1.16.1/24 FLANNEL_MTU=1450 FLANNEL_IPMASQ=false
2.3 維護subnet
當SubnetManager的後端存儲使用的是etcd時,各個主機還須要對本身的lease進行維護,在租期即將到期時,須要對etcd中的lease進行更新,調用SubnetManager中的RenewLease()方法,防止它到期後被自動刪除。另外,咱們能夠在flanneld的命令行啓動參數中用"--subnet-lease-renew-margin"指定在租期到期前多久進行更新。默認值爲1小時,即每23小時更新一次lease,從新獲取一次24小時的租期。
2.4 發現新節點
如今,初始化已經完成了,咱們須要面對以下兩個問題:
一、當本主機的flanneld啓動時,若是集羣中已經存在了其餘主機,咱們如何經過backend進行配置,使得封包可以到達它們
二、若是以後集羣中又添加了新的主機,咱們如何獲取這一事件,並經過backend對配置進行調整,對於刪除主機這類事件同理
固然上述兩個問題,都是經過etcd解決的。backend會一邊經過上文中的WatchLeases()方法對etcd進行監聽,從中獲取各種事件,另外一邊會啓動一個事件處理引擎,不斷地對監聽到的事件進行處理。對於問題1,咱們首先要從etcd中獲取當前全部的lease信息,並將其轉化爲一系列的event,將它交於事件處理引擎進行處理,從而讓封包可以到達這些主機。對於問題2,直接對etcd中的事件進行監聽,將獲取的事件轉換爲事件處理引擎可以處理的形式,並進行處理便可。事件的類型也很簡單,總共就只有EventAdded和EventRemoved兩種,分別表示新增了lease以及一個lease過時。由於不一樣backend的配置方式是徹底不一樣的,下面咱們就將對各類backend的基本原理進行解析,並說明它們如何處理EventAdded和EventRemoved這兩類事件。
3 backend原理解析
在本節中,我將對hostgw,udp和vxlan三種backend進行解析
3.1 hostgw
hostgw是最簡單的backend,它的原理很是簡單,直接添加路由,將目的主機當作網關,直接路由原始封包。例如,咱們從etcd中監聽到一個EventAdded事件:subnet爲10.1.15.0/24被分配給主機Public IP 192.168.0.100,hostgw要作的工做很是簡單,在本主機上添加一條目的地址爲10.1.15.0/24,網關地址爲192.168.0.100,輸出設備爲上文中選擇的集羣間交互的網卡便可。對於EventRemoved事件,刪除對應的路由便可。
3.2 udp
咱們知道當backend爲hostgw時,主機之間傳輸的就是原始的容器網絡封包,封包中的源IP地址和目的IP地址都爲容器全部。這種方法有必定的限制,就是要求全部的主機都在一個子網內,即二層可達,不然就沒法將目的主機當作網關,直接路由。
而udp類型backend的基本思想是:既然主機之間是能夠相互通訊的(並不要求主機在一個子網中),那麼咱們爲何不能將容器的網絡封包做爲負載數據在集羣的主機之間進行傳輸呢?這就是所謂的overlay。具體過程以下所示:
當容器10.1.15.2/24要和容器10.1.20.2/24通訊時,由於該封包的目的地不在本主機是subnet內,所以封包會首先經過網橋轉發到主機中。最終在主機上通過路由匹配,進入網卡flannel0。須要注意的是flannel0是一個tun設備,它是一種工做在三層的虛擬網絡設備,而flanneld是一個proxy,它會監聽flannel0並轉發流量。當封包進入flannel0時,flanneld就能夠從flannel0中將封包讀出,因爲flannel0是三層設備,因此讀出的封包僅僅包含IP層的報頭及其負載。最後flanneld會將獲取的封包做爲負載數據,經過udp socket發往目的主機。同時,在目的主機的flanneld會監聽Public IP所在的設備,從中讀取udp封包的負載,並將其放入flannel0設備內。由此,容器網絡封包到達目的主機,以後就能夠經過網橋轉發到目的容器了。
最後和hostgw不一樣的是,udp backend並不會將從etcd中監聽到的事件中包含的lease信息做爲路由寫入主機中。每當收到一個EventAdded事件,flanneld都會將其中的subnet和Public IP保存在一個數組中,用於轉發封包時進行查詢,找到目的主機的Public IP做爲udp封包的目的地址。
3.3 vxlan
首先,咱們對vxlan的基本原理進行簡單的敘述。從下圖所示的封包結構來看,vxlan和上文提到的udp backend的封包結構是很是相似的,不一樣之處是多了一個vxlan header,以及原始報文中多了個二層的報頭。
下面讓咱們來看看,當有一個EventAdded到來時,flanneld如何進行配置的,以及封包是如何在flannel網絡中流動的。
如上圖所示,當主機B加入flannel網絡時,和其餘全部backend同樣,它會將本身的subnet 10.1.16.0/24和Public IP 192.168.0.101寫入etcd中,和其餘backend不同的是,它還會將vtep設備flannel.1的mac地址也寫入etcd中。
以後,主機A會獲得EventAdded事件,並從中獲取上文中B添加至etcd的各類信息。這個時候,它會在本機上添加三條信息:
1) 路由信息:全部通往目的地址10.1.16.0/24的封包都經過vtep設備flannel.1設備發出,發往的網關地址爲10.1.16.0,即主機B中的flannel.1設備。
2) fdb信息:MAC地址爲MAC B的封包,都將經過vxlan首先發往目的地址192.168.0.101,即主機B
3) arp信息:網關地址10.1.16.0的地址爲MAC B
如今有一個容器網絡封包要從A發往容器B,和其餘backend中的場景同樣,封包首先經過網橋轉發到主機A中。此時經過,查找路由表,該封包應當經過設備flannel.1發往網關10.1.16.0。經過進一步查找arp表,咱們知道目的地址10.1.16.0的mac地址爲MAC B。到如今爲止,vxlan負載部分的數據已經封裝完成。因爲flannel.1是vtep設備,會對經過它發出的數據進行vxlan封裝(這一步是由內核完成的,至關於udp backend中的proxy),那麼該vxlan封包外層的目的地址IP地址該如何獲取呢?事實上,對於目的mac地址爲MAC B的封包,經過查詢fdb,咱們就能知道目的主機的IP地址爲192.168.0.101。
最後,封包到達主機B的eth0,經過內核的vxlan模塊解包,容器數據封包將到達vxlan設備flannel.1,封包的目的以太網地址和flannel.1的以太網地址相等,三層封包最終將進入主機B並經過路由轉發達到目的容器。
事實上,flannel只使用了vxlan的部分功能,因爲VNI被固定爲1,本質上工做方式和udp backend是相似的,區別無非是將udp的proxy換成了內核中的vxlan處理模塊。而原始負載由三層擴展到了二層,可是這對三層網絡方案flannel是沒有意義的,這麼也作僅僅只是爲了適配vxlan的模型。vxlan詳細的原理參見文後的參考文獻,其中的分析更爲具體,也更易理解。
4 總結
總的來講,flannel更像是經典的橋接模式的擴展。咱們知道,在橋接模式中,每臺主機的容器都將使用一個默認的網段,容器與容器之間,主機與容器之間都能互相通訊。要是,咱們能手動配置每臺主機的網段,使它們互不衝突。接着再想點辦法,將目的地址爲非本機容器的流量送到相應主機:若是集羣的主機都在一個子網內,就搞一條路由轉發過去;如果不在一個子網內,就搞一條隧道轉發過去。這樣以來,容器的跨網絡通訊問題不就解決了麼?而flannel作的,其實就是將這些工做自動化了而已。
參考文獻
一、flannel源碼:https://github.com/coreos/flannel
二、《vxlan協議原理解析》:http://cizixs.com/2017/09/25/vxlan-protocol-introduction
三、《linux上實現vxlan網絡》:http://cizixs.com/2017/09/28/linux-vxlan
四、《VxLAN和VTEP》:http://maoxiaomeng.com/2017/07/31/vxlan%E5%92%8Cvtep/