Categories
程式開發

從零開始入門 K8s:理解 CNI 和 CNI 插件


網絡架構是 K8s 中較為複雜的方面之一。 K8s 網絡模型本身對某些特定的網絡功能有著一定的要求,因此,業界已經有了不少的網絡方案來滿足特定的環境和要求。 CNI 意為容器網絡的 API 接口,為了讓用戶在容器創建或銷毀時都能夠更容易地配置容器網絡。在本文中,作者將帶領大家理解典型網絡插件地工作原理、掌握 CNI 插件的使用。

一、CNI 是什麼

首先我們介紹一下什麼是 CNI,它的全稱是 Container Network Interface,即容器網絡的 API 接口。

它是 K8s 中標準的一個調用網絡實現的接口。 Kubelet 通過這個標準的 API 來調用不同的網絡插件以實現不同的網絡配置方式,實現了這個接口的就是 CNI 插件,它實現了一系列的 CNI API 接口。常見的 CNI 插件包括 Calico、flannel、Terway、Weave Net 以及 Contiv。

二、Kubernetes 中如何使用 CNI

K8s 通過 CNI 配置文件來決定使用什麼 CNI。

基本的使用方法為:

  1. 首先在每個結點上配置 CNI 配置文件(/etc/cni/net.d/xxnet.conf),其中 xxnet.conf 是某一個網絡配置文件的名稱;

  2. 安裝 CNI 配置文件中所對應的二進制插件;

  3. 在這個節點上創建 Pod 之後,Kubelet 就會根據 CNI 配置文件執行前兩步所安裝的 CNI 插件;

  4. 上步執行完之後,Pod 的網絡就配置完成了。

具體的流程如下圖所示:

從零開始入門 K8s:理解 CNI 和 CNI 插件 1

在集群裡面創建一個 Pod 的時候,首先會通過 apiserver 將 Pod 的配置寫入。 apiserver 的一些管控組件(比如 Scheduler)會調度到某個具體的節點上去。 Kubelet 監聽到這個 Pod 的創建之後,會在本地進行一些創建的操作。當執行到創建網絡這一步驟時,它首先會讀取剛才我們所說的配置目錄中的配置文件,配置文件裡面會聲明所使用的是哪一個插件,然後去執行具體的CNI 插件的二進製文件,再由CNI 插件進入Pod 的網絡空間去配置Pod 的網絡。配置完成之後,Kuberlet 也就完成了整個 Pod 的創建過程,這個 Pod 就在線了。

大家可能會覺得上述流程有很多步(比如要對 CNI 配置文件進行配置、安裝二進制插件等等),看起來比較複雜。

但如果我們只是作為一個用戶去使用 CNI 插件的話就比較簡單,因為很多 CNI 插件都已提供了一鍵安裝的能力。以我們常用的 Flannel 為例,如下圖所示:只需要我們使用 kubectl apply Flannel 的一個 Deploying 模板,它就能自動地將配置、二進製文件安裝到每一個節點上去。

從零開始入門 K8s:理解 CNI 和 CNI 插件 2

安裝完之後,整個集群的 CNI 插件就安裝完成了。

因此,如果我們只是去使用 CNI 插件的話,那麼其實很多 CNI 插件已經提供了一鍵安裝的腳本,無需大家關心 Kubernetes 內部是如何配置的以及如何調用 API 的。

三、哪個 CNI 插件適合我

社區有很多的 CNI 插件,比如 Calico, flannel, Terway 等等。那麼在一個真正具體的生產環境中,我們要選擇哪一個 CNI 插件呢?

這就要從 CNI 的幾種實現模式說起。我們需要根據不同的場景選擇不同的實現模式,再去選擇對應的具體某一個插件。

通常來說,CNI 插件可以分為三種:Overlay、路由及 Underlay。

