1. 程式人生 > >淺談K8S cni和網路方案

淺談K8S cni和網路方案

此文已由作者黃揚授權網易雲社群釋出。


歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。



在早先的k8s版本中,kubelet程式碼裡提供了networkPlugin,networkPlugin是一組介面,實現了pod的網路配置、解除、獲取,當時kubelet的程式碼中有個一個docker_manager,負責容器的建立和銷燬,亦會負責容器網路的操作。而如今我們可以看到基本上kubelet的啟動引數中,networkPlugin的值都會設定為cni。

cni外掛的使用方式

使用CNI外掛時,需要做三個配置:

  • kubelet啟動引數中networkPlugin設定為cni

  • 在/etc/cni/net.d中增加cni的配置檔案,配置檔案中可以指定需要使用的cni元件及引數

  • 將需要用到的cni元件(二進位制可執行檔案)放到/opt/cni/bin目錄下

所有的cni元件都支援兩個命令:add和del。即配置網路和解除網路配置。

cni外掛的配置檔案是一個json檔案,不同版本的介面、以及不同的cni元件,有著不同的配置內容結構,目前比較通用的介面版本是0.3.1的版本。

在配置檔案中我們可以填入多個cni元件,當這些cni元件的配置以陣列形式記錄時,kubelet會對所有的元件進行按序鏈式呼叫,所有元件呼叫成功後,視為網路配置完成,過程中任何一步出現error,都會進行回滾的del操作。以保證操作流上的原子性。

幾種基本的cni外掛

cni外掛按照程式碼中的存放目錄可以分為三種:ipam、main、meta。

  • ipam cni用於管理ip和相關網路資料,配置網絡卡、ip、路由等。

  • main cni用於進行網路配置,比如建立網橋,vethpair、macvlan等。

  • meta cni有的是用於和第三方CNI外掛進行適配,如flannel,也有的用於配置核心引數,如tuning

由於官方提供的cni元件就有很多,這裡我們詳細介紹一些使用率較高的元件。

ipam類CNI

ipam型別的cni外掛,在執行add命令時會分配一個IP給呼叫者。執行del命令時會將呼叫者指定的ip放回ip池。社群開源的ipam有host-local、dhcp。

host-local

我們可以通過host-local的配置檔案的資料結構來搞懂這個元件是如何管理ip的。

type IPAMConfig struct {
    *Range
    Name       string
    Type       string         `json:"type"`
    Routes     []*types.Route `json:"routes"`//交付的ip對應的路由
    DataDir    string         `json:"dataDir"`//本地ip池的資料庫目錄
    ResolvConf string         `json:"resolvConf"`//交付的ip對應的dns
    Ranges     []RangeSet     `json:"ranges"`//交付的ip所屬的網段,閘道器資訊
    IPArgs     []net.IP       `json:"-"` // Requested IPs from CNI_ARGS and args
}

#配置檔案範例:
{
    "cniVersion": "0.3.1",
    "name": "mynet",
    "type": "ipvlan",
    "master": "foo0",
    "ipam": {
        "type": "host-local",
        "resolvConf": "/home/here.resolv",
        "dataDir": "/home/cni/network",
        "ranges": [
            [
                {
                    "subnet": "10.1.2.0/24",
                    "rangeStart": "10.1.2.9",
                    "rangeEnd": "10.1.2.20",
                    "gateway": "10.1.2.30"
                },
                {
                    "subnet": "10.1.4.0/24"
                }
            ],
            [{
                "subnet": "11.1.2.0/24",
                "rangeStart": "11.1.2.9",
                "rangeEnd": "11.1.2.20",
                "gateway": "11.1.2.30"
            }]
        ]
    }
}

從上面的配置我們可以清楚:

  • host-local元件通過在配置檔案中指定的subnet進行網路劃分

  • host-local在本地通過指定目錄(預設為/var/lib/cni/networks)記錄當前的ip pool資料

  • host-local將IP分配並告知呼叫者時,還可以告知dns、路由等配置資訊。這些資訊通過配置檔案和對應的resolv檔案記錄。

host-local的應用範圍比較廣,kubenet、bridge、ptp、ipvlan等cni network外掛都被用來和host-local配合進行ip管理。

dhcp

