03.01 圖解kubernetes中Pod生命之初的坎坷歷程

kubernetes中的容器創建無疑是個複雜的過程,涉及內部各種組件的統一協作,還有對接外部的CRI運行時,本文嘗試初探一下容器創建流程中的各種細節,瞭解其各種組件協作流程,從而在後續出現問題的時候,也好能大概有點排查方向

1. 基礎築基

1.1 容器管理線程模型

圖解kubernetes中Pod生命之初的坎坷歷程

kubelet中的線程模型屬於master/worker模型,通過單master來監聽各種事件源,併為每個Pod創建一個goroutine來進行Pod業務邏輯的處理,master和worker之間通過一個狀態管道來進行通信

1.2 基於事件驅動的狀態最終一致性

圖解kubernetes中Pod生命之初的坎坷歷程

在通過yaml創建Pod之後,kubernetes會根據當前的事件和當前的Pod狀態,來不斷進行調整,從而達到最終目標狀態的一致性

1.3 組件協作流程

圖解kubernetes中Pod生命之初的坎坷歷程

kubelet的結構體聲明就高達300多行代碼,可見其複雜程度,但是我們按照容器創建這個流程,我們去觀察其核心流程,其實主要可以概括為三部分:kubelet、containerRuntime、CRI容器運行時

2.Kubelet創建容器流程

圖解kubernetes中Pod生命之初的坎坷歷程

2.1 獲取Pod進行准入檢查

kubelet的事件源主要包含兩個部分:靜態Pod和Apiserver,我們這裡只考慮普通的Pod,則會直接將Pod加入到PodManager來進行管理,並且進行准入檢查

准入檢查主要包含兩個關鍵的控制器:驅逐管理與預選檢查

驅逐管理主要是根據當前的資源壓力,檢測對應的Pod是否容忍當前的資源壓力;

預選檢查則是根據當前活躍的容器和當前節點的信息來檢查是否滿足當前Pod的基礎運行環境,例如親和性檢查,同時如果當前的Pod的優先級特別高或者是靜態Pod,則會嘗試為其進行資源搶佔,會按照QOS等級逐級來進行搶佔從而滿足其運行環境

2.2 創建事件管道與容器管理主線程

kubelet接收到一個新創建的Pod首先會為其創建一個事件管道,並且啟動一個容器管理的主線程消費管道里面的事件,並且會基於最後同步時間來等待當前kubelet中最新發生的事件(從本地的podCache中獲取),如果是一個新建的Pod,則主要是通過PLEG中更新時間操作,廣播的默認空狀態來作為最新的狀態

2.3 同步最新狀態

當從本地的podCache中獲取到最新的狀態信息和從事件源獲取的Pod信息後,會結合當前當前statusManager和probeManager裡面的Pod裡面的容器狀態來更新,從而獲取當前感知到的最新的Pod狀態

2.4 准入控制檢查

之前的准入檢查是Pod運行的資源硬性限制的檢查,而這裡的准入檢查則是軟狀態即容器運行時和版本的一些軟件運行環境檢查,如果這裡檢查失敗,則會講對應的容器狀態設置為Blocked

2.5 更新容器狀態

在通過准入檢查之後,會調用statusManager來進行POd最新狀態的同步,此處可能會同步給apiserver

2.6 Cgroup配置

在更新完成狀態之後會啟動一個PodCOntainerManager主要作用則是為對應的Pod根據其QOS等級來進行Cgroup配置的更新

2.7Pod基礎運行環境準備

接下來kubelet會為Pod的創建準備基礎的環境,包括Pod數據目錄的創建、鏡像秘鑰的獲取、等待volume掛載完成等操作

創建Pod的數據目錄主要是創建 Pod運行所需要的Pod、插件、Volume目錄,並且會通過Pod配置的鏡像拉取秘鑰生成秘鑰信息,到此kubelet創建容器的工作就已經基本完成

3.ContainerRuntime

圖解kubernetes中Pod生命之初的坎坷歷程

前面我們提到過針對Pod的操作,最終都是基於事件和狀態的同步而完成,在containerRUntime並不會區分對應的事件是創建還是更新操作,只是根據當前的Pod的信息與目標狀態來進行對比,從而構建出對應的操作,達到目標狀態

3.1 計算Pod容器變更

計算容器變更主要包括:Pod的sandbox是否變更、短聲明週期容器、初始化容器是否完成、業務容器是否已經完成,相應的我們會得到一個幾個對應的容器列表:需要被kill掉的容器列表、需要啟動的容器列表,注意如果我們的初始化容器未完成,則不會進行將要運行的業務容器加入到需要啟動的容器列表,可以看到這個地方是兩個階段