從零開始入門 K8s:理解 CNI 和 CNI 插件 3

  • Overlay 模式的典型特徵是容器獨立於主機的IP 段,這個IP 段進行跨主機網絡通信時是通過在主機之間創建隧道的方式,將整個容器網段的包全都封裝成底層的物理網絡中主機之間的包。該方式的好處在於它不依賴於底層網絡;

  • 路由模式中主機和容器也分屬不同的網段,它與 Overlay 模式的主要區別在於它的跨主機通信是通過路由打通,無需在不同主機之間做一個隧道封包。但路由打通就需要部分依賴於底層網絡,比如說要求底層網絡有二層可達的一個能力;

  • Underlay 模式中容器和宿主機位於同一層網絡,兩者擁有相同的地位。容器之間網絡的打通主要依靠於底層網絡。因此該模式是強依賴於底層能力的。

了解了以上三種常用的實現模式之後,再根據自己的環境、需求判斷可由哪一種模式進行實現,再在對應的模式中去找 CNI 插件。不過社區中有那麼多插件,它們又都屬於哪種模式?如何進行選擇呢?怎麼挑選適合自己的呢?我們可以從以下 3 個方面來考慮。

從零開始入門 K8s:理解 CNI 和 CNI 插件 4

1. 環境限制

不同環境中所支持的底層能力是不同的。

  • 虛擬化環境(例如OpenStack)中的網絡限制較多,比如不允許機器之間直接通過二層協議訪問,必須要帶有IP 地址這種三層的才能去做轉發,限制某一個機器只能使用某些IP等。在這種被做了強限制的底層網絡中,只能去選擇 Overlay 的插件,常見的有 Flannel-vxlan, Calico-ipip, Weave 等等;

  • 物理機環境中底層網絡的限制較少,比如說我們在同一個交換機下面直接做一個二層的通信。對於這種集群環境,我們可以選擇 Underlay 或者路由模式的插件。 Underlay 意味著我們可以直接在一個物理機上插多個網卡或者是在一些網卡上做硬件虛擬化;路由模式就是依賴於 Linux 的路由協議做一個打通。這樣就避免了像 vxlan 的封包方式導致的性能降低。這種環境下我們可選的插件包括 clico-bgp, flannel-hostgw, sriov 等等;

  • 公有云環境也是虛擬化,因此底層限制也會較多。但每個公有云都會考慮適配容器,提升容器的性能,因此每家公有云可能都提供了一些 API 去配置一些額外的網卡或者路由這種能力。在公有云上,我們要盡量選擇公有云廠商提供的 CNI 插件以達到兼容性和性能上的最優。比如 Aliyun 就提供了一個高性能的 Terway 插件。

環境限制考慮完之後,我們心中應該都有一些選擇了,知道哪些能用、哪些不能用。在這個基礎上,我們再去考慮功能上的需求。

2. 功能需求

  • 安全需求;

K8s 支持 NetworkPolicy,就是說我們可以通過 NetworkPolicy 的一些規則去支持“Pod 之間是否可以訪問”這類策略。但不是每個 CNI 插件都支持 NetworkPolicy 的聲明,如果大家有這個需求,可以選擇支持 NetworkPolicy 的一些插件,比如 Calico, Weave 等等。

  • 是否需要集群外的資源與集群內的資源互聯互通;

大家的應用最初都是在虛擬機或者物理機上,容器化之後,應用無法一下就完成遷移,因此就需要傳統的虛擬機或者物理機能跟容器的 IP 地址互通。為了實現這種互通,就需要兩者之間有一些打通的方式或者直接位於同一層。此時可以選擇 Underlay 的網絡,比如 sriov 這種就是 Pod 和以前的虛擬機或者物理機在同一層。我們也可以使用 calico-bgp,此時它們雖然不在同一網段,但可以通過它去跟原有的路由器做一些 BGP 路由的一個發布,這樣也可以打通虛擬機與容器。

  • K8s 的服務發現與負載均衡的能力

K8s 的服務發現與負載均衡就是我們前面所介紹的 K8s 的 Service,但並不是所有的 CNI 插件都能實現這兩種能力。比如很多Underlay 模式的插件,在Pod 中的網卡是直接用的Underlay 的硬件,或者通過硬件虛擬化插到容器中的,這個時候它的流量無法走到宿主機所在的命名空間,因此也無法應用kube-proxy 在宿主機配置的規則。

