k8s單master節點部署【二】(二進制部署)--fannel集羣部署(理論)

k8s單master節點部署【二】(二進制部署)–fannel集羣部署(理論)

前言

  • 根據官網的描述,flannel是一個專爲kubernetes定製的三層網絡解決方案,主要用於解決容器的跨主機通信問題。

1. 概況

  • 首先,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等等,跨主機轉發容器間的網絡流量,完成容器間的跨主機通信。

1.1 容器之間的跨主機通信

在這裏插入圖片描述

  • 集羣範圍內的網絡地址空間爲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,從而達到跨宿主機通信的目的。
  • 因此,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的路由即可。

2. 節點初始化

  • 首先,我們最感興趣的是,當一個新的節點加入集羣時,它是如何初始化的。對此,我們可能會有以下幾個疑問:

  • 若主機有多張網卡和多個IP,如何選擇其中的一張網卡和一個IP用於集羣主機間的通信?主機如何獲取屬於自己的subnet並維護?

  • 我們如何在集羣中有新的節點加入時,獲取對應的subnet和Public IP,並通過配置backend進行訪問?

2.1 網卡及對外ip選擇

  • 對於第一個問題,事實上我們可以在flanneld的啓動參數中通過”–iface”或者”–iface-regex”進行指定。其中”–iface”的內容可以是完整的網卡名或IP地址,而”–iface-regex”則是用正則表達式表示的網卡名或IP地址,並且兩個參數都能指定多個實例。flannel將以如下的優先級順序來選取:
      1. 如果”–iface”和”—-iface-regex”都未指定時,則直接選取默認路由所使用的輸出網卡
      1. 如果”–iface”參數不爲空,則依次遍歷其中的各個實例,直到找到和該網卡名或IP匹配的實例爲止
      1. 如果”–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	unit
	BackendType	string		`json:"-"`
	Backend		json.RawMessage		`json:",omitempty"`
}
  • 對於其他字段的含義及默認值如下:

    • ①SubnetLen表示每個主機分配的subnet大小,我們可以在初始化時對其指定,否則使用默認配置。在默認配置的情況下,如果集羣的網絡地址空間大於/24,則SubnetLen配置爲24,否則它比集羣網絡地址空間小1,例如集羣的大小爲/25,則SubnetLen的大小爲/26
    • ②SubnetMin是集羣網絡地址空間中最小的可分配的subnet,可以手動指定,否則默認配置爲集羣網絡地址空間中第一個可分配的subnet。例如對於”10.1.0.0/16″,當SubnetLen爲24時,第一個可分配的subnet爲”10.1.1.0/24″。
    • ③ SubnetMax表示最大可分配的subnet,對於”10.1.0.0/16″,當subnetLen爲24時,SubnetMax爲”10.1.255.0/24″
    • ④BackendType爲使用的backend的類型,如未指定,則默認爲“udp”
    • ⑤ Backend中會包含backend的附加信息,例如backend爲vxlan時,其中會存儲vtep設備的mac地址
    1. 在獲取了集羣的網絡配置之後,接下來我們就調用SubnetManager中的AcquireLease()獲取本主機的subnet。其中的參數類型LeaseAttrs如下所示:
type LeaseAttrs struct {
	PublicIP 	ip.IP4
	BackendType string			`json:",omitempty"`
	BackendData	json.RawMessage 	`json:",omitempty"`
}
  • 顯然,其中最重要的字段就是Public IP,它實質上是標識了一臺主機。在獲取subnet之前,我們先要從etcd中獲取當前所有已經存在的lease信息-leases,以備後用。
    • ① 事實上,這可能並不是我們第一次在這臺機器上啓動flannel,因此,很有可能,在此之前,這臺機器已經獲取了lease。已知一臺主機其實是由它的Public IP標識的,所以我們可以用Public IP作爲關鍵字匹配leases中所有lease的Public IP。若匹配成功,則檢查相應的lease是否和當前的集羣網絡配置兼容:檢查的內容包括IP是否落在SubnetMin和SubnetMax內,以及subnet大小是否和SubnetLen相等。若兼容,則用新的LeaseAttrs和ttl更新該lease,表示成功獲取本機的lease,否則只能將該lease刪除。
    • ② 當初始化SubnetManager時,會先試圖解析之前flannel獲取了lease後留下的配置文件(該文件的創建,會在下文描述),從中讀取出之前獲取的subnet。如果讀取到的subnet不爲空,則同樣利用該subnet去匹配leases中所有lease的subnet。若匹配成功,則同樣檢查lease是否和當前的集羣網絡配置兼容。若兼容則更新lease,表示成功獲取本機的lease,否則將其刪除。如果該subnet並不能從leases中找到,但是它和當前的集羣網絡配置兼容的話,可以直接將它和LeaseAttrs封裝爲lease,寫入etcd。
    • ③ 若此時還未獲取到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 發現新節點

  • 現在,初始化已經完成了,我們需要面對如下兩個問題:
  • 1、當本主機的flanneld啓動時,如果集羣中已經存在了其他主機,我們如何通過backend進行配置,使得封包能夠到達它們
  • 2、如果之後集羣中又添加了新的主機,我們如何獲取這一事件,並通過backend對配置進行調整,對於刪除主機這類事件同理
  • 當然上述兩個問題,都是通過etcd解決的。backend會一邊通過上文中的WatchLeases()方法對etcd進行監聽,從中獲取各類事件,另一邊會啓動一個事件處理引擎,不斷地對監聽到的事件進行處理。
  • 對於問題1,我們首先要從etcd中獲取當前所有的lease信息,並將其轉化爲一系列的event,將它交於事件處理引擎進行處理,從而讓封包能夠到達這些主機。
  • 對於問題2,直接對etcd中的事件進行監聽,將獲取的事件轉換爲事件處理引擎能夠處理的形式,並進行處理即可。
  • 事件的類型也很簡單,總共就只有EventAdded和EventRemoved兩種,分別表示新增了lease以及一個lease過期。因爲不同backend的配置方式是完全不同的,下面我們就對各種backend的基本原理進行解析,並說明它們如何處理EventAdded和EventRemoved這兩類事件。

3. 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設備。
      1. 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做的,其實就是將這些工作自動化了而已。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章