kubernetes_network

Kubernetes Network


前言

​ 本篇文檔主要介紹kubernetes網絡模型實現原理。

​ 首先從docker網絡模型入手,介紹同宿主機不同容器如何互通(網橋),接着分析不同宿主機的容器是如何通信的(跨主通信)。

​ 然後在有了跨主通信的基礎上,轉向kubernetes集羣如何管理宿主機裏面容器的網絡(CNI插件)。

​ 總體的實現思路都是圍繞着網橋的概念延伸的,在不同宿主機之間搭一個橋(搭什麼樣的橋?),然後讓容器都上這座橋(怎麼讓容器上橋?),最後容器的溝通都在這橋上進行(容器怎麼溝通?)。當然還要考慮,我要溝通的容器在不在這個橋上(怎麼知道橋上有誰?)。

大綱

容器網絡

​ 容器網絡,可以直接聲明使用宿主機的網絡棧(-net=host),即,不開啓Network Namespace:

docker run -d net=host --name nginx-host nginx

​ 這個容器啓動後,直接監聽宿主機的80端口。像這種直接使用宿主機網絡棧的方式,雖然可以爲容器提供良好的網絡性能,但也會不可避免地引入共享網絡資源的問題,比如端口衝突。所以在大多數情況下,希望容器進程能使用自己的Network Namespace裏的網絡棧,就是擁有自己的IP地址和端口。

問題1:那如何把容器網絡與宿主機網絡互通,或者如何讓容器網絡與外界網絡通信?

網橋

​ 在使用Network Namespace之後,容器自己相當於是一臺主機,有一套獨立的“網絡棧”。那問題就在於這個容器如何與其他Network Namespace溝通。我們都知道主機與主機之間通過網線,連到一臺交換機上,然後進行通信。那在Linux中,能夠起到虛擬交換機作用的網絡設備,就是網橋(Bridge)。一個工作在數據鏈路層(Data Link)的設備,主要功能是根據MAC地質學習來將數據包轉發到網橋的不同端口上。

​ 所以,容器網絡實現的第一步,就是Docker項目會默認在宿主機上創建一個名叫docker0的網橋,凡是連到docker0網橋上的容器,就可以通過它來通信。啓動容器後,通過指令ifconfig可以看到docker0這個虛擬網絡設備:

在這裏插入圖片描述

Veth Pair虛擬設備

​ 有了網橋,那下個問題就是,如何把容器“連接”到docker0網橋上?答案就是Veth Pair虛擬設備。

​ Veth Pair虛擬設備,它被創建出來後,總是以兩張虛擬網卡(Veth Peer)的形式成對出現。並且,從其中一個“網卡”發出的數據包,可以直接出現在與它對應的另一張“網卡”上,哪怕這兩個“網卡”在不同的Network Namespace裏。

​ 通過啓動一個ubuntu容器來看下Veth Pait設備如何在容器與宿主之間體現的。

docker run -d --name redis-1 -p 6379:6379 redis:latest 

​ 進入容器,在容器內執行ifconfig指令

#如果command not found執行系列指令
$ apt-get update
$ apt install net-tools

在這裏插入圖片描述

​ 從上圖可以看到,有張網卡叫eth0,這就是Veth Pair虛擬設備的一端。在通過指令route查看容器的路由表,可以看到eth0作爲容器裏的默認路由設備,對172.17.0.0/16網段的請求,也會通過eth0來處理。

在這裏插入圖片描述

​ 按照前面說的,Veth Pair設備有兩端,一端已經在容器內了,另一端就是在宿主機上。在宿主機上,通過指令ifconfig查看。會發現,多了一個veth*******的虛擬網卡設備。

在這裏插入圖片描述

​ 也可以通過指令brctl show查看有哪些虛擬網卡設備連到docker0網橋上了,因爲現在我虛擬機上只有一個容器,所以只會出現一個設備。之後,在這個宿主機上啓動其他的容器,都會添加到這個docker0上面。

#如果指令command not found,執行下列指令
$ apt-get update
$ apt install bridge-utils

在這裏插入圖片描述

​ 當然,通常一臺宿主機上上會有很多容器,容器會有很多veth開頭的虛擬網絡設備,可以通過下面方法找到你容器對應的veth虛擬網卡設備:

​ 首先在容器內打印car /sys/class/net/neth0/iflink這個序號,然後根據這個序號在宿主機上根據指令ip link上的序號,就可以找到你容器對應的虛擬網卡。

在這裏插入圖片描述
在這裏插入圖片描述

