源码导读系列之 kubelet

kubelet是运行在kubernetes集群中每个节点上的一个代理,管理多种来源的pod,并尽力确保pod中的容器正常运行。kubelet功能繁多,想要大而全的解析kubelet的功能显然得不偿失。本篇文章只做一些重要的常见的kubelet功能的源码导读,以期达到更深入的理解kubelet的目的。

根据kubelet的功能介绍,大致梳理一下分析的大致思路。如kubelet处理pod的创建删除流程,pod的liveness和readness管理,kubelet如何监控pod中的container的状态,如何向api-server更新节点和pod的状态信息,kubelet与cri都有哪些交互等。

个人感觉以源码导读的方式记录阅读代码的成果比源码解析的方式更科学,代码细节可能会变化,主干流程是相对比较稳定的,并且要真正的学习和理解代码,还是需要自己深入到代码中探索和发现的。当发现问题需要处理时,可以更快找到代码的入口点,细读可疑的部分代码,提升处理问题的效率。

kubelet循环

pod调谐.png

从图中可以看到kubelet就是在一个循环中通过主动轮训或者事件通知的方式不断的监测pod,将pod调整到预期的状态。

  • pod的创建来源可以有多种,可以是api-server将一个pod调度到本节点,可以是一个本地的文件目录,也称作mirror pod或static pod(/etc/kubernetes/manifests)。
  • 如果pod配置了liveness,当探针检查失败时,会重启pod。
  • PLEG,如果pod中的container因为某些原因退出了,kubelet可以通过查询和对比新旧pod数据感知到pod的变化,并调整pod到预期状态。
  • 为了分离任务,抽象出了PodWorkers这一层,pod的状态由kubelet通过dispatchWork接口将任务分配到PodWorkers中去处理。

kubelet创建pod

创建pod流程图.png

当kubelet接受到新建pod的请求后,开始做一系列的处理,涉及到kubelet本身,podWorker,kubeGenericRuntimeManager等多个组件,最后调用CRI真正的去启动pod中的容器。这里要梳理清楚pod所需的资源是哪些组件负责的,如拉取镜像是由CRI完成的,sandbox相关的网络是由CRI调用CNI完成的,cgroup目录是由kubelet本身完成的等等。
其中SyncPod是pod状态同步中一个比较重要的函数,创建删除更新等都会调用到这个函数,为方便理解,放上代码中的注释,注释中的流程说明浅显易懂且清晰。

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

pod生命周期管理.png

PLEG全称Pod lifecycle event generator,准确的翻译应该是Pod生命周期事件生成器。我把它叫做Pod生命周期管理,因为它是Pod的生命周期管理中的重要的一部分,是产生Pod变更事件的来源。

kubelet的pleg模块不断的检查Pod中的sandbox和container的状态变化,如果有变化,则产生事件,kubelet读取事件,并将pod状态调整到期望的状态。例如Pod中的container的1号进程因为内存超限触发oom导致容器退出,pleg通过对比新旧pod数据,生成一个事件,kubelet感知到容器退出就可以重启失败的容器。这里要注意区分被oom的进程是容器的1号进程还是容器内的普通进程,1号进程很重要,可以清理僵尸进程,可以实现容器内子进程的优雅退出等等,专门有一个tini项目来实现容器中的1号进程,新版本的docker中也增加了参数--init来启动专门的1号进程

检查的时间间隔g.relistPeriod是可以配置的,默认值是1s。

kubelet中的那些manager

kubelet中有很多的manager,这些manager根据名字就可以判断出它的作用,这些功能独立的manager相互协作,穿插在pod的生命周期中,实现了kubelet的完整功能。或者说podSpec中有很多的字段,这些manager就是处理这些podSpec中的字段的。例如statusManager负责缓存pod的状态并更新到apiserver,evictionManager负责pod的驱逐,imageManager负责gc,通过调用CRI的image接口删除不再使用的镜像等等。

了解了这些manager,在看kubelet的代码时,就可以快速的把握住主线,不会迷失在纷繁复杂的代码中。如果关注configmap在创建pod时如何生效,就可以重点看下configMapManager。

这些manager运行的套路基本都是一致的,先是通过包装的New()函数初始化一个Manager对象,在调用manager的Start或者Run方法开始做具体的工作。

manager.png

下面代码中也是一些重要的函数入口,通过记录这些入口函数,也可以很快速的找到具体功能的代码片段。如查看node的update更新节点状态,就可以查看syncNodeStatus函数。

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数据结构与CRI接口之间的一个桥梁。如创建pod会调用到klet.syncPod。

    klet.podWorkers = newPodWorkers(
        klet.syncPod,
        klet.syncTerminatingPod,
        klet.syncTerminatedPod,
        kubeDeps.Recorder,
        klet.workQueue,
        klet.resyncInterval,
        backOffPeriod,
        klet.podCache,
    )

metrics

云原生组件一般都提供了metrics接口来获取服务的一些细节的状态,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}
    相关代码入口如下:

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")

重要的数据结构

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 ...

你可能感兴趣的:(源码导读系列之 kubelet)