Kubernetes-三大開放介面-初見

ML李嘉圖發表於2022-02-15

容器執行時介面CRI

歷史

OCI出現是為了它的核心目標圍繞容器的格式和執行時制定一個開放的工業化標準,並推動這個標準,保持容器的靈活性和開放性,容器能執行在任何的硬體和系統上。容器不應該繫結到特定的客戶機或編排堆疊,不應該與任何特定的供應商緊密關聯,並且可以跨多種作業系統
官網上對 OCI 的介紹如下:

Established in June 2015 by Docker and other leaders in the container industry, the OCI currently contains two specifications: the Runtime Specification (runtime-spec) and the Image Specification (image-spec). The Runtime Specification outlines how to run a “filesystem bundle” that is unpacked on disk. At a high-level an OCI implementation would download an OCI Image then unpack that image into an OCI Runtime filesystem bundle. At this point the OCI Runtime Bundle would be run by an OCI Runtime.

OCI由docker以及其他容器行業領導者建立於2015年,目前主要有兩個標準:容器執行時標準(runtime-spec)和容器映象標準(image-spec)。

這兩個標準通過OCI runtime filesytem bundle的標準格式連線在一起,OCI映象可以通過工具轉換成bundle,然後 OCI 容器引擎能夠識別這個bundle來執行容器

文件主要做了兩個事情:

  • 建立映象的規則
  • 執行映象的規則

瞭解了OCI,以及docker在相容OCI標準架構的調整後, 迎來我們的重點 CRI,CRI是kubernetes推出的一個標準,推出標準可見其在容器編排領域的地位。

簡介

容器執行時介面(Container Runtime Interface),簡稱 CRI。CRI 中定義了 容器映象 的服務的介面,因為容器執行時與映象的生命週期是彼此隔離的,因此需要定義兩個服務。該介面使用 Protocol Buffer,基於 gRPC,在 Kubernetes v1.10 + 版本中是在 pkg/kubelet/apis/cri/runtime/v1alpha2api.proto 中定義的。

容器執行時(Container Runtime):顧名思義就是容器從拉取映象到啟動執行再到中止的整個生命週期。 其中最知名的就是Docker了。

每個容器執行時都有特點,因此不少使用者希望Kubernetes能夠支援更多的容器執行時。為了更具擴充套件性,kubernetes引入了容器執行時外掛API,即 Container Runtime Interface,簡稱CRI。

  • Protocol Buffers API(一種更高效的類似json的資料格式)包含兩個gRPC服務:
    • ImageService和RuntimeService。
      • ImageService提供了從倉庫拉取映象、檢視和移除映象的功能。
      • RuntimeService負責Pod和容器的生命週期管理,以及與容器的互動 (exec/attach/port-forward)。

架構

Container Runtime 實現了 CRI gRPC Server,包括 RuntimeServiceImageService。該 gRPC Server 需要監聽本地的 Unix socket,而 kubelet 則作為 gRPC Client 執行。

啟用 CRI

除非整合了 rktnetes,否則 CRI 都是被預設啟用了,從 Kubernetes 1.7 版本開始,舊的預整合的 docker CRI 已經被移除。

要想啟用 CRI 只需要在 kubelet 的啟動引數重傳入此引數:--container-runtime-endpoint 遠端執行時服務的端點。當前 Linux 上支援 unix socket,windows 上支援 tcp。

Kubelet拉起一個容器的過程:

  1. Kubelet通過CRI介面(gRPC)呼叫docker-shim,請求建立一個容器這一步中,kubelet可以視作一個簡單的CRI Client,而docker-shim就是接收請求的Server,注意的是docker-shim是內嵌在Kubelet中的

  2. docker-shim收到請求後,轉化成Docker Daemon能聽懂的請求,發到Docker Daemon上請求建立一個容器

  3. Docker Daemon請求containerd建立一個容器

  4. containerd收到請求後建立一個containerd-shim程式,通過containerd-shim操作容器,容器程式需要一個父程式來做諸如收集狀態, 維持stdin等fd開啟等工作

  5. containerd-shim在呼叫runC來啟動容器

  6. runC 啟動完容器後本身會直接退出,containerd-shim則會成為容器程式的父程式,負責收集容器程式的狀態,上報給containerd。

通過上面kubelet建立容器的流程, 我們可以看到kubelet通過CRI的標準來與外部容器執行時進行互動。

kubernetes 早期版本1.5之前內建了docker和rkt,也就是支援兩種執行時, 這個時候如果使用者想自定義執行時就比較痛苦了,需要修改kubelet原始碼。

同時不同的容器執行時各有所長,隨著k8s在容器編排領域裡面老大的地位,許多使用者希望kubernetes支援更多的容器執行時,滿足不同使用者,不同環境的使用。

