kubelet是運行在kubernetes集群中每個節(jié)點上的一個代理,管理多種來源的pod,并盡力確保pod中的容器正常運行。kubelet功能繁多,想要大而全的解析kubelet的功能顯然得不償失。本篇文章只做一些重要的常見的kubelet功能的源碼導(dǎo)讀,以期達到更深入的理解kubelet的目的。
根據(jù)kubelet的功能介紹,大致梳理一下分析的大致思路。如kubelet處理pod的創(chuàng)建刪除流程,pod的liveness和readness管理,kubelet如何監(jiān)控pod中的container的狀態(tài),如何向api-server更新節(jié)點和pod的狀態(tài)信息,kubelet與cri都有哪些交互等。
個人感覺以源碼導(dǎo)讀的方式記錄閱讀代碼的成果比源碼解析的方式更科學(xué),代碼細節(jié)可能會變化,主干流程是相對比較穩(wěn)定的,并且要真正的學(xué)習(xí)和理解代碼,還是需要自己深入到代碼中探索和發(fā)現(xiàn)的。當(dāng)發(fā)現(xiàn)問題需要處理時,可以更快找到代碼的入口點,細讀可疑的部分代碼,提升處理問題的效率。
kubelet循環(huán)

從圖中可以看到kubelet就是在一個循環(huán)中通過主動輪訓(xùn)或者事件通知的方式不斷的監(jiān)測pod,將pod調(diào)整到預(yù)期的狀態(tài)。
- pod的創(chuàng)建來源可以有多種,可以是api-server將一個pod調(diào)度到本節(jié)點,可以是一個本地的文件目錄,也稱作mirror pod或static pod(/etc/kubernetes/manifests)。
- 如果pod配置了liveness,當(dāng)探針檢查失敗時,會重啟pod。
- PLEG,如果pod中的container因為某些原因退出了,kubelet可以通過查詢和對比新舊pod數(shù)據(jù)感知到pod的變化,并調(diào)整pod到預(yù)期狀態(tài)。
- 為了分離任務(wù),抽象出了PodWorkers這一層,pod的狀態(tài)由kubelet通過dispatchWork接口將任務(wù)分配到PodWorkers中去處理。
kubelet創(chuàng)建pod

當(dāng)kubelet接受到新建pod的請求后,開始做一系列的處理,涉及到kubelet本身,podWorker,kubeGenericRuntimeManager等多個組件,最后調(diào)用CRI真正的去啟動pod中的容器。這里要梳理清楚pod所需的資源是哪些組件負責(zé)的,如拉取鏡像是由CRI完成的,sandbox相關(guān)的網(wǎng)絡(luò)是由CRI調(diào)用CNI完成的,cgroup目錄是由kubelet本身完成的等等。
其中SyncPod是pod狀態(tài)同步中一個比較重要的函數(shù),創(chuàng)建刪除更新等都會調(diào)用到這個函數(shù),為方便理解,放上代碼中的注釋,注釋中的流程說明淺顯易懂且清晰。
pkg/kubelet/kuberuntime/kuberuntime_manager.go
// SyncPod syncs the running pod into the desired pod by executing following steps:
//
// 1. Compute sandbox and container changes.
// 2. Kill pod sandbox if necessary.
// 3. Kill any containers that should not be running.
// 4. Create sandbox if necessary.
// 5. Create ephemeral containers.
// 6. Create init containers.
// 7. Create normal containers.
func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) {
}
PLEG

PLEG全稱Pod lifecycle event generator,準(zhǔn)確的翻譯應(yīng)該是Pod生命周期事件生成器。我把它叫做Pod生命周期管理,因為它是Pod的生命周期管理中的重要的一部分,是產(chǎn)生Pod變更事件的來源。
kubelet的pleg模塊不斷的檢查Pod中的sandbox和container的狀態(tài)變化,如果有變化,則產(chǎn)生事件,kubelet讀取事件,并將pod狀態(tài)調(diào)整到期望的狀態(tài)。例如Pod中的container的1號進程因為內(nèi)存超限觸發(fā)oom導(dǎo)致容器退出,pleg通過對比新舊pod數(shù)據(jù),生成一個事件,kubelet感知到容器退出就可以重啟失敗的容器。這里要注意區(qū)分被oom的進程是容器的1號進程還是容器內(nèi)的普通進程,1號進程很重要,可以清理僵尸進程,可以實現(xiàn)容器內(nèi)子進程的優(yōu)雅退出等等,專門有一個tini項目來實現(xiàn)容器中的1號進程,新版本的docker中也增加了參數(shù)--init來啟動專門的1號進程
檢查的時間間隔g.relistPeriod是可以配置的,默認值是1s。
kubelet中的那些manager
kubelet中有很多的manager,這些manager根據(jù)名字就可以判斷出它的作用,這些功能獨立的manager相互協(xié)作,穿插在pod的生命周期中,實現(xiàn)了kubelet的完整功能。或者說podSpec中有很多的字段,這些manager就是處理這些podSpec中的字段的。例如statusManager負責(zé)緩存pod的狀態(tài)并更新到apiserver,evictionManager負責(zé)pod的驅(qū)逐,imageManager負責(zé)gc,通過調(diào)用CRI的image接口刪除不再使用的鏡像等等。
了解了這些manager,在看kubelet的代碼時,就可以快速的把握住主線,不會迷失在紛繁復(fù)雜的代碼中。如果關(guān)注configmap在創(chuàng)建pod時如何生效,就可以重點看下configMapManager。
這些manager運行的套路基本都是一致的,先是通過包裝的New()函數(shù)初始化一個Manager對象,在調(diào)用manager的Start或者Run方法開始做具體的工作。

