kubelet 源码分析

Posted by Wang Gang on 2019-09-03

pod创建流程

image.png

kubelet 工作内容

  • 监视分配给该Node节点的pods
  • 挂载pod所需要的volumes
  • 下载pod的secret
  • 通过docker/rkt来运行pod中的容器
  • 周期的执行pod中为容器定义的liveness探针
  • 上报pod的状态给系统的其他组件
  • 上报Node的状态

基础架构

image.png

  • 10250 kubelet API –kublet暴露出来的端口,通过该端口可以访问获取node资源以及状态,另外可以配合kubelet的启动参数contention-profiling enable-debugging-handlers来提供了用于调试和profiling的api

  • 4194 cAdvisor –kublet通过该端口可以获取到本node节点的环境信息以及node上运行的容器的状态等内容

  • 10255 readonly API –kubelet暴露出来的只读端口,访问该端口不需要认证和鉴权,该http server提供查询资源以及状态的能力.注册的消息处理函数定义src/k8s.io/kubernetes/pkg/kubelet/server/server.go:149

  • 10248 /healthz –kubelet健康检查,通过访问该url可以判断Kubelet是否正常work, 通过kubelet的启动参数–healthz-port –healthz-bind-address来指定监听的地址和端口

  • 启动流程

    • 创建对象kubeClient、eventClient、heartbeatClient、externalkubeClient、kubeDeps.ContainerManager

组件说明

1. PLEG

PLEG全称为PodLifecycleEvent,PLEG会一直调用container runtime获取本节点的pods,之后比较本模块中之前缓存的pods信息,比较最新的pods中的容器的状态是否发生改变,当状态发生切换的时候,生成一个eventRecord事件,输出到eventChannel中. syncPod模块会接收到eventChannel中的event事件,来触发pod同步处理过程,调用contaiener runtime来重建pod,保证pod工作正常.

事件监听器eventChannel(提供一个watch,包含的是go的channel)->podManager->podWorker->syncPodfn->syncPod(同步pod)->startContainer

  • 事件监听器eventChannel 监听内容

    • configCh:将配置更改的pod分派给事件类型的相应处理程序回调
    • plegCh:更新运行时缓存;同步pod
    • syncCh:同步所有等待同步的pod
    • houseKeepingCh:触发pods的清理
    • liveness manager:对失败的pod或存在失败的容器的pod进行检查
  • 同步pod预处理

    • 如果想要删除pod,立即进行删除并返回
    • 如果正在创建pod,记录pod worker启动延迟
    • 调用generateAPIPodStatus为pod准备v1.PodStatus
    • 如果第一次检测到pod正在运行,记录pod启动延迟
    • 在状态管理器中更新窗格的状态
    • 如果它不应该运行,请杀死它
    • 如果pod是静态pod,并且还没有镜像pod,则创建一个镜像pod
    • 如果pod不存在,则为其创建数据目录
    • 等待卷附加/装载
    • 获取pod的拉取secret
    • 调用容器运行时的SyncPod回调
    • 更新pod的ingress和egress限制
  • 同步pod

    • 计算沙箱和容器更改。
    • 如有必要,先删除沙箱
    • 杀死任何不应运行的容器。
    • 如有必要,创建沙箱。
    • 创建init容器。
    • 创建普通容器。
  • 启动容器

    • 拉镜像
    • 创建容器
    • 启动容器
    • 运行post start生命周期钩子(如果声明)–container.Lifecycle
podManager

podManager提供了接口来存储和访问pod的信息,维持static pod和mirror pods的关系

image.png

StatusManager

该模块负责pod里面的容器的状态,接受从其它模块发送过来的pod状态改变的事件,进行处理,并更新到kube-apiserver中.

image.png

runtimeManager

containerRuntime负责kubelet与不同的runtime实现进行对接,实现对于底层container的操作,初始化之后得到的runtime实例将会被之前描述的组件所使用.
当前可以通过kubelet的启动参数–container-runtime来定义是使用docker还是rkt.runtime需要实现的接口定义在src/k8s.io/kubernetes/pkg/kubelet/apis/cri/services.go文件里面

image.png

cAdvicor

image.png

probemanager

探针管理,依赖于statusManager,livenessManager,containerRefManager,实现Pod的健康检查的功能.LivenessProbe和ReadinessProbe,
LivenessProbe:用于判断容器是否存活,如果探测到容器不健康,则kubelet将杀掉该容器,并根据容器的重启策略做相应的处理
ReadinessProbe: 用于判断容器是否启动完成

  • 探针有三种实现方式
    • execprobe:在容器内部执行一个命令,如果命令返回码为0,则表明容器健康
    • tcprobe:通过容器的IP地址和端口号执行TCP检查,如果能够建立TCP连接,则表明容器健康
    • httprobe:通过容器的IP地址,端口号以及路径调用httpGet方法,如果响应status>=200 && status<=400的时候,则认为容器状态健康