於是從kubernetes1.5開始增加了CRI介面, 有了CRI介面無需修改kubelet原始碼就可以支援更多的容器執行時,

與此同時內建的docker和rtk逐漸從kubernetes原始碼中移除,到kubernetes1.11版本Kubelet內建的rkt程式碼刪除,CNI的實現遷移到dockers-shim之內,除了docker之外,其他的容器執行時都通過CRI接入。

外部的容器執行時一般稱為CRI shim,它除了實現CRI介面外,也要負責為容器配置網路,即CNI,有了CNI可以支援社群內的眾多網路外掛。

CRI 介面

CRI主要定義兩個介面, ImageService和RuntimeService,如下圖:

ImageService:負責映象的生命管理週期

  • 查詢映象列表
  • 拉取映象到本地
  • 查詢映象狀態
  • 刪除本地映象
  • 查詢映象佔用空間

RuntimeService:負責管理Pod和容器的生命週期

  • PodSandbox 的管理介面
    PodSandbox是對kubernete Pod的抽象,用來給容器提供一個隔離的環境(比如掛載到相同的cgroup下面)並提供網路等共享的名稱空間。PodSandbox通常對應到一個Pause容器或者一臺虛擬機器。
  • Container 的管理介面
    在指定的 PodSandbox 中建立、啟動、停止和刪除容器。
  • Streaming API介面
    包括Exec、Attach和PortForward 等三個和容器進行資料互動的介面,這三個介面返回的是執行時Streaming Server的URL,而不是直接跟容器互動。
  • 狀態介面
    包括查詢API版本和查詢執行時狀態。

容器生態可以下面的三層抽象:

Orchestration API -> Container API -> Kernel API

  • Orchestration API: kubernetes API標準就是這層的標準,無可非議
  • Container API: 標準就是CRI
  • Kernel API: 標準就是OCI

當前支援的 CRI 後端

我們最初在使用 Kubernetes 時通常會預設使用 Docker 作為容器執行時,其實從 Kubernetes 1.5 開始已經支援 CRI,通過 CRI 介面可以指定使用其它容器執行時作為 Pod 的後端,目前支援 CRI 的後端有:

  • cri-o:cri-o 是 Kubernetes 的 CRI 標準的實現,並且允許 Kubernetes 間接使用 OCI 相容的容器執行時,可以把 cri-o 看成 Kubernetes 使用 OCI 相容的容器執行時的中間層。
  • cri-containerd:基於 Containerd 的 Kubernetes CRI 實現
  • rkt:由 CoreOS 主推的用來跟 docker 抗衡的容器執行時
  • frakti:基於 hypervisor 的 CRI
  • docker:Kuberentes 最初就開始支援的容器執行時,目前還沒完全從 kubelet 中解耦,Docker 公司同時推廣了 OCI 標準

CRI 是由 SIG-Node 來維護的。

容器網路介面CNI

簡介

容器網路介面(Container Network Interface),簡稱 CNI,是 CNCF 旗下的一個專案,由一組用於配置 Linux 容器的網路介面的規範和庫組成,同時還包含了一些外掛。CNI 僅關心容器建立時的網路分配,和當容器被刪除時釋放網路資源。有關詳情請檢視 GitHub

Kubernetes 原始碼的 vendor/github.com/containernetworking/cni/libcni 目錄中已經包含了 CNI 的程式碼,也就是說 Kubernetes 中已經內建了 CNI。

為什麼出現?

不管是 Docker 還是 Kubernetes,在網路方面目前都沒有一個完美的、終極的、普適性的解決方案,不同的使用者和企業因為各種原因會使用不同的網路方案。

目前存在網路方案 flannel、calico、openvswitch、weave、ipvlan等,而且以後一定會有其他的網路方案,這些方案介面和使用方法都不相同,而不同的容器平臺都需要網路功能,它們之間的適配如果沒有統一的標準,會有很大的工作量和重複勞動。

CNI 就是這樣一個標準,它旨在為容器平臺提供網路的標準化。不同的容器平臺(比如目前的 kubernetes、mesos 和 rkt)能夠通過相同的介面呼叫不同的網路元件。

CNI(Conteinre Network Interface) 是 Google 和 CoreOS 主導制定的容器網路標準,它本身並不是實現或者程式碼,可以理解成一個協議。這個標準是在 rkt 網路提議 的基礎上發展起來的,綜合考慮了靈活性、擴充套件性、ip 分配、多網路卡等因素。

這個協議連線了兩個元件:容器管理系統和網路外掛。它們之間通過 JSON 格式的檔案進行通訊,實現容器的網路功能。具體的事情都是外掛來實現的,包括:建立容器網路空間(network namespace)、把網路介面(interface)放到對應的網路空間、給網路介面分配 IP 等等。