這種情況下,插件就無法訪問到 K8s 的服務發現。因此大家如果需要服務發現與負載均衡,在選擇 Underlay 的插件時就需要注意它們是否支持這兩種能力。

經過功能需求的過濾之後,能選的插件就很少了。經過環境限制和功能需求的過濾之後,如果還剩下 3、4 種插件,可以再來考慮性能需求。

3. 性能需求

我們可以從 Pod 的創建速度和 Pod 的網絡性能來衡量不同插件的性能。

  • Pod 的創建速度

當我們創建一組 Pod 時,比如業務高峰來了,需要緊急擴容,這時比如說我們擴容了 1000 個 Pod,就需要 CNI 插件創建並配置 1000 個網絡資源。 Overlay 和路由模式在這種情況下的創建速度是很快的,因為它是在機器裡面又做了虛擬化,所以只需要調用內核接口就可以完成這些操作。但對於 Underlay 模式,由於需要創建一些底層的網絡資源,所以整個 Pod 的創建速度相對會慢一些。因此對於經常需要緊急擴容或者創建大批量的 Pod 這些場景,我們應該盡量選擇 Overlay 或者路由模式的網絡插件。

  • Pod 的網絡性能

主要表現在兩個 Pod 之間的網絡轉發、網絡帶寬、PPS 延遲等這些性能指標上。 Overlay 模式的性能較差,因為它在節點上又做了一層虛擬化,還需要去封包,封包又會帶來一些包頭的損失、CPU 的消耗等,如果大家對網絡性能的要求比較高,比如說機器學習、大數據這些場景就不適合使用Overlay 模式。這種情形下我們通常選擇 Underlay 或者路由模式的 CNI 插件。

相信大家通過這三步的挑選之後都能找到適合自己的網絡插件。

四、如何開發自己的 CNI 插件

有時社區的插件無法滿足自己的需求,比如在阿里雲上只能使用vxlan 這種Overlay 的插件,而Overlay 插件的性能相對較差,無法滿足阿里雲上的一些業務需求,所以阿里雲上開發了一個Terway 的插件。

如果我們自己的環境比較特殊,在社區裡面又找不到合適的網絡插件,此時可以開發一個自己的 CNI 插件。

CNI 插件的實現通常包含兩個部分:

  1. 一個二進制的 CNI 插件去配置 Pod 網卡和 IP 地址。這一步配置完成之後相當於給 Pod 上插上了一條網線,就是說它已經有自己的 IP、有自己的網卡了;

  2. 一個 Daemon 進程去管理 Pod 之間的網絡打通。這一步相當於說將 Pod 真正連上網絡,讓 Pod 之間能夠互相通信。

給 Pod 插上網線

那麼如何實現第一步,給 Pod 插上網線呢?通常是這樣一個步驟:

從零開始入門 K8s:理解 CNI 和 CNI 插件 5

1. 給 Pod 準備一個網卡

通常我們會用一個 “veth” 這種虛擬網卡,一端放到 Pod 的網絡空間,一端放到主機的網絡空間,這樣就實現了 Pod 與主機這兩個命名空間的打通。

2. 給 Pod 分配 IP 地址

這個 IP 地址有一個要求,我們在之前介紹網絡的時候也有提到,就是說這個 IP 地址在集群裡需要是唯一的。如何保障集群裡面給 Pod 分配的是個唯一的 IP 地址呢?

一般來說我們在創建整個集群的時候會指定 Pod 的一個大網段,按照每個節點去分配一個 Node 網段。比如說上圖右側創建的是一個 172.16 的網段,我們再按照每個節點去分配一個 /24 的段,這樣就能保障每個節點上的地址是互不衝突的。然後每個Pod 再從一個具體的節點上的網段中再去順序分配具體的IP 地址,比如Pod1 分配到了172.16.0.1,Pod2 分配到了172.16.0.2,這樣就實現了在節點裡面IP 地址分配的不衝突,並且不同的Node 又分屬不同的網段,因此不會衝突。

這樣就給 Pod 分配了集群裡面一個唯一的 IP 地址。