Container/RefManager

容器引用的管理,相对简单的Manager,通过定义map来实现了containerID与v1.ObjectReferece容器引用的映射.

image.png

image.png

VolumeManager

负责node节点上pod所使用的volume的管理.主要涉及如下功能
Volume状态的同步,模块中会启动gorountine去获取当前node上volume的状态信息以及期望的volume的状态信息,会去周期性的sync volume的状态,另外volume与pod的生命周期关联,pod的创建删除过程中volume的attach/detach流程.更重要的是kubernetes支持多种存储的插件,kubelet如何调用这些存储插件提供的interface.涉及的内容较多,更加详细的信息可以看kubernetes中volume相关的代码和文档.

image.png

EvictionManager

evictManager当node的节点资源不足的时候,达到了配置的evict的策略,将会从node上驱赶pod,来保证node节点的稳定性.可以通过kubelet启动参数来决定evict的策略.另外当node的内存以及disk资源达到evict的策略的时候会生成对应的node状态记录.

image.png

ImageGC

imageGC负责Node节点的镜像回收,当本地的存放镜像的本地磁盘空间达到某阈值的时候,会触发镜像的回收,删除掉不被pod所使用的镜像.回收镜像的阈值可以通过kubelet的启动参数来设置.

image.png

ContainerGC

containerGC负责Node节点上的dead状态的container,自动清理掉node上残留的容器.具体的GC操作由runtime来实现.

image.png

ImageManager

调用kubecontainer.ImageService提供的PullImage/GetImageRef/ListImages/RemoveImage/ImageStates的方法来保证pod运行所需要的镜像,主要是为了kubelet支持cni.

image.png

containerManager

负责node节点上运行的容器的cgroup配置信息,kubelet启动参数如果指定–cgroupPerQos的时候,kubelet会启动gorountie来周期性的更新pod的cgroup信息,维持其正确.实现了pod的Guaranteed/BestEffort/Burstable三种级别的Qos,通过配置kubelet可以有效的保证了当有大量pod在node上运行时,保证node节点的稳定性.该模块中涉及的struct主要包括

image.png

PodSandboxManager

image.png