例子:2容器互ping

​ 在宿主機上再添加一個redis-2容器,然後redis-1 ping redis-2是默認可以互ping的。

​ 宿主機結構如下:

在這裏插入圖片描述

​ 這個⽬的IP地址會匹配到redis-1容器⾥的第⼆條路由規則。可以看到,這條路由規則的⽹關(Gateway)是0.0.0.0,這就意味着這是⼀條直連規則,即:凡是匹配到這條規則的IP包,應該經過本機的eth0⽹卡,通過⼆層⽹絡直接發往⽬的主機。

​ 而通過二層網絡到達redis-2容器,就需要有172.17.0.2這個IP地址對應的MAC地址。所以redis-1容器的⽹絡協議棧,就需要通過eth0⽹卡發送⼀個ARP⼴播,來通過IP地址查找對應的MAC地址。

​ 所以,在收到這些ARP請求之後,docker0⽹橋就會扮演⼆層交換機的⻆⾊,把ARP⼴播轉發到其他被“插”在docker0上的虛擬⽹卡上。這樣,同樣連接在docker0上的redis-2容器的⽹絡協議棧就會收到這個ARP請求,從⽽將172.17.0.3所對應的MAC地址回覆給redis-1容器。

​ 有了目的的MAC地址,redis-1容器的eth0網卡就可以將數據包發出去。

​ ⽽根據Veth Pair設備的原理,這個數據包會⽴刻出現在宿主機上的veth703d5b8虛擬⽹卡上。不過,此時這個veth703d5b8⽹卡的⽹絡協議棧的資格已經被“剝奪”,所以這個數據包就直接流⼊到了docker0⽹橋⾥。

​ 因爲是同宿主機內的容器,所以dockere0根據二層ebtables規則轉發該數據包,及轉發到veth3571f6b網卡。這個網卡就是redis-2的Veth Pair虛擬網絡設備的另一端,所以redis-1發來的數據包就會出現在redis-2容器的eth0網卡上。

​ 這樣,redis-2的網絡協議棧就會對請求進行處理,返回Pong到redis-1。

跨主網絡實現

​ 通過前面一節,瞭解到同宿主機下的容器網絡通過網橋+Veth Pair的方式來實現網絡通信。但是,容器不會永遠只和同宿主機的容器通信。

問題2:不同宿主機上的容器如何實現網絡通信?

​ 這個問題,其實就是容器的“跨主通信“問題”。在Docker的默認配置下,一臺宿主機上的docker0網橋,和其他宿主機上的docker0網橋,沒有任何關聯,它們互相之間也沒辦法連通。所以,連接在這些網橋上的容器,自然也沒辦法進行通信。

Overlay Network

​ 在實現同宿主機下容器互通的時候,爲了把不容Network Namespace下的容器“連到”同一個網絡下,我們創建了docker0網橋。那在實現不同宿主機下的容器互通,我們是否可以效仿呢?答案是肯定的。

在這裏插入圖片描述

​ 通過上圖可以看到,通過軟件的方式,創建一個整個“公用”的網橋,然後把集羣裏的所有容器都連接到這個網橋上,那容器與容器之間是不是就可以根據IP來互通了。

​ 所以構築跨主網絡的核心在於:我們需要在已有的宿主機網絡上,在通過軟件構建一個覆蓋在已有宿主機網絡只上的、可以把所有容器聯通在一起的虛擬網絡。這種網絡就被稱爲:Overlay Network(覆蓋網絡)

​ 當node1的container1要訪問node2上的container1時,node1上的“特殊網橋”在收到數據包之後,能夠通過某種方式,把數據包轉發給正確的容器,node2的container1。

​ 下面通過更詳細的網絡方案,Flannel,來理解容器“跨主通信”的原理。

Flannel網絡插件

​ Flannel項目是CoreOS公司主推的容器網絡方案。Flannel項目本身只是一個框架,真正爲我們提供容器網絡功能的是Flannel的後端實現。Flannel支持三種後端實現,分別是:

  • UPD

  • VXLAN

  • host-gw

    這三種方法代表了三種目前容器跨主網絡的主流實現方法。

​ 在宿主機啓動Flannel後(一般宿主機已經是kubernetes集羣中的一個節點了),首先宿主機上會出現flannel0設備,flannel0是一個TUN設備,Tunnel設備(Network Layerd的虛擬網絡設備,在操作系統內核和用戶應用程序之間傳遞IP包)。然後,在宿主機上創建新的路由規則。以下面架構中node1爲例,會增加一條轉發到flannel0的路由:

$ ip route
default via 10.168.0.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
10.168.0.0/24 dev eth0 proto kernel scope link src 10.168.0.2
UDP模式

​ UDP模式最直接,但是性能最差,目前已經被棄用。不過,我們可以通過UDP模式,來了解容器跨主網絡的實現原理,然後再通過某些技術去解決UDP模式的問題,形成新的解決方案。

​ 通過下面這架構來理解UDP的跨主網絡:

在這裏插入圖片描述

先看一下這個架構,2個node,分別有2個容器:

  • node1,etch0網卡IP爲10.168.0.2,flannel0設備IP爲100.16.1.0,docker0設備IP爲100.96.1.1,container1的IP爲100.96.1.12

  • node2,etch0網卡IP爲10.168.0.3,flannel0設備IP爲100.16.2.0,docker0設備IP爲100.96.2.1,container1的IP爲100.96.2.3

    我們將通過node1發送一個包給node2,通過這個發包整個過程,瞭解flannel UDP模式下的運作原理。

發包過程

  1. 容器發包

    node1的container1裏的進程發起IP包,源IP爲100.96.1.12,目的IP爲100.96.2.1。這裏步驟在上一節的容器網絡有介紹,容器的包如何出現在docker0上,這裏就不在贅述。因此,容器發出的IP包就會出現在docker0網橋上。由於目的IP 100.96.2.3不在node1的docker0的網段上,所以IP包的下一個目的地,就取決於宿主機上的路由規則。

  2. IP包發送至flannel設備

    在前面我們講到flannel會事先在宿主機上添加一條路由規則,在ip route結果裏:

    100.96.0.0/16 dev flannel0 proto kernel scope link src 100.96.1.0
    

    可以看到這個容器發起的IP包匹配這條路由規則,從而進入到flannel0這個設備裏。前面介紹過,這個設備是在操作系統內核和用戶應用程序之間傳遞IP包。當IP包發送給flannel0設備後,flannel0就會把這個IP包,交個創建這個設備的應用程序,也就是Flannel進程。這個一個從內核態向用戶態的流動方向。flanneld(即Flannel進程)收到IP包後,根據目的IP 100.96.2.3,就把這個IP包發到node2宿主機。

    問題3:flanneld進程如何知道IP的轉發路徑?

    這裏就不得不提Flannel中一個很重要的概念:子網(subnet)

    在由Flannel管理的容器網絡裏,一臺宿主機上的所有容器,都屬於該宿主機被分配的一個“子網”。在我們的例子中,node1的子網是100.96.1.0/24,node1的container1 IP爲100.96.1.12。node2的子網是100.96.2.0/24,node2的container1 IP爲100.96.2.3。那這些對應關係存在哪呢?怎麼讓不同宿主機的flanneld進程都知道呢?別忘了,我們現在kubernetes的環境下,kubernetes通過Etcd來存儲整個集羣的元數據。同樣的,子網與宿主機的對應關係,也是保存在Etcd當中。可以通過etcdctl查看:

    $ etcdctl ls /coreos.com/network/subnets
    /coreos.com/network/subnets/100.96.1.0-24
    /coreos.com/network/subnets/100.96.2.0-24
    

    所以,flanneld進程在處理由flannel0傳入的IP包時,就可以根據目的IP的地址,匹配到對應的子網,然後再從Etcd中找到這個子網對應的宿主機的IP是10.168.0.3。

    $ etcdctl get /coreos.com/network/subnets/100.96.2.0-24
    {"PublicIP":"10.168.0.3"}
    
  3. 封裝UDP

    flanneld在收到IP包後,就會把這個IP包直接封裝在一個UDP包裏,然後發送給node2。源IP爲10.168.0.2,即node1的IP地址。目的IP爲10.168.0.3,即node2的IP地址。當然,這個發送請求可以成功的原因是,每臺宿主機上的flanneld都監聽這個一個8285的端口,所以flanneld只要把UDP包發往Node2的8285端口即可。

  4. flanneld把包通過eth0發到node2上,通過宿主網絡

  5. 解封裝UDP

    node2上監聽8285端口的進程也是flanneld,所以,flanneld就可以從這個UDP包解析出封裝在裏面的、node1的container1的IP包。接下來,flanneld會直接把這個IP包發送給它所管理的TUN設備,即flannel0設備。

  6. flannel設備轉發至docker0

    根據前面講的,這是一個從用戶態向內核態的流動方向,所以Linux內核網絡棧就會負責處理這個IP包,具體通過本機的路由表來尋找這個IP包下一步要轉發到哪裏。根據node2的ip route:

    $ ip route
    default via 10.168.0.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
    10.168.0.0/24 dev eth0 proto kernel scope link src 10.168.0.3
    

    這個IP包的目的IP是100.96.2.3,匹配第三條,所以這個IP包就會轉發至docker0網橋

  7. 容器接收IP包

    第七步,就和容器網絡裏面的接收是一樣的,docker0網橋會扮演二層交換機的角色,將數據包發送給正確的端口,進而通過Veth Pair設備進入到node2的container1的Network Namespace裏。