3.2 初始化失敗嘗試終止

如果之前檢測到之前的初始化容器失敗,則會檢查當前Pod的所有容器和sandbox關聯的容器如果有在運行的容器,會全部進行Kill操作,並且等待操作完成

3.3 未知狀態容器補償

當一些Pod的容器已經運行,但是其狀態仍然是Unknow的時候,在這個地方會進行統一的處理,全部kill掉,從而為接下來的重新啟動做清理操作,此處和3.2只會進行一個分支,但核心的目標都是清理那些運行失敗或者無法獲取狀態的容器

3.4 創建容器沙箱

在啟動Pod的容器之前,首先會為其創建一個sandbox容器,當前Pod的所有容器都和Pod對應的sandbox共享同一個namespace從而共享一個namespace裡面的資源,創建Sandbox比較複雜,後續會繼續介紹

3.5 啟動Pod相關容器

Pod的容器目前分為三大類:短生命週期容器、初始化容器、業務容器,啟動順序也是從左到右依次進行,如果對於的容器創建失敗,則會通過backoff機制來延緩容器的創建,這裡我們順便介紹下containerRuntime啟動容器的流程

3.5.1 檢查容器鏡像是否拉取

鏡像的拉取首先會進行對應容器鏡像的拼接,然後將之前獲取的拉取的秘鑰信息和鏡像信息,一起交給CRI運行時來進行底層容器鏡像的拉取,當然這裡也會各種backoff機制,從而避免頻繁拉取失敗影響kubelet的性能

3.5.2 創建容器配置

創建容器配置主要是為了容器的運行創建對應的配置數據,主要包括:Pod的主機名、域名、掛載的volume、configMap、secret、環境變量、掛載的設備信息、要掛載的目錄信息、端口映射信息、根據環境生成執行的命令、日誌目錄等信息

3.5.3 調用runtimeService完成容器的創建

調用runtimeService傳遞容器的配置信息,調用CRI,並且最終調用容器的創建接口完成容器的狀態

3.5.4 調用runtimeService啟動容器

通過之前創建容器返回的容器ID,來進行對應的容器的啟動,並且會為容器創建對應的日誌目錄

3.5.5 執行容器的回調鉤子

如果容器配置了PostStart鉤子,則會在此處進行對應鉤子的執行,如果鉤子的類型是Exec類則會調用CNI的EXec接口完成在容器內的執行

4. 運行沙箱容器

圖解kubernetes中Pod生命之初的坎坷歷程

4.1 拉取sandbox鏡像

首先會拉取sandbox鏡像

4.2 創建沙箱容器

4.2.1 應用SecurityContext

在創建容器之前會先根據SecurityContext裡面的配資信息,來進行容器SecurityContext的配置,主要包括特權等級、只讀目錄、運行賬戶組等信息

4.2 其餘基礎信息

除了應用SecurityContext還會進行斷開、OOMScoreAdj、Cgroup驅動等信息的映射

4.3 創建容器

根據上面的各種配置信息來進行容器的創建

4.3 創建checkpoint

checkpoint主要是將當前sandbox的配置信息進行序列化,並且存儲其當前的快照信息

4.4 啟動sandbox容器

啟動sandbox容器則會直接調用StartContainer同時傳入之前創建容器返回的ID完成容器的啟動,並且此時會重寫覆蓋容器的dns配置文件

4.5 容器網絡設置

容器的網絡配置主要是調用CNI插件來完成容器網絡的配置,這裡就先不展開了

5. Pod容器啟動總結

圖解kubernetes中Pod生命之初的坎坷歷程

kubelet是容器管理的核心大管家,其負責各種准入控制、狀態管理、探測管理、volume管理、QOS管理、CSI對接的統一調度,並且為Runtime運行時準備基礎的數據和並反饋Pod當前的最新狀態

圖解kubernetes中Pod生命之初的坎坷歷程

Runtime層則將kubelet組裝的數據,按照CRI運行時的目標配置和kubelet管理的資源配置信息來進行資源的重組,並且根據Pod的容器的狀態來決策容器的啟停、創建等操作,並完成容器的基礎配置環境的構建,並最終調用CRI完成容器的創建,而CRI運行時,則會講傳遞過來的各種數據進行進一步的組合,並應用到主機和對應的namespace資源限制,並根據自己的容器服務組織數據,調用容器服務完成容器的最終創建


分享到:


相關文章: