久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

PouchContainer CRI的設計與實現方法是怎樣的

159次閱讀
沒有評論

共計 6530 個字符,預計需要花費 17 分鐘才能閱讀完成。

PouchContainer CRI 的設計與實現方法是怎樣的,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面丸趣 TV 小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

1. CRI 簡介

在每個 Kubernetes 節點的最底層都有一個程序負責具體的容器創建刪除工作,Kubernetes 會對其接口進行調用,從而完成容器的編排調度。我們將這一層軟件稱之為容器運行時(Container Runtime),大名鼎鼎的 Docker 就是其中的代表。

當然,容器運行時并非只有 Docker 一種,包括 CoreOS 的 rkt,hyper.sh 的 runV,Google 的 gvisor,以及本文的主角 PouchContainer,都包含了完整的容器操作,能夠用來創建特性各異的容器。不同的容器運行時有著各自獨特的優點,能夠滿足不同用戶的需求,因此 Kubernetes 支持多種容器運行時勢在必行。

最初,Kubernetes 原生內置了對 Docker 的調用接口,之后社區又在 Kubernetes 1.3 中集成了 rkt 的接口,使其成為了 Docker 以外,另一個可選的容器運行時。不過,此時不論是對于 Docker 還是對于 rkt 的調用都是和 Kubernetes 的核心代碼強耦合的,這無疑會帶來如下兩方面的問題:

新興的容器運行時,例如 PouchContainer 這樣的后起之秀,加入 Kubernetes 生態難度頗大。容器運行時的開發者必須對于 Kubernetes 的代碼 (至少是 Kubelet) 有著非常深入的理解,才能順利完成兩者之間的對接。

Kubernetes 的代碼將更加難以維護,這也體現在兩方面:(1)將各種容器運行時的調用接口全部硬編碼進 Kubernetes,會讓 Kubernetes 的核心代碼變得臃腫不堪,(2)容器運行時接口細微的改動都會引發 Kubernetes 核心代碼的修改,增加 Kubernetes 的不穩定性

為了解決這些問題,社區在 Kubernetes 1.5 引入了 CRI(Container Runtime Interface),通過定義一組容器運行時的公共接口將 Kubernetes 對于各種容器運行時的調用接口屏蔽至核心代碼以外,Kubernetes 核心代碼只對該抽象接口層進行調用。而對于各種容器運行時,只要滿足了 CRI 中定義的各個接口就能順利接入 Kubernetes,成為其中的一個容器運行時選項。方案雖然簡單,但是對于 Kubernetes 社區維護者和容器運行時開發者來說,都是一種解放。

2. CRI 設計概述

如上圖所示,左邊的 Kubelet 是 Kubernetes 集群的 Node Agent,它會對本節點上容器的狀態進行監控,保證它們都按照預期狀態運行。為了實現這一目標,Kubelet 會不斷調用相關的 CRI 接口來對容器進行同步。

CRI shim 則可以認為是一個接口轉換層,它會將 CRI 接口,轉換成對應底層容器運行時的接口,并調用執行,返回結果。對于有的容器運行時,CRI shim 是作為一個獨立的進程存在的,例如當選用 Docker 為 Kubernetes 的容器運行時,Kubelet 初始化時,會附帶啟動一個 Docker shim 進程,它就是 Docker 的 CRI shime。而對于 PouchContainer,它的 CRI shim 則是內嵌在 Pouchd 中的,我們將其稱之為 CRI manager。關于這一點,我們會在下一節討論 PouchContainer 相關架構時再詳細敘述。

CRI 本質上是一套 gRPC 接口,Kubelet 內置了一個 gRPC Client,CRI shim 中則內置了一個 gRPC Server。Kubelet 每一次對 CRI 接口的調用,都將轉換為 gRPC 請求由 gRPC Client 發送給 CRI shim 中的 gRPC Server。Server 調用底層的容器運行時對請求進行處理并返回結果,由此完成一次 CRI 接口調用。