社群的cni元件中就包含了dhcp這個ipam,但並沒有提供一個可以參考的案例,翻看了相關的原始碼,大致邏輯是:

  • 向dhcp申請ip時,dhcp會使用rpc訪問本地的socket(/run/cni/dhcp.sock)申請一個ip的租約。然後將IP告知呼叫者。

  • 向dhcp刪除IP時,dhcp同樣通過rpc請求,解除該IP的租約。

main(network)類CNI

main型別的cni元件做的都是一些核心功能,比如配置網橋、配置各種虛擬化的網路介面(veth、macvlan、ipvlan等)。這裡我們著重講使用率較高的bridge和ptp。

bridge

brige模式,即網橋模式。在node上建立一個linux bridge,並通過vethpair的方式在容器中設定網絡卡和IP。只要為容器配置一個二層可達的閘道器:比如給網橋配置IP,並設定為容器ip的閘道器。容器的網路就能建立起來。

如下是bridge的配置項資料結構:

type NetConf struct {
    types.NetConf
    BrName       string `json:"bridge"` //網橋名
    IsGW         bool   `json:"isGateway"`  //是否將網橋配置為閘道器
    IsDefaultGW  bool   `json:"isDefaultGateway"` //
    ForceAddress bool   `json:"forceAddress"`//如果網橋已存在且已配置了其他IP,通過此引數決定是否將其他ip除去
    IPMasq       bool   `json:"ipMasq"`//如果true,配置私有網段到外部網段的masquerade規則
    MTU          int    `json:"mtu"`
    HairpinMode  bool   `json:"hairpinMode"`
    PromiscMode  bool   `json:"promiscMode"`
}

我們關注其中的一部分欄位,結合程式碼可以大致整理出bridge元件的工作內容。首先是ADD命令:

  • 執行ADD命令時,brdige元件建立一個指定名字的網橋,如果網橋已經存在,就使用已有的網橋;

  • 建立vethpair,將node端的veth裝置連線到網橋上;

  • 從ipam獲取一個給容器使用的ip資料,並根據返回的資料計算出容器對應的閘道器;

  • 進入容器網路名字空間,修改容器中網絡卡名和網絡卡ip,以及配置路由,並進行arp廣播(注意我們只為vethpair的容器端配置ip,node端是沒有ip的);

  • 如果IsGW=true,將網橋配置為閘道器,具體方法是:將第三步計算得到的閘道器IP配置到網橋上,同時根據需要將網橋上其他ip刪除。最後開啟網橋的ip_forward核心引數;

  • 如果IPMasq=true,使用iptables增加容器私有網網段到外部網段的masquerade規則,這樣容器內部訪問外部網路時會進行snat,在很多情況下配置了這條路由後容器內部才能訪問外網。(這裡程式碼中會做exist檢查,防止生成重複的iptables規則);

  • 配置結束,整理當前網橋的資訊,並返回給呼叫者。

其次是DEL命令:

  • 根據命令執行的引數,確認要刪除的容器ip,呼叫ipam的del命令,將IP還回IP pool;

  • 進入容器的網路名字空間,根據容器IP將對應的網絡卡刪除;

  • 如果IPMasq=true,在node上刪除建立網路時配置的幾條iptables規則。

ptp

ptp其實是bridge的簡化版。但是它做的網路配置其實看上去倒是更復雜了點。並且有一些配置在自測過程中發現並沒有太大用處。它只建立vethpair,但是會同時給容器端和node端都配置一個ip。容器端配置的是容器IP,node端配置的是容器IP的閘道器(/32),同時,容器裡做了一些特殊配置的路由,以滿足讓容器發出的arp請求能被vethpair的node端響應。實現內外的二層連通。

