您好,登錄后才能下訂單哦!
Kubernetes已然成為云環境中大規模部署容器化應用的事實標準,而Kubelet作為Kubernetes集群中的節點代理,即節點的守護者,會具體負責其所在節點的波德的生命周期管理,切實保證各個容器應用都按照預期的狀態平穩運行。它會首先獲取分配到本節點的波德的配置信息,再根據這些配置信息調用底層的容器運行時,例如多克爾或者PouchContainer,創建具體的波德,并對這些波德進行監控,保證節點上的所有Pod按照預期的狀態運行。本文將結合Kubelet源碼,對上述過程進行詳細的分析。
本文作者姚增增,花名里奇,Kubernetes社區的資深貢獻者,阿里集團開源富容器引擎PouchContainer的維護者,主導并推進了PouchContainer容器技術中CRI接口的設計與實現。從事云原生相關的技術領域,關注容器技術,編排技術,操作系統內核。當前是浙江大學 SEL 實驗室的在讀研究生,個人推崇開源理念。
Kubelet有多種途徑獲取本節點需要運行的吊艙的配置信息。最重要的自然是API服務器,其次還能通過指定配置文件的目錄以及訪問特定的HTTP端口獲取.Kubelet會定期對它們進行訪問,獲取波德配置的更新并及時調整位于本節點的Pod的運行狀態。
在Kubelet初始化的時候會創建一個PodConfig
對象如下所示:
// kubernetes/pkg/kubelet/config/config.go
type PodConfig struct {
pods *podStorage
mux *config.Mux
// the channel of denormalized changes passed to listeners
updates chan kubetypes.PodUpdate
...
}
PodConfig
本質上是Pod配置信息的一個復用器。內含的mux
會對各種Pod配置信息的源(包括apiserver
,file
以及http
)進行監聽,定期同步各源當前的Pod配置狀態。在pods
中則緩存了上次同步時各源的Pod配置狀態。mux
將兩者進行對比之后,即可得到配置發生變化的Pod。接著,它會根據變化類型對不同的Pod進行分類,每個類型的Pod注入一個PodUpdate
結構中:
// kubernetes/pkg/kubelet/types/pod_update.go
type PodUpdate struct {
Pods []*v1.Pod
Op PodOperation
Source string
}
Op
字段即定義了上述的Pod變化類型。例如,它的值可以為ADD
或REMOVE
,表示對Pods
中定義的Pod進行相應的增選。最后,各種類型的PodUpdate
都會被注入到PodConfig
的updates
中。因此,我們只要對updates
這個頻道進行監聽,就能得到所有有關本節點Pod的更新信息。
當Kubelet初始化完成之后,最終會調用如下所示的syncLoop
函數:
// kubernetes/pkg/kubelet/kubelet.go
// syncLoop is the main loop for processing changes. It watches for changes from
// three channels (file, apiserver, and http) and creates a union of them. For
// any new change seen, will run a sync against desired state and running state. If
// no changes are seen to the configuration, will synchronize the last known desired
// state every sync-frequency seconds. Never returns.
func (kl *Kubelet) syncLoop(updates <-chan kubetypes.PodUpdate, handler SyncHandler){
...
for {
if !kl.syncLoopIteration(...) {
break
}
}
...
}
正如它的注釋所表明的,syncLoop
函數是Kubelet的主循環。它會對updates
進行監聽,獲取吊艙的最新配置,并在當前狀態(運行狀態)和期望狀態(所需狀態)之間進行同步,使本節點的pod都按預期狀態運行。事實上,syncLoop
僅僅對對syncLoopIteration
的封裝,每一次具體的同步操作都將交由syncLoopIteration
完成。
// kubernetes/pkg/kubelet/kubelet.go
func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate ......) bool {
select {
case u, open := <-configCh:
switch u.Op {
case kubetypes.ADD:
handler.HandlePodAdditions(u.Pods)
case kubetypes.UPDATE:
handler.HandlePodUpdates(u.Pods)
...
}
case e := <-plegCh:
...
handler.HandlePodSyncs([]*v1.Pod{pod})
...
case <-syncCh:
podsToSync := kl.getPodsToSync()
if len(podsToSync) == 0 {
break
}
handler.HandlePodSyncs(podsToSync)
case update := <-kl.livenessManager.Updates():
if update.Result == proberesults.Failure {
...
handler.HandlePodSyncs([]*v1.Pod{pod})
}
case <-housekeepingCh:
...
handler.HandlePodCleanups()
...
}
}
syncLoopIteration
函數的處理邏輯很簡單,它會對多個渠道進行監聽,一旦從某個渠道中獲取到了某類事件,就調用相應的處理函數對其進行處理。下面,我們對各類事件做一個簡單的敘述:
從configCh
中莢電子雜志配置信息的改變,并根據改變的類型,調用對應的處理函數。例如有新的吊艙綁定到本節點上時,調用就會HandlePodAdditions
在本。節點上新建這些吊艙。如果某些莢的配置發生了改變,則會調用HandlePodUpdates
對這些pod進行更新。
若莢中有容器的狀態發生了變化,例如有新的容器創建并運行,則會向plegCh
這個通道發送PodlifecycleEvent
這樣一個事件,其中包含了事件類型ContainerStarted
,該容器的ID,以及它所屬Pod的ID,接著syncLoopIteration
會調用HandlePodSyncs
對該pod進行同步。
syncCh
其實是一個定時器,Kubelet默認每隔一秒它就會觸發一次,對當前節點上所有需要同步的pod進行同步。
Kubelet在初始化過程中會創建livenessManager
,它會對進行了相關配置的吊艙進行健康檢查。一旦檢測到吊艙的運行狀態出錯,同樣會調用HandlePodSyncs
對相應的吊艙進行同步。關于這部分的內容,我們將在下文中詳細描述。
houseKeepingCh
同樣是一個定時器,Kubelet默認每隔兩秒它就會觸發一次并調用處理函數HandlePodCleanups
。簡單地說,這就是一個定時清理的機制,每隔一段時間對那些已經結束運行的pod的相關資源進行回收。
HandlePodAdditions
,HandlePodUpdates
還是HandlePodSyncs
都會在完成自己特有的一些操作之后調用dispatchWork
函數。而dispatchWork
函數如果確認了要同步的吊艙處于不Terminated
狀態,調用就會podWokers
的Update
方法對莢果進行更新。事實上,不管是新建莢,還是對莢的更新,同步,我們都可以將其統一為從當前狀態(運行狀態)到目標狀態(所需狀態)過渡的過程。這樣的解釋對于pod的更新和同步是很直觀的。而對于新建pod,則可以認為它的當前狀態為空,那么我們也能將其納入這個框架中。因此,無論我們是要創建,更新還是同步莢,我們名單最終調用只要統一的Update
函數,就能讓指定的吊艙從當前狀態轉換到目標狀態。podWorkers
會在Kubelet初始化的過程中被創建,如下所示:
// kubernetes/pkg/kubelet/pod_workers.go
type podWorkers struct {
...
podUpdates map[types.UID]chan UpdatePodOptions
isWorking map[types.UID]bool
lastUndeliveredWorkUpdate map[types.UID]UpdatePodOptions
workQueue queue.WorkQueue
syncPodFn syncPodFnType
podCache kubecontainer.Cache
...
}
當Kubelet每創建一個新的吊艙,都會為其配置一個專有的莢工人。每個莢工人其實就是一個夠程,它會創建一個緩存大小為1,類型為UpdatePodOptions
(一個UpdatePodOptions
就是一個莢更新事件)的信道,并不斷對其監聽來獲取pod的更新事件并調用podWorkers
中syncPodFn
字段指定的同步函數進行具體的同步工作。
同時,pod worker會將該通道注冊到podWorkers
中的podUpdates
這個地圖中,從而可以讓外部將指定的更新事件發送到對應的pod worker,讓它進行處理。
如果pod worker正在處理某個更新,這時候又來了另外一個更新事件怎么辦?podWorkers
會將其中最新的一個緩存到lastUndeliveredWorkUpdate
并在pod worker處理完當前更新事件之后馬上對其進行處理。
最后,pod worker每處理完一次更新,都會將pod加入podWorkers
的workQueue
隊列,而且會附加一個時延,只有時延消耗完了,才能將pod從隊列中再次取出,進行下一次的同步。在上文中我們提到,每過1秒就會觸發一次syncCh
,收集本節點上需要進行同步的豆莢調用再HandlePodSyncs
進行同步。事實上,那些莢從正是workQueue
中的電子雜志,在當前時間節點,時延到期的吊艙。由此,整個pod的同步過程,如下所示,形成了一個閉環。
Kubelet在創建podWorkers
對象的時候,會用自己的syncPod
方法syncPodFn
初始化。不過該方法所做的工作也僅僅是真正進行同步前的一些準備工作。例如將pod的最新狀態上傳給Apiserver,創建pod的專屬目錄,獲取莢的拉動秘密等等。最終,Kubelet調用會所屬其的containerRuntime
的SyncPod
方法進行同步工作。containerRuntime
是Kubelet對底層容器運行時的一種抽象,定義了各種容器運行時需要滿足的接口。SyncPod
方法就是這些接口中的一個。
Kubelet并不會進行任何具體的容器相關的操作,所謂pod的同步,本質上還是對相關容器狀態的改變,而要做到這一點,最終必然只能調用例如PouchContainer這樣的底層容器運行時來完成。
下面,將我們展示進入containerRuntime
的SyncPod
方法,展示真正的同步工作:
// kubernetes/pkg/kubelet/kuberuntime/kuberuntime_manager.go
func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, _ v1.PodStatus, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult)
該函數首先會調用computePodActions(pod, podStatus)
,對pod的當前狀態podStatus
和pod的目標狀態pod
進行比較,從而計算出我們要進行哪些具體的同步工作。計算結束之后,返回一個PodActions
對象如下所示:
// kubernetes/pkg/kubelet/kuberuntime/kuberuntime_manager.go
type podActions struct {
KillPod bool
CreateSandbox bool
SandboxID string
Attempt uint32
ContainersToKill map[kubecontainer.ContainerID]containerToKillInfo
NextInitContainerToStart *v1.Container
ContainersToStart []int
}
事實上,PodActions
就是一個操作列表:
KillPod
和CreateSandbox
的值一般情況下是一致的,表示是否殺死當前Pod的Sandbox(若創建一個新Pod,則該操作為空)而創建一個新的
SandboxID
用于對pod的創建操作進行標識,若它的值為空,表示第一次創建pod,否則表示殺死原有的sandbox而創建一個新的
Attempt
表示pod重新創建sandbox的次數,第一次創建pod時,該值為0,作用和SandboxID
是類似的
ContainersToKill
指定了我們需要殺死的pod中的一些容器,之所以要刪除它們,可能是因為容器的配置已經發生了變化,或者對它的健康檢查失敗了
如果pod的init容器還沒有全部運行完成或者在運行過程中出現了問題,NextInitContainerToStart
表示下一個要創建的init container,創建并啟動它,此次同步結束
若莢的沙箱已經創建完成,init container也都運行完畢,則根據ContainersToStart
啟動pod中還未正常運行的普通容器
有了這樣一份操作列表之后,SyncPod
剩下的操作就異常簡單了,無非是根據配置,按部就班地調用底層容器運行時的相應接口,進行具體的容器增刪工作,完成同步。
總的來說,對于pod的同步可以簡單歸結為:當莢的目標狀態發生改變,或者每隔一個同步周期,都會觸發對相應pod的同步,而同步的具體內容就是將容器的目標狀態和當前狀態進行比對計算,生成一張容器的啟停清單,根據該清單調用底層的容器運行時接口完成相應容器的啟停工作。
如果簡單地將容器類比為一個進程的話,那么Kubelet本質上就是一個面向容器的進程監視器。它的任務就是不斷地促成本節點pod的運行狀態向目標狀態轉換。轉換的方式也非常簡單粗暴,如果有不符合要求的容器就直接刪除,再根據新的配置重建一個,并不存在對一個已有容器反復修改啟停的情況。到此為止,Kubelet核心的處理邏輯闡述完畢。
文中源碼對應的Kubernetes版本為v1.9.4
,commit:bee2d1505c4fe820744d26d41ecd3fdd4a3d6546
Kubernetes詳細的源碼注釋參加我的github
Kubernetes源碼
https://github.com/YaoZengzeng/kubernetes
什么甚至是一個kubelet?
http://kamalmarhubi.com/blog/2015/08/27/what-even-is-a-kubelet/
阿里百萬級規模開源容器PouchContainer GA版本已發布。此版本延續以往的節奏,繼續在Cloud Native(Kubernetes)生態的支持,以及容器安全隔離等方面做了持續性增強,同時開始孵化PouchContainer的插件機制,使得生態用戶可以更加友好便捷地通過自研插件實現容器功能的擴展。
PouchContainer發布GA版本之前,已在阿里巴巴數據中心得到大規模的驗證; GA版本發布之后,相信其一系列的突出特性同樣可以服務于行業,作為一種開箱即用的系統軟件技術,幫助行業服務在推進云原生架構轉型上占得先機。
為了分享并促進社區的進步,邀請大家參加2018年9月9日(周日)上海PouchContainer Meetup,掃描上圖二維碼或者點擊閱讀原文立即報名。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。