下面代碼中也是一些重要的函數(shù)入口,通過記錄這些入口函數(shù),也可以很快速的找到具體功能的代碼片段。如查看node的update更新節(jié)點狀態(tài),就可以查看syncNodeStatus函數(shù)。
kl.imageManager.Start()
go kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop)
kl.oomWatcher.Start(kl.nodeRef)
kl.cadvisor.Start()
kubeInformers.Start(wait.NeverStop)
kl.containerLogManager.Start()
kl.pleg.Start()
kl.runtimeClassManager.Start(wait.NeverStop)
kl.statusManager.Start()
kl.containerManager.Start(node, kl.GetActivePods, kl.sourcesReady, kl.statusManager, kl.runtimeService)
kl.evictionManager.Start(kl.StatsProvider, kl.GetActivePods, kl.podResourcesAreReclaimed, evictionMonitoringPeriod)
go kl.pluginManager.Run(kl.sourcesReady, wait.NeverStop)
go wait.Until(kl.syncNodeStatus, kl.nodeStatusUpdateFrequency, wait.NeverStop) -> defaultNodeStatusFuncs
go kl.fastStatusUpdateOnce()
go kl.nodeLeaseController.Run(wait.NeverStop)
go wait.Until(kl.updateRuntimeUp, 5*time.Second, wait.NeverStop)
go k.Run(podCfg.Updates()) -> syncLoop -> syncLoopIteration
podWorkers
podWokers對象可以看做是kubelet的pod數(shù)據(jù)結(jié)構(gòu)與CRI接口之間的一個橋梁。如創(chuàng)建pod會調(diào)用到klet.syncPod。
klet.podWorkers = newPodWorkers(
klet.syncPod,
klet.syncTerminatingPod,
klet.syncTerminatedPod,
kubeDeps.Recorder,
klet.workQueue,
klet.resyncInterval,
backOffPeriod,
klet.podCache,
)
metrics
云原生組件一般都提供了metrics接口來獲取服務(wù)的一些細節(jié)的狀態(tài),kubelet除了提供metrics可供prometheus采集外,還提供了stats接口來獲取運行時信息,包括pod,container等。
訪問10250端口 需要證書,10255端口 不需要證書
另外kubelet還開放了其他幾個有意義的http接口
- /stats/
- /stats/summary
- /stats/container
- 127.0.0.1:10255/pods
- /{podName}/{containerName}
- /{namespace}/{podName}/{uid}/{containerName}
相關(guān)代碼入口如下:
func (s *Server) InstallDefaultHandlers(enableCAdvisorJSONEndpoints bool) {
s.addMetricsBucketMatcher("healthz")
healthz.InstallHandler(s.restfulCont,
healthz.PingHealthz,
healthz.LogHealthz,
healthz.NamedCheck("syncloop", s.syncLoopHealthCheck),
)
s.addMetricsBucketMatcher("pods")
ws := new(restful.WebService)
ws.
Path("/pods").
Produces(restful.MIME_JSON)
ws.Route(ws.GET("").
To(s.getPods).
Operation("getPods"))
s.restfulCont.Add(ws)
s.addMetricsBucketMatcher("stats")
s.restfulCont.Add(stats.CreateHandlers(statsPath, s.host, s.resourceAnalyzer, enableCAdvisorJSONEndpoints))
s.addMetricsBucketMatcher("metrics")
s.addMetricsBucketMatcher("metrics/cadvisor")
s.addMetricsBucketMatcher("metrics/probes")
s.addMetricsBucketMatcher("metrics/resource/v1alpha1")
重要的數(shù)據(jù)結(jié)構(gòu)
pkg/apis/core/types.go
type Pod struct {
}
type Node struct {
}
pkg/kubelet/kubelet.go
type Kubelet struct {
}
type SyncHanler interface {
HandlePodAdditions(pods []*v1.Pod)
HandlePodUpdates(pods []*v1.Pod)
HandlePodRemoves(pods []*v1.Pod)
HandlePodReconcile(pods []*v1.Pod)
HandlePodSyncs(pods []*v1.Pod)
HandlePodCleanups() error
}
pkg/kubelet/pod_workers.go
type podWorkers struct {
}
pkg/kubelet/kuberuntime/kuberuntime_manager.go
func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) {
}
type kubeGenericRuntimeManager struct {
}
pkg/kubelet/kubelet.go
makePodSourceConfig static_pod, api-server ...