​ 至此,就是跨主網絡flannel UDP模式通信的完整例子。這個例子有個前提,就是docker0網橋的地址範圍必須是Flannel爲宿主機分配的子網。可以通過下面方式實現:

$ FLANNEL_SUBNET=100.96.1.1/24
$ dockerd --bip=$FLANNEL_SUBNET ...
#在kubernetes裏可以通過kubelet的參數--pod-network-cidr設定

​ 可以看到,Flannel UDP模式提供的其實是⼀個三層的Overlay⽹絡,即:它⾸先對發出端的IP包進⾏UDP封裝,然後在接收端進⾏解封裝拿到原始的IP包,進⽽把這個IP包轉發給⽬標容器。這就好⽐,Flannel在不同宿主機上的兩個容器之間打通了⼀條“隧道”,使得這兩個容器可以直接使⽤IP地址進⾏通信,⽽⽆需關⼼容器和宿主機的分佈情況。

性能不足分析

​ 在一開始的時候,我們提到UDP模式雖然直接,但是性能不好,現在我們分析下爲何性能不好。

​ 實際上,相⽐於兩臺宿主機之間的直接通信,基於Flannel UDP模式的容器通信多了⼀個額外的步驟,即flanneld的處理過程。⽽這個過程,由於使⽤到了flannel0這個TUN設備,僅在發出IP包的過程中,就需要經過三次⽤戶態與內核態之間的數據拷⻉,如下所示:

在這裏插入圖片描述
​ 我們可以看到:
​ 第⼀次:⽤戶態的容器進程發出的IP包經過docker0⽹橋進⼊內核態;
​ 第⼆次:IP包根據路由表進⼊TUN(flannel0)設備,從⽽回到⽤戶態的flanneld進程;
​ 第三次:flanneld進⾏UDP封包之後重新進⼊內核態,將UDP包通過宿主機的eth0發出去。
​ 此外,我們還可以看到,Flannel進⾏UDP封裝(Encapsulation)和解封裝(Decapsulation)的過程,也都是在⽤戶態完成的。在Linux操作系統中,上述這些上下⽂切換和⽤戶態操作的代價其實是⽐較⾼的,這也正是造成Flannel UDP模式性能不好的主要原因。
​ 所以說,我們在進⾏系統級編程的時候,有⼀個⾮常重要的優化原則,就是要減少⽤戶態到內核態的切換次數,並且把核⼼的處理邏輯都放在內核態進⾏。

VXLAN模式

​ 從上面UDP性能分析來看,UDP模式主要問題是在有多次的用戶態轉內核態,內核態轉用戶態的操作。因此,Flannel後來提出了VXLAN模式,也是主流的容器網絡方案。

​ VXLAN,即Virtual Extensible LAN(虛擬可擴展局域網),是Linux內核本身就支持的一種網絡虛擬化技術。與UDP模式不同的是,VXLAN可以完全在內核態實現UDP封裝和解封裝的工作,從而通過與前面相似的“隧道”機制,構建出Overlay Network。

​ VXLAN的覆蓋⽹絡的設計思想是:在現有的三層⽹絡之上,“覆蓋”⼀層虛擬的、由內核VXLAN模塊負責維護的⼆層⽹絡,使得連接在這個VXLAN⼆層⽹絡上的“主機”(虛擬機或者容器都可以)之間,可以像在同⼀個局域⽹(LAN)⾥那樣⾃由通信。當然,實際上,這些“主機”可能分佈在不同的宿主機上,甚⾄是分佈在不同的物理機房⾥。
​ ⽽爲了能夠在⼆層⽹絡上打通“隧道”,VXLAN會在宿主機上設置⼀個特殊的⽹絡設備作爲“隧道”的兩端。這個設備就叫作VTEP,即:VXLAN Tunnel End Point(虛擬隧道端點)。VTEP設備的作用,其實跟前面的flanneld進程非常相似。只不過,它進行封裝和解封裝的對象,是二層數據幀(Ethernet Frame),並且整個工作的執行流程,全部都是在內核裏完成的,因爲VXLAN本身就是Linux內核中的一個模塊。