關於網路,Docker 也提出了 CNM 標準,它要解決的問題和 CNI 是重合的,也就是說目前兩者是競爭關係。

目前 CNM 只能使用在 Docker 中,而 CNI 可以使用在任何容器執行時。CNM 主要用來實現 docker 自身的網路問題,也就是 docker network 子命令提供的功能。

介面定義

CNI 的介面中包括以下幾個方法:

type CNI interface {
    AddNetworkList (net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
    DelNetworkList (net *NetworkConfigList, rt *RuntimeConf) error
    AddNetwork (net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
    DelNetwork (net *NetworkConfig, rt *RuntimeConf) error
}

該介面只有四個方法,新增網路、刪除網路、新增網路列表、刪除網路列表。

官方網路外掛

所有的標準和協議都要有具體的實現,才能夠被大家使用。CNI 也不例外,目前官方在 github 上維護了同名的 CNI 程式碼庫,裡面已經有很多可以直接拿來使用的 CNI 外掛。

官方提供的外掛目前分成三類:main、meta 和 ipam。main 是主要的實現了某種特定網路功能的外掛;meta 本身並不會提供具體的網路功能,它會呼叫其他外掛,或者單純是為了測試;ipam 是分配 IP 地址的外掛。

ipam 並不提供某種網路功能,只是為了靈活性把它單獨抽象出來,這樣不同的網路外掛可以根據需求選擇 ipam,或者實現自己的 ipam。

這些外掛的功能說明如下:

  • main
    • loopback:這個外掛很簡單,負責生成 lo 網路卡,並配置上 127.0.0.1/8 地址
    • bridge:和 docker 預設的網路模型很像,把所有的容器連線到虛擬交換機上
    • macvlan:使用 macvlan 技術,從某個物理網路卡虛擬出多個虛擬網路卡,它們有獨立的 ip 和 mac 地址
    • ipvlan:和 macvlan 類似,區別是虛擬網路卡有著相同的 mac 地址
    • ptp:通過 veth pair 在容器和主機之間建立通道
  • meta
    • flannel:結合 bridge 外掛使用,根據 flannel 分配的網段資訊,呼叫 bridge 外掛,保證多主機情況下容器
  • ipam
    • host-local:基於本地檔案的 ip 分配和管理,把分配的 IP 地址儲存在檔案中
    • dhcp:從已經執行的 DHCP 伺服器中獲取 ip 地址

介面引數

網路外掛是獨立的可執行檔案,被上層的容器管理平臺呼叫。網路外掛只有兩件事情要做:把容器加入到網路以及把容器從網路中刪除。呼叫外掛的資料通過兩種方式傳遞:環境變數和標準輸入。一般外掛需要三種型別的資料:容器相關的資訊,比如 ns 的檔案、容器 id 等;網路配置的資訊,包括網段、閘道器、DNS 以及外掛額外的資訊等;還有就是 CNI 本身的資訊,比如 CNI 外掛的位置、新增網路還是刪除網路。

我們來看一下為容器新增網路是怎麼工作的,刪除網路和它過程一樣。

把容器加入到網路

呼叫外掛的時候,這些引數會通過環境變數進行傳遞:

  • CNI_COMMAND:要執行的操作,可以是 ADD(把容器加入到某個網路)、DEL(把容器從某個網路中刪除)
  • CNI_CONTAINERID:容器的 ID,比如 ipam 會把容器 ID 和分配的 IP 地址儲存下來。可選的引數,但是推薦傳遞過去。需要保證在管理平臺上是唯一的,如果容器被刪除後可以迴圈使用
  • CNI_NETNS:容器的 network namespace 檔案,訪問這個檔案可以在容器的網路 namespace 中操作
  • CNI_IFNAME:要配置的 interface 名字,比如 eth0
  • CNI_ARGS:額外的引數,是由分號;分割的鍵值對,比如 “FOO=BAR;hello=world”
  • CNI_PATH:CNI 二進位制查詢的路徑列表,多個路徑用分隔符 : 分隔

網路資訊主要通過標準輸入,作為 JSON 字串傳遞給外掛,必須的引數包括:

  • cniVersion:CNI 標準的版本號。因為 CNI 在演化過程中,不同的版本有不同的要求
  • name:網路的名字,在叢集中應該保持唯一
  • type:網路外掛的型別,也就是 CNI 可執行檔案的名稱
  • args:額外的資訊,型別為字典
  • ipMasq:是否在主機上為該網路配置 IP masquerade
  • ipam:IP 分配相關的資訊,型別為字典
  • dns:DNS 相關的資訊,型別為字典

外掛接到這些資料,從輸入和環境變數解析到需要的資訊,根據這些資訊執行程式邏輯,然後把結果返回給呼叫者,返回的結果中一般包括這些引數:

  • IPs assigned to the interface:網路介面被分配的 ip,可以是 IPv4、IPv6 或者都有
  • DNS 資訊:包含 nameservers、domain、search domains 和其他選項的字典

CNI 協議的內容還在不斷更新,請到官方文件獲取當前的資訊。

CNI 的特性

CNI 作為一個協議/標準,它有很強的擴充套件性和靈活性。如果使用者對某個外掛有額外的需求,可以通過輸入中的 args 和環境變數 CNI_ARGS 傳輸,然後在外掛中實現自定義的功能,這大大增加了它的擴充套件性;CNI 外掛把 main 和 ipam 分開,使用者可以自由組合它們,而且一個 CNI 外掛也可以直接呼叫另外一個 CNI 外掛,使用起來非常靈活。

如果要實現一個繼承性的 CNI 外掛也不復雜,可以編寫自己的 CNI 外掛,根據傳入的配置呼叫 main 中已經有的外掛,就能讓使用者自由選擇容器的網路。

在 kubernetes 中的使用

CNI 目前已經在 kubernetes 中開始使用,也是目前官方推薦的網路方案,具體的配置方法可以參考kubernetes 官方文件

kubernetes 使用了 CNI 網路外掛之後,工作過程是這樣的:

  • kubernetes 先建立 pause 容器生成對應的 network namespace
  • 呼叫網路 driver(因為配置的是 CNI,所以會呼叫 CNI 相關程式碼)
  • CNI driver 根據配置呼叫具體的 cni 外掛
  • cni 外掛給 pause 容器配置正確的網路
  • pod 中其他的容器都是用 pause 的網路

容器儲存介面CSI

背景

Kubernetes原生支援一些儲存型別的 PV,如 iSCSI、NFS、CephFS 等等,這些 in-tree 型別的儲存程式碼放在 Kubernetes 程式碼倉庫中。這裡帶來的問題是 Kubernetes 程式碼與三方儲存廠商的程式碼強耦合

  • 更改 in-tree 型別的儲存程式碼,使用者必須更新 Kubernetes元件,成本較高
  • in-tree 儲存程式碼中的 bug 會引發 Kubernetes元件不穩定
  • Kubernetes社群需要負責維護及測試 in-tree 型別的儲存功能
  • in-tree 儲存外掛享有與 Kubernetes核心元件同等的特權,存在安全隱患
  • 三方儲存開發者必須遵循 KKubernetes8s 社群的規則開發 in-tree 型別儲存程式碼

CSI 容器儲存介面標準的出現解決了上述問題,將三方儲存程式碼與 Kubernetes程式碼解耦,使得三方儲存廠商研發人員只需實現 CSI 介面(無需關注容器平臺是 Kubernetes還是 Swarm 等)。

簡介

容器儲存介面(Container Storage Interface),簡稱 CSI,CSI 試圖建立一個行業標準介面的規範,藉助 CSI 容器編排系統(CO)可以將任意儲存系統暴露給自己的容器工作負載。

csi 卷型別是一種 out-tree(即跟其它儲存外掛在同一個程式碼路徑下,隨 Kubernetes 的程式碼同時編譯的) 的 CSI 卷外掛,用於 Pod 與在同一節點上執行的外部 CSI 卷驅動程式互動。部署 CSI 相容卷驅動後,使用者可以使用 csi 作為卷型別來掛載驅動提供的儲存。

CSI 持久化卷支援是在 Kubernetes v1.9 中引入的,作為一個 alpha 特性,必須由叢集管理員明確啟用。換句話說,叢集管理員需要在 apiserver、controller-manager 和 kubelet 元件的 “--feature-gates =” 標誌中加上 “CSIPersistentVolume = true”。

CSI 持久化卷具有以下欄位可供使用者指定:

  • driver:一個字串值,指定要使用的卷驅動程式的名稱。必須少於 63 個字元,並以一個字元開頭。驅動程式名稱可以包含 “。”、“ - ”、“_” 或數字。
  • volumeHandle:一個字串值,唯一標識從 CSI 卷外掛的 CreateVolume 呼叫返回的卷名。隨後在卷驅動程式的所有後續呼叫中使用卷控制程式碼來引用該卷。
  • readOnly:一個可選的布林值,指示卷是否被髮布為只讀。預設是 false。

參考

https://www.jianshu.com/p/c7748893ab00

https://jimmysong.io/kubernetes-handbook/concepts/cni.html

https://www.kubernetes.org.cn/9127.html

相關文章