ptp的網路配置步驟如下:

  • 從ipam獲取IP,根據ip型別(ipv4或ipv6)配置響應的核心ip_forward引數;

  • 建立一對vethpair;一端放到容器中;

  • 進入容器的網路namespace,配置容器端的網絡卡,修改網絡卡名,配置IP,並配置一些路由。假如容器ip是10.18.192.37/20,所屬網段是10.18.192.0/20,閘道器是10.18.192.1,我們這裡將進行這樣的配置:

    • 配置IP後,核心會自動生成一條路由,形如:10.18.192.0/20 dev eth0 scope link,我們將它刪掉:ip r d ****

    • 配置一條私有網到閘道器的真實路由:ip r a 10.18.192.0/20 via 10.18.192.1 dev eth0

    • 配置一條到閘道器的路由:10.18.192.1/32 dev eth0 scope link

  • 退出到容器外,將vethpair的node端配置一個IP(ip為容器ip的閘道器,mask=32);

  • 配置外部的路由:訪問容器ip的請求都路由到vethpair的node端裝置去。

  • 如果IPMasq=true,配置iptables

  • 獲取完整的網絡卡資訊(vethpair的兩端),返回給呼叫者。

與bridge不同主要的不同是:ptp不使用網橋,而是直接使用vethpair+路由配置,這個地方其實有很多其他的路由配置可以選擇,一樣可以實現網路的連通性,ptp配置的方式只是其中之一。萬變不離其宗的是:

只要容器內網絡卡發出的arp請求,能被node回覆或被node轉發並由更上層的裝置回覆,形成一個二層網路,容器裡的資料報文就能被髮往node上;然後通過node上的路由,進行三層轉發,將資料報文發到正確的地方,就可以實現網路的互聯。

bridge和ptp其實是用了不同方式實現了這個原則中的“二層網路”:

  • bridge元件給網橋配置了閘道器的IP,並給容器配置了到閘道器的路由。實現二層網路

  • ptp元件給vethpair的對端配置了閘道器的IP,並給容器配置了單獨到閘道器IP的路由,實現二層網路

ptp模式的路由還存在一個問題:沒有配置default路由,因此容器不能訪問外部網路,要實現也很簡單,以上面的例子,在容器裡增加一條路由:default via 10.18.192.1 dev eth0

host-device

相比前面兩種cni main元件,host-device顯得十分簡單因為他就只會做兩件事情:

  • 收到ADD命令時,host-device根據命令引數,將網絡卡移入到指定的網路namespace(即容器中)。

  • 收到DEL命令時,host-device根據命令引數,將網絡卡從指定的網路namespace移出到root namespace。

細心的你肯定會注意到,在bridge和ptp元件中,就已經有“將vethpair的一端移入到容器的網路namespace”的操作。那這個host-device不是多此一舉嗎?

並不是。host-device元件有其特定的使用場景。假設叢集中的每個node上有多個網絡卡,其中一個網絡卡配置了node的IP。而其他網絡卡都是屬於一個網路的,可以用來做容器的網路,我們只需要使用host-device,將其他網絡卡中的某一個丟到容器裡面就行。

host-device模式的使用場景並不多。它的好處是:bridge、ptp等方案中,node上所有容器的網路報文都是通過node上的一塊網絡卡出入的,host-device方案中每個容器獨佔一個網絡卡,網路流量不會經過node的網路協議棧,隔離性更強。缺點是:在node上配置數十個網絡卡,可能並不好管理;另外由於不經過node上的協議棧,所以kube-proxy直接廢掉。k8s叢集內的負載均衡只能另尋他法了。

macvlan

有關macvlan的實踐可以參考這篇文章。這裡做一個簡單的介紹:macvlan是linux kernal的特性,用於給一個物理網路介面(parent)配置虛擬化介面,虛擬化介面與parent網路介面擁有不同的mac地址,但parent介面上收到發給其對應的虛擬化介面的mac的包時,會分發給對應的虛擬化介面,有點像是將虛擬化介面和parent介面進行了'橋接'。給虛擬化網路介面配置了IP和路由後就能互相訪問。

macvlan省去了linux bridge,但是配置macvlan後,容器不能訪問parent介面的IP。

ipvlan

ipvlan與macvlan有點類似,但對於核心要求更高(3.19),ipvlan也會從一個網路介面創建出多個虛擬網路介面,但他們的mac地址是一樣的, 只是IP不一樣。通過路由可以實現不同虛擬網路介面之間的互聯。

使用ipvlan也不需要linux bridge,但容器一樣不能訪問parent介面的IP。 關於ipvlan的內容可以參考這篇文章

關於macvlan和ipvlan,還可以參考這篇文章

meta 類CNI

meta元件通常進行一些額外的網路配置(tuning),或者二次呼叫(flannel)。