​ 和UDP模式一樣,在啓動flannel VXLAN模式後,會在宿主機上創建一個flannel.1的虛擬網絡設備,也就是VTEP設備,這個設備既有IP地址也有MAC地址。如果這個Flannel網絡有別的宿主機,就會添加一條路由到其他宿主機上。例如,node2是後來添加到Flannel網絡的,那node的路由規則就會添加下面這條:

Destination Gateway    Genmask        Flags  Metric Ref   Use  Iface
100.96.2.0  100.96.2.0 255.255.255.0  UG     0      0     0    flannel.1

VXLAN的架構如下,和UDP模式的很類似:

在這裏插入圖片描述

​ 我們也通過node1的container1發包到node2的container1來了解VXLAN的工作模式,主要封包封裝的變化:

  1. 容器發包

    實現方式和UDP模式的第一個步驟一樣,不做贅述。

  2. IP包轉發至flannel.1

    實現方式和UDP模式的第二個步驟類似,不做贅述。

    到這裏的包頭如下:

在這裏插入圖片描述

  1. 封裝成爲UDP後轉發至node2

    此時IP包已經到了VTEP設備,即flannel.1。這些VTEP設備之間,就需要想辦法組成一個虛擬的二層網絡,即通過二層數據幀進行通信。所以,“源VTEP設備”(node1的flannel.1)收到IP包後,就要把IP包加上一個目的MAC地址,封裝成一個二層數據幀,然後發送給“目的VTEP設備”(node2的flannel.1)。

    目的VTEP設備的MAC地址,早就在node2添加到Flannel網絡的時候,自動添加到node1上的ARP記錄裏。可以用指令ip neigh show dev flannel.1查看

    $ ip neigh show dev flannel.1
    100.96.2.0 lladdr 5e:f8:4f:00:e3:37 PERMANENT
    

    有了這個目的VTEP設備的MAC地址,Linux內核就可以開始二層封包工作。Linux內核會把目的VTEP設備的MAC地址,填在Inner Ethernet Header字段,得到一個二層數據幀,此時包頭如下:

在這裏插入圖片描述

但是添加的目的VTEP設備MAC地址相對於宿主機來說沒有實際意義,因爲現在封裝好的數據幀,並不能在宿主機的二層網絡裏傳輸,也被成爲內部數據幀(Inner Ethernet Frame)。爲了讓內部數據幀轉換爲外部數據幀,成爲宿主機網絡裏的一個普通數據幀,Linux內核還需要做如下動作。

Linux內核會在“內部數據幀”前面,加一個特殊的VXLAN頭,用來表示這是實際上是一個VXLAN要使用的數據幀,此時包頭如下:

在這裏插入圖片描述

然後,Linux內核會把這個數據幀封裝進一個UDP包發出去。跟UDP模式類似,在宿主機看來,它會以爲⾃⼰的flannel.1設備只是在向另外⼀臺宿主機的flannel.1設備,發起了⼀次普通的UDP鏈接。它哪⾥會知道,其實這個UDP包⾥⾯,其實是⼀個完整的⼆層數據幀:

在這裏插入圖片描述

到目前爲止,外部數據幀還剩下IP層和數據層還沒有封裝。從前面得知,flannel.1設備只知道另一端flannel.1設備的MAC地址,卻不知道對應的宿主機地址是什麼,所以我們要先找到這個UDP包應該發給哪個宿主機。

flannel.1設備實際上要扮演一個“網橋”的角色,在二層網絡進行UDP包的轉發。而在Linux內核裏面,“⽹橋”設備進⾏轉發的依據,來⾃於⼀個叫作FDB(Forwarding Database)的轉發數據庫。因此,flannel.1對應的FDB信息可以通過bridge fdb找到,這個也是通過flanneld進程維護的:

# 在Node 1上,使⽤“⽬的VTEP設備”的MAC地址進⾏查詢
$ bridge fdb show flannel.1 | grep 5e:f8:4f:00:e3:37
5e:f8:4f:00:e3:37 dev flannel.1 dst 10.168.0.3 self permanent

找到目的宿主機的IP之後,就是一個正常的、宿主機網絡上的封包工作,外部數據幀的包頭如下:

在這裏插入圖片描述

