深入理解Kubelet核心执行框架
?
Kubernetes已然成為云環(huán)境中大規(guī)模部署容器化應用的事實標準,而Kubelet作為Kubernetes集群中的節(jié)點代理,即節(jié)點的守護者,會具體負責其所在節(jié)點的波德的生命周期管理,切實保證各個容器應用都按照預期的狀態(tài)平穩(wěn)運行。它會首先獲取分配到本節(jié)點的波德的配置信息,再根據(jù)這些配置信息調(diào)用底層的容器運行時,例如多克爾或者PouchContainer,創(chuàng)建具體的波德,并對這些波德進行監(jiān)控,保證節(jié)點上的所有Pod按照預期的狀態(tài)運行。本文將結(jié)合Kubelet源碼,對上述過程進行詳細的分析。
?
本文作者姚增增,花名里奇,Kubernetes社區(qū)的資深貢獻者,阿里集團開源富容器引擎PouchContainer的維護者,主導并推進了PouchContainer容器技術中CRI接口的設計與實現(xiàn)。從事云原生相關的技術領域,關注容器技術,編排技術,操作系統(tǒng)內(nèi)核。當前是浙江大學??SEL?實驗室的在讀研究生,個人推崇開源理念。
1.獲取Pod配置
Kubelet有多種途徑獲取本節(jié)點需要運行的吊艙的配置信息。最重要的自然是API服務器,其次還能通過指定配置文件的目錄以及訪問特定的HTTP端口獲取.Kubelet會定期對它們進行訪問,獲取波德配置的更新并及時調(diào)整位于本節(jié)點的Pod的運行狀態(tài)。
在Kubelet初始化的時候會創(chuàng)建一個PodConfig對象如下所示:
//?kubernetes/pkg/kubelet/config/config.go type?PodConfig?struct?{pods?*podStoragemux??*config.Mux//?the?channel?of?denormalized?changes?passed?to?listenersupdates?chan?kubetypes.PodUpdate... }PodConfig本質(zhì)上是Pod配置信息的一個復用器。內(nèi)含的mux會對各種Pod配置信息的源(包括apiserver,file以及http)進行監(jiān)聽,定期同步各源當前的Pod配置狀態(tài)。在pods中則緩存了上次同步時各源的Pod配置狀態(tài)。mux將兩者進行對比之后,即可得到配置發(fā)生變化的Pod。接著,它會根據(jù)變化類型對不同的Pod進行分類,每個類型的Pod注入一個PodUpdate結(jié)構(gòu)中:
//?kubernetes/pkg/kubelet/types/pod_update.go type?PodUpdate?struct?{Pods???[]*v1.PodOp?????PodOperationSource?string }Op字段即定義了上述的Pod變化類型。例如,它的值可以為ADD或REMOVE,表示對Pods中定義的Pod進行相應的增選。最后,各種類型的PodUpdate都會被注入到PodConfig的updates中。因此,我們只要對updates這個頻道進行監(jiān)聽,就能得到所有有關本節(jié)點Pod的更新信息。
2.對Pod進行同步
當Kubelet初始化完成之后,最終會調(diào)用如下所示的syncLoop函數(shù):
//?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函數(shù)是Kubelet的主循環(huán)。它會對updates進行監(jiān)聽,獲取吊艙的最新配置,并在當前狀態(tài)(運行狀態(tài))和期望狀態(tài)(所需狀態(tài))之間進行同步,使本節(jié)點的pod都按預期狀態(tài)運行。事實上,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函數(shù)的處理邏輯很簡單,它會對多個渠道進行監(jiān)聽,一旦從某個渠道中獲取到了某類事件,就調(diào)用相應的處理函數(shù)對其進行處理。下面,我們對各類事件做一個簡單的敘述:
從configCh中莢電子雜志配置信息的改變,并根據(jù)改變的類型,調(diào)用對應的處理函數(shù)。例如有新的吊艙綁定到本節(jié)點上時,調(diào)用就會HandlePodAdditions在本。節(jié)點上新建這些吊艙。如果某些莢的配置發(fā)生了改變,則會調(diào)用HandlePodUpdates對這些pod進行更新。
若莢中有容器的狀態(tài)發(fā)生了變化,例如有新的容器創(chuàng)建并運行,則會向plegCh這個通道發(fā)送PodlifecycleEvent這樣一個事件,其中包含了事件類型ContainerStarted,該容器的ID,以及它所屬Pod的ID,接著syncLoopIteration會調(diào)用HandlePodSyncs對該pod進行同步。
syncCh其實是一個定時器,Kubelet默認每隔一秒它就會觸發(fā)一次,對當前節(jié)點上所有需要同步的pod進行同步。
Kubelet在初始化過程中會創(chuàng)建livenessManager,它會對進行了相關配置的吊艙進行健康檢查。一旦檢測到吊艙的運行狀態(tài)出錯,同樣會調(diào)用HandlePodSyncs對相應的吊艙進行同步。關于這部分的內(nèi)容,我們將在下文中詳細描述。
houseKeepingCh同樣是一個定時器,Kubelet默認每隔兩秒它就會觸發(fā)一次并調(diào)用處理函數(shù)HandlePodCleanups。簡單地說,這就是一個定時清理的機制,每隔一段時間對那些已經(jīng)結(jié)束運行的pod的相關資源進行回收。
如上圖所示,大多數(shù)處理函數(shù)的執(zhí)行路徑是非常類似的。
?
不管是HandlePodAdditions,HandlePodUpdates還是HandlePodSyncs都會在完成自己特有的一些操作之后調(diào)用dispatchWork函數(shù)。而dispatchWork函數(shù)如果確認了要同步的吊艙處于不Terminated狀態(tài),調(diào)用就會podWokers的Update方法對莢果進行更新。事實上,不管是新建莢,還是對莢的更新,同步,我們都可以將其統(tǒng)一為從當前狀態(tài)(運行狀態(tài))到目標狀態(tài)(所需狀態(tài))過渡的過程。這樣的解釋對于pod的更新和同步是很直觀的。而對于新建pod,則可以認為它的當前狀態(tài)為空,那么我們也能將其納入這個框架中。因此,無論我們是要創(chuàng)建,更新還是同步莢,我們名單最終調(diào)用只要統(tǒng)一的Update函數(shù),就能讓指定的吊艙從當前狀態(tài)轉(zhuǎn)換到目標狀態(tài)。
podWorkers會在Kubelet初始化的過程中被創(chuàng)建,如下所示:
//?kubernetes/pkg/kubelet/pod_workers.go type?podWorkers?struct?{...podUpdates?map[types.UID]chan?UpdatePodOptionsisWorking?map[types.UID]boollastUndeliveredWorkUpdate?map[types.UID]UpdatePodOptionsworkQueue?queue.WorkQueuesyncPodFn?syncPodFnTypepodCache?kubecontainer.Cache... }當Kubelet每創(chuàng)建一個新的吊艙,都會為其配置一個專有的莢工人。每個莢工人其實就是一個夠程,它會創(chuàng)建一個緩存大小為1,類型為UpdatePodOptions(一個UpdatePodOptions就是一個莢更新事件)的信道,并不斷對其監(jiān)聽來獲取pod的更新事件并調(diào)用podWorkers中syncPodFn字段指定的同步函數(shù)進行具體的同步工作。
同時,pod worker會將該通道注冊到podWorkers中的podUpdates這個地圖中,從而可以讓外部將指定的更新事件發(fā)送到對應的pod worker,讓它進行處理。
如果pod worker正在處理某個更新,這時候又來了另外一個更新事件怎么辦?podWorkers會將其中最新的一個緩存到lastUndeliveredWorkUpdate并在pod worker處理完當前更新事件之后馬上對其進行處理。
最后,pod worker每處理完一次更新,都會將pod加入podWorkers的workQueue隊列,而且會附加一個時延,只有時延消耗完了,才能將pod從隊列中再次取出,進行下一次的同步。在上文中我們提到,每過1秒就會觸發(fā)一次syncCh,收集本節(jié)點上需要進行同步的豆莢調(diào)用再HandlePodSyncs進行同步。事實上,那些莢從正是workQueue中的電子雜志,在當前時間節(jié)點,時延到期的吊艙。由此,整個pod的同步過程,如下所示,形成了一個閉環(huán)。
?
Kubelet在創(chuàng)建podWorkers對象的時候,會用自己的syncPod方法syncPodFn初始化。不過該方法所做的工作也僅僅是真正進行同步前的一些準備工作。例如將pod的最新狀態(tài)上傳給Apiserver,創(chuàng)建pod的專屬目錄,獲取莢的拉動秘密等等。最終,Kubelet調(diào)用會所屬其的containerRuntime的SyncPod方法進行同步工作。containerRuntime是Kubelet對底層容器運行時的一種抽象,定義了各種容器運行時需要滿足的接口。SyncPod方法就是這些接口中的一個。
Kubelet并不會進行任何具體的容器相關的操作,所謂pod的同步,本質(zhì)上還是對相關容器狀態(tài)的改變,而要做到這一點,最終必然只能調(diào)用例如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)該函數(shù)首先會調(diào)用computePodActions(pod, podStatus),對pod的當前狀態(tài)podStatus和pod的目標狀態(tài)pod進行比較,從而計算出我們要進行哪些具體的同步工作。計算結(jié)束之后,返回一個PodActions對象如下所示:
//?kubernetes/pkg/kubelet/kuberuntime/kuberuntime_manager.go type?podActions?struct?{KillPod?boolCreateSandbox?boolSandboxID?stringAttempt?uint32ContainersToKill?map[kubecontainer.ContainerID]containerToKillInfoNextInitContainerToStart?*v1.ContainerContainersToStart?[]int }事實上,PodActions就是一個操作列表:
KillPod和CreateSandbox的值一般情況下是一致的,表示是否殺死當前Pod的Sandbox(若創(chuàng)建一個新Pod,則該操作為空)而創(chuàng)建一個新的
SandboxID用于對pod的創(chuàng)建操作進行標識,若它的值為空,表示第一次創(chuàng)建pod,否則表示殺死原有的sandbox而創(chuàng)建一個新的
Attempt表示pod重新創(chuàng)建sandbox的次數(shù),第一次創(chuàng)建pod時,該值為0,作用和SandboxID是類似的
ContainersToKill指定了我們需要殺死的pod中的一些容器,之所以要刪除它們,可能是因為容器的配置已經(jīng)發(fā)生了變化,或者對它的健康檢查失敗了
如果pod的init容器還沒有全部運行完成或者在運行過程中出現(xiàn)了問題,NextInitContainerToStart表示下一個要創(chuàng)建的init container,創(chuàng)建并啟動它,此次同步結(jié)束
若莢的沙箱已經(jīng)創(chuàng)建完成,init container也都運行完畢,則根據(jù)ContainersToStart啟動pod中還未正常運行的普通容器
有了這樣一份操作列表之后,SyncPod剩下的操作就異常簡單了,無非是根據(jù)配置,按部就班地調(diào)用底層容器運行時的相應接口,進行具體的容器增刪工作,完成同步。
總的來說,對于pod的同步可以簡單歸結(jié)為:當莢的目標狀態(tài)發(fā)生改變,或者每隔一個同步周期,都會觸發(fā)對相應pod的同步,而同步的具體內(nèi)容就是將容器的目標狀態(tài)和當前狀態(tài)進行比對計算,生成一張容器的啟停清單,根據(jù)該清單調(diào)用底層的容器運行時接口完成相應容器的啟停工作。
總結(jié)
如果簡單地將容器類比為一個進程的話,那么Kubelet本質(zhì)上就是一個面向容器的進程監(jiān)視器。它的任務就是不斷地促成本節(jié)點pod的運行狀態(tài)向目標狀態(tài)轉(zhuǎn)換。轉(zhuǎn)換的方式也非常簡單粗暴,如果有不符合要求的容器就直接刪除,再根據(jù)新的配置重建一個,并不存在對一個已有容器反復修改啟停的情況。到此為止,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/
?
阿里百萬級規(guī)模開源容器PouchContainer GA版本已發(fā)布。此版本延續(xù)以往的節(jié)奏,繼續(xù)在Cloud Native(Kubernetes)生態(tài)的支持,以及容器安全隔離等方面做了持續(xù)性增強,同時開始孵化PouchContainer的插件機制,使得生態(tài)用戶可以更加友好便捷地通過自研插件實現(xiàn)容器功能的擴展。
?
PouchContainer發(fā)布GA版本之前,已在阿里巴巴數(shù)據(jù)中心得到大規(guī)模的驗證; GA版本發(fā)布之后,相信其一系列的突出特性同樣可以服務于行業(yè),作為一種開箱即用的系統(tǒng)軟件技術,幫助行業(yè)服務在推進云原生架構(gòu)轉(zhuǎn)型上占得先機。
為了分享并促進社區(qū)的進步,邀請大家參加2018年9月9日(周日)上海PouchContainer Meetup,掃描上圖二維碼直接報名。掃掃密碼描上圖二維碼或描上圖二維碼或者點擊閱讀原文立即報名
《新程序員》:云原生和全面數(shù)字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的深入理解Kubelet核心执行框架的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Golang 在阿里集团调度集群管理系统
- 下一篇: 阿里云加入 OCI,共建容器开放标准