tuning

用於進行核心網路引數的配置。並將呼叫者的資料和配置後的核心引數返回給呼叫者。

有時候我們需要配置一些虛擬網路介面的核心引數,比如:網易雲在早期經典網路方案中曾修改vethpair的proxy_arp引數(後面會介紹)。可以通過這個元件進行配置。 另外一些可能會改動的網路引數比如:

  • accept_redirects

  • send_redirects

  • proxy_delay

  • accept_local

  • arp_filter

可以在這裡檢視可配置的網路引數和釋義。

portmap

用於在node上配置iptables規則,進行SNAT,DNAT和埠轉發。

portmap元件通常在main元件執行完畢後執行,因為它的執行引數仰賴之前的元件提供

flannel

cni plugins中的flannel是開源網路方案flannel的“呼叫器”。這也是flannel網路方案適配CNI架構的一個產物。為了便於區分,以下我們稱cni plugins中的flannel 為flanenl cni

我們知道flannel是一個容器的網路方案,通常使用flannel時,node上會執行一個daemon程序:flanneld,這個程序會返回該node上的flannel網路、subnet,MTU等資訊。並儲存到本地檔案中。

如果對flannel網路方案有一定的瞭解,會知道他在做網路介面配置時,其實幹的事情和bridge元件差不多。只不過flannel網路下的bridge會跟flannel0網絡卡互聯,而flannel0網絡卡上的資料會被封包(udp、vxlan下)或直接轉發(host-gw)。

flannel cni做的事情就是:

  • 執行ADD命令時,flannel cni會從本地檔案中讀取到flanneld的配置。然後根據命令的引數和檔案的配置,生成一個新的cni配置檔案(儲存在本地,檔名包含容器id以作區分)。新的cni配置檔案中會使用其他cni元件,並注入相關的配置資訊。之後,flannel cni根據這個新的cni配置檔案執行ADD命令。

  • 執行DEL命令時,flannel cni從本地根據容器id找到之前建立的cni配置檔案,根據該配置檔案執行DEL命令。

也就是說flannel cni此處是一個flannel網路模型的委託者,falnnel網路模型委託它去呼叫其他cni元件,進行網路配置。通常呼叫的是bridge和host-local。

幾種常見的網路方案

上述所有的cni元件,能完成的事情就是建立容器到虛擬機器上的網路。而要實現跨虛擬機器的容器之間的網路,有幾種可能的辦法:

  • 容器的IP就是二層網路裡分配的IP,這樣容器相當於二層網路裡的節點,那麼就可以天然互訪;

  • 容器的IP與node的IP不屬於同一個網段,node上配置個到各個網段的路由(指向對應容器網段所部屬的node IP),通過路由實現互訪[flannel host-gw, calico bgp均是通過此方案實現];

  • 容器的IP與node的IP不屬於同一個網段,node上有服務對容器發出的包進行封裝,對發給容器的包進行解封。封裝後的包通過node所在的網路進行傳輸。解封后的包通過網橋或路由直接發給容器,即overlay網路。[flannel udp/vxlan,calico ipip,openshift-sdn均通過此方案實現]

kubenet

瞭解常用的網路方案前,我們先了解一下kubenet,kubenet其實是k8s程式碼中內建的一個cni元件。如果我們要使用kubenet,就得在kubelet的啟動引數中指定networkPlugin值為kubenet而不是cni

如果你閱讀了kubernetes的原始碼,你就可以在一個名為kubenet_linux.go的檔案中看到kubenet做了什麼事情:

  • 身為一種networkPlugin,kubenet自然要實現networkPlugin的一些介面。比如SetUpPod,TearDownPod,GetPodNetworkStatus等等,kubelet通過這些介面進行容器網路的建立、解除、查詢。

  • 身為一個程式碼中內建的cni,kubenet要主動生成一個cni配置檔案(位元組流資料),自己按照cni的規矩去讀取配置檔案,做類似ADD/DEL指令的工作。實現網路的建立、解除。

設計上其實挺蠢萌的。實際上是為了省事。我們可以看下自生成的配置檔案:

{
  "cniVersion": "0.1.0",
  "name": "kubenet",
  "type": "bridge",
  "bridge": "%s", //通常這裡預設是“cbr0”
  "mtu": %d,    //kubelet的啟動引數中可以配置,預設使用機器上的最小mtu
  "addIf": "%s", //配置到容器中的網絡卡名字
  "isGateway": true,
  "ipMasq": false,
  "hairpinMode": %t, 
  "ipam": {
    "type": "host-local",
    "subnet": "%s", //node上容器ip所屬子網,通常是kubelet的pod-cidr引數指定
    "gateway": "%s", //通過subnet可以確定gateway
    "routes": [
      { "dst": "0.0.0.0/0" }
    ]
  }
}

配置檔案中明確了要使用的其他cni元件:bridge、host-local(這裡程式碼中還會呼叫lo元件,通常lo元件會被k8s程式碼直接呼叫,所以不需要寫到cni配置檔案中)。之後的事情就是執行二進位制而已。

為什麼我們要學習kubenet?因為kubenet可以讓使用者以最簡單的成本(配置networkPlugin和pod-cidr兩個啟動kubelet啟動引數),配置出一個簡單的、虛擬機器本地的容器網路。結合上面提到的幾種“跨虛擬機器的容器之間的網路方案”,就是一個完整的k8s叢集網路方案了。

通常kubenet不適合用於overlay網路方案,因為overlay網路方案定製化要求會比較高。

許多企業使用vpc網路時,使用自定義路由實現不同pod-cidr之間的路由,他們的網路方案裡就會用到kubenet,比如azure AKS(基礎網路)。

flannel

關於flannel,上面的文章也提到了一下。網上flannel的文章也是一搜一大把。這裡簡單介紹下flannel對k8s的支援,以及通用的幾個flannel backend(後端網路配置方案)。

flannel for kubernetes

flannel在對kubernets進行支援時,flanneld啟動引數中會增加--kube-subnet-mgr引數,flanneld會初始化一個kubernetes client,獲取本地node的pod-cidr,這個pod-cidr將會作為flannel為node本地容器規劃的ip網段。記錄到/run/flannel/subnet.env。(flannel_cni元件會讀取這個檔案並寫入到net-conf.json中,供cni使用)。

udp/vxlan

flannel的overlay方案。每個node節點上都有一個flanneld程序,和flannel0網橋,容器網路會與flannel0網橋互聯,並經由flannel0發出,所以flanneld可以捕獲到容器發出的報文,進行封裝。udp方案下會給報文包裝一個udp的頭部,vxlan下會給報文包裝一個vxlan協議的頭部(配置了相同VNI的node,就能進行互聯)。 目前flannel社群還提供了更多實驗性的封裝協議選擇,比如ipip,但仍舊將vxlan作為預設的backend。

host-gw

flannel的三層路由方案。每個node節點上都會記錄其他節點容器ip段的路由,通過路由,node A上的容器發給node B上的容器的資料,就能在node A上進行轉發。

alloc

類似kubenet,只分配子網,不做其他任何事情。

支援雲廠商的vpc

flannel支援了aliVPC、gce、aws等雲廠商的vpc網路。原理都是一樣的,就是當flanneld在某雲廠商的機器上執行時,根據機器自身的vpc網路IP,和flanneld分配在該機器上的subnet,呼叫雲廠商的api建立對應的自定義路由。

calico

calico是基於BGP路由實現的容器叢集網路方案,對於使用者來說,基礎的calico使用體驗可能和flannel host-gw是基本一樣的:node節點上做好對容器arp的響應。然後通過node上的路由將容器發出的包轉發到對端容器所在node的IP。對端節點上再將包轉發給對端容器。

ipip模式則如同flannel ipip模式。對報文封裝一個ipip頭部,頭部中使用node ip。傳送到對端容器所在node的IP,對端的網路元件再解包,並轉發給容器。

不同之處在於flannel方案下路由都是通過程式碼邏輯進行配置。而calico則在每個節點建立bgp peer,bgp peer彼此之間會進行路由的共享和學習,所以自動生成並維護了路由。



點選可免費體驗網易雲容器服務

更多網易技術、產品、運營經驗分享請點選


相關文章:
【推薦】 JVM鎖實現探究2:synchronized深探
【推薦】 Flask寫web時cookie的處理
【推薦】 MemcachedHash演算法