這樣封包工作就結束了。

  1. node2收到數據包

    node2的內核網絡棧發現這個數據幀裏有VXLAN Header,所以Linux內核會對它進行拆包,拿到裏面的內部數據幀,交給node2的flannel.1設備。

  2. flannel.1設備轉發到docker0

    flannel.1設備會繼續拆包,取出“原始IP包”。根據取得的IP,轉發至docker0設備。

  3. 容器接收IP包

    第六步,就和容器網絡裏面的接收是一樣的,docker0網橋會扮演二層交換機的角色,將數據包發送給正確的端口,進而通過Veth Pair設備進入到node2的container1的Network Namespace裏。

​ VXLAN模式組件的覆蓋網絡,其實就是一個由不同宿主機上的VTEP設備,也就是flannel.1設備組成的虛擬二層網絡。對於VTEP設備來說,它發出的“內部數據幀”就彷彿是⼀直在這個虛擬的⼆層⽹絡上流動。這也正是覆蓋⽹絡的含義。

​ 相較於UDP模式,VXLAN少了flanneld的封裝動作,都在Linux內核完成了。

Kubernetes網絡實現

問題4:在前面兩節介紹了容器在同宿主機,不同宿主機之間的網絡通信,那放到kubernetes上是如何做的呢?

​ 網絡插件真正要做的事情,則是通過某種方法,把不同宿主機上的特殊設備聯通個,從而達到容器跨主機通信的目的。

CNI插件

​ 實際上,kubernetes對容器網絡的主要處理方法。kubernets是通過CNI的接口,維護了一個單獨的網橋來代替docker0。這個網橋叫CNI網橋,它在宿主機上的設備名稱默認是:cni0。在kubernetes環境裏,它的工作方式跟上兩節的工作方式沒有不同,只不過docker0網橋被替換成CNI網橋

架構基於Flannel VXLAN模式,如下:

在這裏插入圖片描述

在這⾥,Kubernetes爲Flannel分配的⼦⽹範圍是10.244.0.0/16。可以在部署的時候指定:

$ kubeadm init --pod-network-cidr=10.244.0.0/16
創建理由

​ kubernetes之所以要設置這樣⼀個與docker0⽹橋功能⼏乎⼀樣的CNI⽹橋,主要原因包括兩個方面:

  • Kubernetes項⽬並沒有使⽤Docker的⽹絡模型(CNM),所以它並不希望、也不具備配置docker0⽹橋的能⼒;

  • 這還與Kubernetes如何配置Pod,也就是Infra容器的Network Namespace密切相關。

設計思想

​ kubernetes在啓動Infra容器之後,就可以直接調⽤CNI⽹絡插件,爲這個Infra容器的Network Namespace,配置符合預期的⽹絡棧。

管理對象

注意:CNI網橋只接管所有CNI插件負責的、即kubernetes創建的容器。如果用docker run單獨啓動的一個容器,那麼Docker項目還是會把這個容器連接到docker0網橋上。這個容器的IP也應該是docker0網橋的網段。

部署與實現

問題5:CNI如何配置容器的網絡棧?

  • 部署

    在部署Kubernetes的時候,有⼀個步驟是安裝kubernetes-cni包,⽬的就是在宿主機上安裝CNI插件所需的基礎可執行文件。安裝完後,可以通過ls -al /opt/cni/bin查看相關的執行文件:

在這裏插入圖片描述

上圖的文件可以分爲這幾個類:

main插件:用來創建具體的網絡設備的二進制文件,有bridge,ipvlan,lookback,macvlan,ptp,vlan

IPAM(IP Address Management)插件:負責分配IP地址的二進制文件,dhcp,host-local

由CNI社區維護的CNI插件:flannel,tuning,portmap,bandwidth。注意,flannel CNI插件內置,Weave,Calico等網絡插件需要把對應的CNI插件放到/opt/cni/bin下

  • 實現

    如果要實現一個給kubernetes用的容器網絡方案,其實需要做兩部分工作,以Flannel項目爲例:

    ⾸先,實現這個網絡方案本身。這⼀部分需要編寫的,其實就是flanneld進程⾥的主要邏輯。⽐如,創建和配置flannel.1設備、配置宿主機路由、配置ARP和FDB表⾥的信息等等。
    然後,實現該⽹絡⽅案對應的CNI插件。這⼀部分主要需要做的,就是配置Infra容器⾥⾯的⽹絡棧,並把它連接在CNI⽹橋上。

    接下來,你就需要在宿主機上安裝flanneld(網絡方案本身)。⽽在這個過程中,flanneld啓動後會在每臺宿主機上⽣成它對應的CNI配置⽂件(它其實是⼀個ConfigMap),從⽽告訴Kubernetes,這個集羣要使⽤Flannel作爲容器⽹絡⽅案。加載CNI配置文件的進程是dockershim,它是kubernetes CRI的實現(在kubernetes中,處理容器網絡相關的邏輯並不會在kubelet主幹代碼裏執行,而是會在具體的CRI實現裏完成)。

    /etc/cni/net.d/10-flannel.conflist爲例:

在這裏插入圖片描述

dockershim會加載上述的CNI配置文件。注意,kubernetes⽬前不⽀持多個CNI插件混⽤。如果你在CNI配置⽬錄(/etc/cni/net.d)⾥放置了多個CNI配置⽂件的話,dockershim只會加載按字⺟順序排序的第⼀個插件。

CNI允許你在一個CNI配置文件裏,通過plugins字段,可以定義多個插件進行協作。以上述的CNI配置文件,dockershim會把這個CNI配置文件加載起來,並且把列表裏的第一個插件,也就是flannel插件,設置爲默認插件。在後⾯的執⾏過程中,flannel和portmap插件會按照定義順序被調⽤,從⽽依次完成“配置容器⽹絡”和“配置端⼝映射”這兩步操作。

注意到上面配置文件裏面有一個字段叫delegate。Delegate字段的意思是,這個CNI插件並不會自己做事,⽽是會調⽤Delegate指定的某種CNI內置插件來完成。對於Flannel來說,它調⽤的Delegate插件,就是前⾯介紹到的CNI bridge插件。所以說,dockershim對Flannel CNI插件的調⽤,其實就是⾛了個過場。Flannel CNI插件唯⼀需要做的,就是對dockershim傳來的Network Configuration進⾏補充。

工作原理

​ 當kubelet組件需要創建Pod的時候,它第⼀個創建的⼀定是Infra容器。所以在這⼀步,dockershim就會先調⽤Docker API創建並啓動Infra容器,緊接着執⾏⼀個叫作SetUpPod的⽅法。這個⽅法的作⽤就是:爲CNI插件準備參數,然後調⽤CNI插件爲Infra容器配置⽹絡。

​ 以上面的例子,就是會調用/opt/cni/bin/flannel插件,它需要兩部分參數

  1. 由dockershim設置的一組CNI環境變量

    環境變量參數: CNI_COMMAND,值爲ADD和DEL。ADD和DEL也是CNI插件唯一需要實現的兩個方法。字面理解就是ADD把容器添加到CNI網絡裏,DEL把容器從CNI網絡裏移除。

    對於⽹橋類型的CNI插件來說,這兩個操作意味着把容器以Veth Pair的⽅式“插”到CNI⽹橋上,或者從⽹橋上“拔”掉。

  2. dockershim從CNI配置文件里加載到的、默認插件的配置信息

    這個配置信息在CNI中被叫作Network Configuration,dockershim會把Network Configuration以JSON數據的格式,通過標準輸入的方式傳給Flannel CNI插件。