CRI 定義的 gRPC 接口可劃分兩類,ImageService 和 RuntimeService:其中 ImageService 負責管理容器的鏡像,而 RuntimeService 則負責對容器生命周期進行管理以及與容器進行交互(exec/attach/port-forward)。

3. CRI Manager 架構設計

在 PouchContainer 的整個架構體系中,CRI Manager 實現了 CRI 定義的全部接口,擔任了 PouchContainer 中 CRI shim 的角色。當 Kubelet 調用一個 CRI 接口時,請求就會通過 Kubelet 的 gRPC Client 發送到上圖的 gRPC Server 中。Server 會對請求進行解析,并調用 CRI Manager 相應的方法進行處理。

我們先通過一個例子來簡單了解一下各個模塊的功能。例如,當到達的請求為創建一個 Pod,那么 CRI Manager 會先將獲取到的 CRI 格式的配置轉換成符合 PouchContainer 接口要求的格式,調用 Image Manager 拉取所需的鏡像,再調用 Container Manager 創建所需的容器,并調用 CNI Manager,利用 CNI 插件對 Pod 的網絡進行配置。最后,Stream Server 會對交互類型的 CRI 請求,例如 exec/attach/portforward 進行處理。

值得注意的是,CNI Manager 和 Stream Server 是 CRI Manager 的子模塊,而 CRI Manager,Container Manager 以及 Image Manager 是三個平等的模塊,它們都位于同一個二進制文件 Pouchd 中,因此它們之間的調用都是最為直接的函數調用,并不存在例如 Docker shim 與 Docker 交互時,所需要的遠程調用開銷。下面,我們將進入 CRI Manager 內部,對其中重要功能的實現做更為深入的理解。

4. Pod 模型的實現

在 Kubernetes 的世界里,Pod 是最小的調度部署單元。簡單地說,一個 Pod 就是由一些關聯較為緊密的容器構成的容器組。作為一個整體,這些“親密”的容器之間會共享一些東西,從而讓它們之間的交互更為高效。例如,對于網絡,同一個 Pod 中的容器會共享同一個 IP 地址和端口空間,從而使它們能直接通過 localhost 互相訪問。對于存儲,Pod 中定義的 volume 會掛載到其中的每個容器中,從而讓每個容器都能對其進行訪問。

事實上,只要一組容器之間共享某些 Linux Namespace 以及掛載相同的 volume 就能實現上述的所有特性。下面,我們就通過創建一個具體的 Pod 來分析 PouchContainer 中的 CRI Manager 是如何實現 Pod 模型的:

1、當 Kubelet 需要新建一個 Pod 時,首先會對 RunPodSandbox 這一 CRI 接口進行調用,而 CRI Manager 對該接口的實現是創建一個我們稱之為 infra container 的特殊容器。從容器實現的角度來看,它并不特殊,無非是調用 Container Manager,創建一個鏡像為 pause-amd64:3.0 的普通容器。但是從整個 Pod 容器組的角度來看,它是有著特殊作用的,正是它將自己的 Linux Namespace 貢獻出來,作為上文所說的各容器共享的 Linux Namespace,將容器組中的所有容器聯結到一起。它更像是一個載體,承載了 Pod 中所有其他的容器,為它們的運行提供基礎設施。而一般我們也用 infra container 代表一個 Pod。

2、在 infra container 創建完成之后,Kubelet 會對 Pod 容器組中的其他容器進行創建。每創建一個容器就是連續調用 CreateContainer 和 StartContainer 這兩個 CRI 接口。對于 CreateContainer,CRI Manager 僅僅只是將 CRI 格式的容器配置轉換為 PouchContainer 格式的容器配置,再將其傳遞給 Container Manager,由其完成具體的容器創建工作。這里我們唯一需要關心的問題是,該容器如何加入上文中提到的 infra container 的 Linux Namespace。其實真正的實現非常簡單,在 Container Manager 的容器配置參數中有 PidMode, IpcMode 以及 NetworkMode 三個參數,分別用于配置容器的 Pid Namespace,Ipc Namespace 和 Network Namespace。籠統地說,對于容器的 Namespace 的配置一般都有兩種模式:None 模式,即創建該容器自己獨有的 Namespace,另一種即為 Container 模式,即加入另一個容器的 Namespace。顯然,我們只需要將上述三個參數配置為 Container 模式,加入 infra container 的 Namespace 即可。具體是如何加入的,CRI Manager 并不需要關心。對于 StartContainer,CRI Manager 僅僅只是做了一層轉發,從請求中獲取容器 ID 并調用 Container Manager 的 Start 接口啟動容器。

3、最后,Kubelet 會不斷調用 ListPodSandbox 和 ListContainers 這兩個 CRI 接口來獲取本節點上容器的運行狀態。其中 ListPodSandbox 羅列的其實就是各個 infra container 的狀態,而 ListContainer 羅列的是除了 infra container 以外其他容器的狀態。現在問題是,對于 Container Manager 來說,infra container 和其他 container 并不存在任何區別。那么 CRI Manager 是如何對這些容器進行區分的呢? 事實上,CRI Manager 在創建容器時,會在已有容器配置的基礎之上,額外增加一個 label,標志該容器的類型。從而在實現 ListPodSandbox 和 ListContainers 接口的時候,以該 label 的值作為條件,就能對不同類型的容器進行過濾。

綜上,對于 Pod 的創建,我們可以概述為先創建 infra container,再創建 pod 中的其他容器,并讓它們加入 infra container 的 Linux Namespace。

5. Pod 網絡配置

因為 Pod 中所有的容器都是共享 Network Namespace 的,因此我們只需要在創建 infra container 的時候,對它的 Network Namespace 進行配置即可。

在 Kubernetes 生態體系中容器的網絡功能都是由 CNI 實現的。和 CRI 類似,CNI 也是一套標準接口,各種網絡方案只要實現了該接口就能無縫接入 Kubernetes。CRI Manager 中的 CNI Manager 就是對 CNI 的簡單封裝。它在初始化的過程中會加載目錄 /etc/cni/net.d 下的配置文件,如下所示:

其中指定了配置 Pod 網絡會使用到的 CNI 插件,例如上文中的 bridge,以及一些網絡配置信息,例如本節點 Pod 所屬的子網范圍和路由配置。

下面我們就通過具體的步驟來展示如何將一個 Pod 加入 CNI 網絡:

1、當調用 container manager 創建 infra container 時,將 NetworkMode 設置為 None 模式,表示創建一個該 infra container 獨有的 Network Namespace 且不做任何配置。

2、根據 infra container 對應的 PID,獲取其對應的 Network Namespace 路徑 /proc/{pid}/ns/net。

3、調用 CNI Manager 的 SetUpPodNetwork 方法,核心參數為步驟二中獲取的 Network Namespace 路徑。該方法做的工作就是調用 CNI Manager 初始化時指定的 CNI 插件,例如上文中的 bridge,對參數中指定的 Network Namespace 進行配置,包括創建各種網絡設備,進行各種網絡配置,將該 Network Namespace 加入插件對應的 CNI 網絡中。

對于大多數 Pod,網絡配置都是按照上述步驟操作的,大部分的工作將由 CNI 以及對應的 CNI 插件替我們完成。但是對于一些特殊的 Pod,它們會將自己的網絡模式設置為 Host,即和宿主機共享 Network Namespace。這時,我們只需要在調用 Container Manager 創建 infra container 時,將 NetworkMode 設置為 Host,并且跳過 CNI Manager 的配置即可。

對于 Pod 中其他的容器,不論 Pod 是處于 Host 網絡模式,還是擁有獨立的 Network Namespace,都只需要在調用 Container Manager 創建容器時,將 NetworkMode 配置為 Container 模式,加入 infra container 所在的 Network Namespace 即可。