3. 配置 Pod 的 IP 和路由

  • 第一步,將分配到的 IP 地址配置給 Pod 的虛擬網卡;

  • 第二步,在Pod 的網卡上配置集群網段的路由,令訪問的流量都走到對應的Pod 網卡上去,並且也會配置默認路由的網段到這個網卡上,也就是說走公網的流量也會走到這個網卡上進行路由;

  • 最後在宿主機上配置到 Pod 的 IP 地址的路由,指向到宿主機對端 veth1 這個虛擬網卡上。這樣實現的是從 Pod 能夠到宿主機上進行路由出去的,同時也實現了在宿主機上訪問到 Pod 的 IP 地址也能路由到對應的 Pod 的網卡所對應的對端上去。

給 Pod 連上網絡

剛才我們是給 Pod 插上網線,也就是給它配了 IP 地址以及路由表。那怎麼打通 Pod 之間的通信呢?也就是讓每一個 Pod 的 IP 地址在集群裡面都能被訪問到。

一般我們是在 CNI Daemon 進程中去做這些網絡打通的事情。通常來說是這樣一個步驟:

  • 首先 CNI 在每個節點上運行的 Daemon 進程會學習到集群所有 Pod 的 IP 地址及其所在節點信息。學習的方式通常是通過監聽 K8s APIServer,拿到現有 Pod 的 IP 地址以及節點,並且新的節點和新的 Pod 的創建的時候也能通知到每個 Daemon;

  • 拿到 Pod 以及 Node 的相關信息之後,再去配置網絡進行打通。

    • 首先 Daemon 會創建到整個集群所有節點的通道。這裡的通道是個抽象概念,具體實現一般是通過 Overlay 隧道、阿里雲上的 VPC 路由表、或者是自己機房裡的 BGP 路由完成的;

    • 第二步是將所有 Pod 的 IP 地址跟上一步創建的通道關聯起來。關聯也是個抽象概念,具體的實現通常是通過 Linux 路由、fdb 轉發表或者OVS 流表等完成的。 Linux 路由可以設定某一個 IP 地址路由到哪個節點上去。 fdb 轉發表是 forwarding database 的縮寫,就是把某個 Pod 的 IP 轉發到某一個節點的隧道端點上去(Overlay 網絡)。 OVS 流表是由 Open vSwitch 實現的,它可以把 Pod 的 IP 轉發到對應的節點上。

六、本文總結

本文的主要內容就到此為止了,這里為大家簡單總結一下:

  1. 在我們自己的環境中搭建一個 K8s 集群,應當如何選擇最適合自己的網絡插件?

  2. 當社區網絡插件不能滿足時,如何開發自己的網絡插件?

相關閱讀:

從零開始入門 K8s:Kubernetes 網絡模型進階

從零開始入門 K8s:Kubernetes API 編程範式

從零開始入門 K8s:Kubernetes API 編程利器 Operator 和 Operator Framework

從零開始入門 K8s:有狀態應用編排 – StatefulSet

從零開始入門 K8s:Kubernetes 存儲架構及插件使用

從零開始入門 K8s:GPU 管理和 Device Plugin 工作機制

從零開始入門 K8s:調度器的調度流程和算法介紹

從零開始入門 K8s:Kubernetes 調度和資源管理

從零開始入門 K8s:etcd 性能優化實踐

從零開始入門 K8s:手把手帶你理解 etcd

從零開始入門 K8s:深入剖析 Linux 容器

從零開始入門 K8s:Kubernetes 中的服務發現與負載均衡

從零開始入門 K8s:Kubernetes 網絡概念及策略控制

從零開始入門 K8s:監控與日誌的可觀測性

從零開始入門 K8s:應用存儲和持久化數據卷:存儲快照與拓撲調度

從零開始入門 K8s:應用存儲和持久化數據卷的核心知識

從零開始入門 K8s:應用配置管理

從零開始入門 K8s:應用編排與管理:Job & DaemonSet

從零開始入門 K8s:應用編排與管理

從零開始入門 K8s:K8s 的應用編排與管理

從零開始入門 K8s:詳解 Pod 及容器設計模式

從零開始入門 K8s:詳解 K8s 容器基本概念

從零開始入門 K8s:詳解 K8s 核心概念