接下去我們通過這兩部分參數實現的ADD操作,來看一下CNI插件如何把一個Infra容器加到CNI網絡裏。

  1. 補充配置文件

    在上面講到因爲配置文件中的Delegate字段,所以Flannel CNI會對dockershim傳來的Network Configuration會先進行補充,⽐如,將Delegate的Type字段設置爲bridge,將Delegate的IPAM字段設置爲host-local等:

    {
    	"hairpinMode":true,
    	"ipMasq":false,
    	#讀取⾃Flannel在宿主機上⽣成的Flannel配置⽂件,即:宿主機上的/run/flannel/subnet.env⽂件。
    	"ipam":{ 
    		"routes":[
    			{
    				"dst":"10.244.0.0/16"
    			}
    		],
    		"subnet":"10.244.1.0/24",
    		"type":"host-local"
    	},
    	"isDefaultGateway":true,
    	"isGateway":true,
    	"mtu":1410,
    	"name":"cbr0",
    	"type":"bridge"
    }
    

    這就是補充完之後的Delegate字段。

  2. 調用CNI bridge插件

    Flannel CNI插件就會調用CNI bridge插件,也就是/opt/cni/bin/bridge二進制文件。

    調⽤CNI bridge插件需要的兩部分參數:

    • 第⼀部分、也就是CNI環境變量,CNI_COMMAND = ADD
    • 第⼆部分Network Configration,正是上⾯補充好的Delegate字段。Flannel CNI插件會把Delegate字段的內容以標準輸⼊(stdin)的⽅式傳遞給CNI bridge插件。

    有了這兩部分參數,接下來CNI bridge插件就可以“代表”Flannel,進⾏“將容器加⼊到CNI⽹絡⾥”這⼀步操作了 。

  3. 檢查CNI網橋是否存在

    如果沒有會創建一個CNI網橋,這個步驟在宿主機上執行:

    $ ip link add cni0 type bridge
    $ ip link set cni0 up
    
  4. 創建Veth Pair設備

    CNI bridge插件會通過Infra容器的Network Namespace⽂件,進⼊到這個Network Namespace⾥⾯,然後創建⼀對Veth Pair設備。並且把其中一個Veth Pair“移動”到宿主機上:

    #創建⼀對Veth Pair設備。其中⼀個叫作eth0,另⼀個叫作vethb4963f3
    $ ip link add eth0 type veth peer name vethb4963f3
    
    # 啓動eth0設備
    $ ip link set eth0 up
    
    # 將Veth Pair設備的另⼀端(也就是vethb4963f3設備)放到宿主機(也就是Host Namespace)⾥
    $ ip link set vethb4963f3 netns $HOST_NS
    
    # 通過Host Namespace,啓動宿主機上的vethb4963f3設備
    $ ip netns exec $HOST_NS ip link set vethb4963f3 up
    

    這樣Veth Pair設備,容器端的eth0,宿主機端的vethb4963f3就創建好了。

  5. Veth Pair連至CNI網橋

    在宿主機上,CNI bridge插件要把宿主機端的Veth Pair設備連到CNI網橋上:

    $ ip link set vethb4963f3 master cni0
    
  6. 設置Hairpin Mode(髮夾模式)

    在vethb4963f3連至CNI網橋後,CNI bridge插件會爲他設置Hairpin Mode。這是因爲,在默認情況下,⽹橋設備是不允許⼀個數據包從⼀個端⼝進來後,再從這個端⼝發出去的。但是,它允許你爲這個端⼝開啓HairpinMode,從⽽取消這個限制。這個特性,主要⽤在容器需要通過NAT(即:端⼝映射)的⽅式,“⾃⼰訪問⾃⼰”的場景下。

  7. 調用CNI ipam插件

    CNI bridge插件會調⽤CNI ipam插件,從ipam.subnet字段規定的⽹段⾥爲容器分配⼀個可⽤的IP地址。然後,CNI bridge插件就會把這個IP地址添加在容器的eth0⽹卡上,同時爲容器設置默認路由,在容器裏:

    $ ip addr add 10.244.0.2/24 dev eth0
    $ ip route add default via 10.244.0.1 dev eth0
    
  8. 爲CNI網橋添加IP

    最後,CNI bridge插件會爲CNI網橋添加IP地址,在宿主上:

    $ ip addr add 10.244.0.1/24 dev cni0
    

在執⾏完上述操作之後,CNI插件會把容器的IP地址等信息返回給dockershim,然後被kubelet添加到Pod的Status字段。至此,網橋類型CNI插件的ADD方法實現流程就結束了(非網橋類型的CNI插件會有不同,需要注意)。


總結

​ 通過從docker角度看網絡模型,一步一步瞭解,到從kubernetes角度看網絡模型的實現。

​ 容器首先通過網橋與宿主機搭了一座橋,docker0。再創建Veth Pair設備上橋,這樣容器的網絡和宿主機的網絡就可以串在一起了,容器的數據包可以出現在宿主機上進行下一步的轉發。之後,爲了讓不同宿主機之間的容器可以通過容器IP互通,引入了跨主通信的覆蓋網絡,overlay network。Overlay Network的設計思想也是像網橋一樣,只不過搭的橋連通的是不同宿主機上的容器。再通過Flannel項目的UDP模式和VXLAN模式更深入瞭解跨主通信這座橋是怎麼搭的,怎麼通的。由於是不同的宿主機,爲了能夠讓橋上的容器都知道這座橋有誰,把這個橋上的容器信息放在etcd上,供查閱。

​ 最後kubernetes集羣的網絡模型,由於kubernetes的最小調度單位是Pod,而不是docker容器。所以雖然kubernetes也搭橋,但是是用CNI插件搭了一個CNI網橋。也通過Flannel項目的例子,來描述了kubernetes的Pod如何上這個CNI網橋。

​ 網絡插件還有很多,但是基本原理都是通過,建一座橋,然後找個管橋的,最後再找個車伕接人上橋。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章