Q

  1. kubelet 是如何启动?
    • 入口程序(配置信息的处理kube-configeration,kubeflags,kubeserver,kubedeps,kube,kubeconfigcontroller(监听配置文件的修改))
    • 启动程序
      • 判断kubelet是否以standalone模式运行;
      • 创建对象kubeClient、evnetClient、heartbeatClient、externalkubeClient、kubeDeps.ContainerManager;
      • 调用RunKubelet
      • 在RunKubelet方法中,builder函数指定为CreateAndInitKubelet,调用返回Kubelet.Bootstrap;
        1. 调用kubelet.NewMainKubelet(pkg/kubelet/kubelet.go)方法,创建kubelet对象;
        2. 调用makePodSourceConfig方法,获得pod信息来源;
          a. 调用kubelet.NewMainKubelet(pkg/kubelet/kubelet.go)方法,创建kubelet对象,
          b. config.PodConfig实现了Mux接口,Mux框架用来合并多个数据源,当任何一个数据源有更新时,都会促发Mux的Merge函数用来合并更新的信息,这里podCfg的数据源,即kubetypes.FileSource、kubetypes.HTTPSource和kubetypes.ApiserverSource;Merge函数会把合并后的Pod信息输入到s.updates这个channel中;
      • 判断kubelet启动方式runonce和standalone
      • startKubelet主要启动一个goroutine,执行k.Run(podCfg.Updates()),其中podCfg.Updates()返回的是kubetypes.PodUpdate channel,从这个channel里面我们可以获取到Pod信息;主要适用于pods的更新,同步,删除等,都可以往这个podupdatechannel中发送pod
    • 运行程序
      • 启动kl.syncNodeStatus,主要是每隔kl.nodeStatusUpdateFrequency会向kube-apiserver更新本节点的信息;
      • 启动kl.syncNetworkStatus,每隔30s会更新networkplugin状态;
      • 启动kl.updateRuntimeUp,每隔5s会调用kl.setRuntimeSync,同步container runtime(即docker或rkt)的信息;
      • 启动kl.syncNetworkUtil,每隔1s会同步iptables信息;
      • 启动podKiller,负责杀死不要的Pod;
      • 同时启动了几个重要的manager,即kl.statusManager.Start、kl.probeManager.Start、kl.pleg.Start;
      • 最后启动kl.syncLoop(updates, kl);
    • main Loop循环调用,获取pod更新状态
      • 创建一个for循环不停的执行syncLoopIteration,获取的Pod信息;kubelet通过三种方式获取Pod信息,一种是传统的通过watch kube-apiserver获取pod信息,一种是通过文件获取,最后一种是通过http获取,后两种模式下,我们称kubelet运行于standalone模式;
    • Pod信息处理
      • Pod信息处理主要由syncLoopIteration这个函数进行处理,configCh这个channel就是podcfg的updates,即Pod信息的来源;
      • syncLoopIteration函数会从configCh读取Pod信息,然后根据u.Op为ADD、UPDATE、REMOVE、RECONCILE进行处理,分别调用handler.HandlePodAdditions(u.Pods)、handler.HandlePodUpdates(u.Pods)、handler.HandlePodDeletions(u.Pods)、handler.HandlePodReconcile(u.Pods);
    • Pod创建
      • Pod创建通过HandlePodAdditions([]*v1.pods)函数。函数首先将pods按照创建日期排列;
      • 然后按照创建日期依次处理Pod:调用kl.podManager.AddPod(pod),将Pod加入podManager,podManager是一个重要的结构,前面说过的manager都依赖于这个结构体工作。这里还有一个mirrorpod的概念,mirrorpod主要与kubelet运行于standalone模式有关,假如pod是通过file或http的形式获得的,这个pod被称为static pod,k8s会在集群中创建一个对应的mirror pod;
      • 接着函数调用kl.dispatchWork去处理Pod,最后将这个pod加入到probeManager,k8s里面存在两种probe(探针),一种是readiness probe,另一种是liveness probe;
      • 展开函数dispatchWork,该函数会调用kl.podWorkers.UpdatePod;
    • PodWorkers
      • 让我们看看PodWorkers数据结构,podUpdates是一个map类型,每一个Pod的uuid作为key,而UpdatePodOptions的channel作为value;
        image.png
      • UpdatePod方法首先会去检查podUpdates这个map,如果新创建的pod没有update goroutine,它会创建一个go routine,执行函数mannagePodLoop。注意每一个pod都会有一个相应的go routine执行mannagePodLoop,其参数podUpdates这个channel则用来传递pod update的信息;
        image.png
    • manangePodLoop
      • manangePodLoop方法调用syncPodFn函数去同步Pod,syncPodFn这个函数实际上是syncPod函数;(定义在pkg/kubelet/kubelet.go Run方法中)
    • syncPod
      • 如果想要删除pod,立即进行删除并返回
      • 如果正在创建pod,记录pod worker启动延迟
      • 调用generateAPIPodStatus为pod准备v1.PodStatus
      • 如果第一次检测到pod正在运行,记录pod启动延迟
      • 在状态管理器中更新窗格的状态
      • 如果它不应该运行,请杀死它
      • 如果pod是静态pod,并且还没有镜像pod,则创建一个镜像pod
      • 如果pod不存在,则为其创建数据目录
      • 等待卷附加/装载
      • 获取pod的拉取secret
      • 调用容器运行时的SyncPod回调
      • 更新pod的ingress和egress限制
    • 启动容器
      • 拉镜像
      • 创建容器
      • 启动容器
      • 运行post start生命周期钩子(如果声明)–container.Lifecycle
  1. 如何接收到kube-scheduler的调度?

    • 通过kubeletserver提供的http接口调用
  2. 如何做的准入处理?(admission)

  3. 如何生成pod(启动过程,挂载目录,调用cni网络插件)

  4. 如何监控pods状态?

    • 每个pod在启动的时候会生成一个goroutine去一直做liveness判断
    • 启动kl.syncNodeStatus,主要是每隔kl.nodeStatusUpdateFrequency会向kube-apiserver更新本节点的信息;
    • 启动kl.syncNetworkStatus,每隔30s会更新networkplugin状态;
    • 启动kl.updateRuntimeUp,每隔5s会调用kl.setRuntimeSync,同步container runtime(即docker或rkt)的信息;
    • 启动kl.syncNetworkUtil,每隔1s会同步iptables信息;
  5. 如何删除pod

    • 启动podKiller,负责杀死不要的Pod;
    • 同步apiserver中的pod和本地pod状态,不匹配的话会调用syncPod实现pod状态调整,实现pod的CURD操作
  6. 自身健康汇报到kube-apiserver

    • 通过启动一个healthz的路由供外部调用,实现自身的服务检查
  7. list-watch是如何监听api-server中的pod更改?