6. IO 流處理

Kubernetes 提供了例如 kubectl exec/attach/port-forward 這樣的功能來實現用戶和某個具體的 Pod 或者容器的直接交互。如下所示:

可以看到,exec 一個 Pod 等效于 ssh 登錄到該容器中。下面,我們根據 kubectl exec 的執行流來分析 Kubernetes 中對于 IO 請求的處理,以及 CRI Manager 在其中扮演的角色。

如上圖所示,執行一條 kubectl exec 命令的步驟如下:

1、kubectl exec 命令的本質其實是對 Kubernetes 集群中某個容器執行 exec 命令,并將由此產生的 IO 流轉發到用戶的手中。所以請求將首先層層轉發到達該容器所在節點的 Kubelet,Kubelet 再根據配置調用 CRI 中的 Exec 接口。請求的配置參數如下:

2、令人感到意外的是,CRI Manager 的 Exec 方法并沒有直接調用 Container Manager,對目標容器執行 exec 命令,而是轉而調用了其內置的 Stream Server 的 GetExec 方法。

3、Stream Server 的 GetExec 方法所做的工作是將該 exec 請求的內容保存到了上圖所示的 Request Cache 中,并返回一個 token,利用該 token,我們可以重新從 Request Cache 中找回對應的 exec 請求。最后,將這個 token 寫入一個 URL 中,并作為執行結果層層返回到 ApiServer。

4、ApiServer 利用返回的 URL 直接對目標容器所在節點的 Stream Server 發起一個 http 請求,請求的頭部包含了 Upgrade 字段,要求將 http 協議升級為 websocket 或者 SPDY 這樣的 streaming protocol,用于支持多條 IO 流的處理,本文我們以 SPDY 為例。

5、Stream Server 對 ApiServer 發送的請求進行處理,首先根據 URL 中的 token,從 Request Cache 中獲取之前保存的 exec 請求配置。之后,回復該 http 請求,同意將協議升級為 SPDY,并根據 exec 請求的配置等待 ApiServer 創建指定數量的 stream,分別對應標準輸入 Stdin,標準輸出 Stdout,標準錯誤輸出 Stderr。

6、待 Stream Server 獲取指定數量的 Stream 之后,依次調用 Container Manager 的 CreateExec 和 startExec 方法,對目標容器執行 exec 操作并將 IO 流轉發至對應的各個 stream 中。

7、最后,ApiServer 將各個 stream 的數據轉發至用戶,開啟用戶與目標容器的 IO 交互。

事實上,在引入 CRI 之前,Kubernetes 對于 IO 的處理方式和我們的預期是一致的,Kubelet 會直接對目標容器執行 exec 命令,并將 IO 流轉發回 ApiServer。但是這樣會讓 Kubelet 承載過大的壓力,所有的 IO 流都需要經過它的轉發,這顯然是不必要的。因此上述的處理雖然初看較為復雜,但是有效地緩解了 Kubelet 的壓力,并且也讓 IO 的處理更為高效。

看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注丸趣 TV 行業資訊頻道,感謝您對丸趣 TV 的支持。

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-08-16發表,共計6530字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 东海县| 新化县| 玉环县| 遵义市| 富阳市| 五峰| 楚雄市| 滕州市| 开阳县| 景德镇市| 浮梁县| 荆门市| 文山县| 灵丘县| 克什克腾旗| 福清市| 桦南县| 兴隆县| 呈贡县| 汨罗市| 武夷山市| 珠海市| 瑞丽市| 江安县| 子洲县| 红原县| 焦作市| 莆田市| 河南省| 黄龙县| 马龙县| 兴城市| 奇台县| 叶城县| 务川| 会昌县| 宁武县| 九寨沟县| 信阳市| 贡觉县| 东兰县|