Kubelet配置源和垃圾回收流程

在上一篇文章中,概括的划分了kubelet的核心模块,这一篇文章重点来分析一下其中Config和GC在源码中是如何实现的。

Config

这部分其实就是kubelet的业务入口,此处所谓的config不是指kubelet的配置参数,而是指业务(也就是pod)配置的意思。形象的说,当你在/etc/kubernetes/manifests下放上pod的yaml文件时,kubelet就通过config模块来扫描到pod配置文件的内容,然后在kubelet中创建出对应的static pod。同理,当你通过kubectl创建的pod被scheduler调度到该节点时,config模块就会感知到Api-Server上的配置的变动,然后对应的将pod创建出来。

Config关注的源头有三个:

  • SourceFile
    就是启动kubelet指定的静态pod目录

  • SourceURL
    觉得就是SourceFile的web版,具体没有用过

  • SourceApiServer
    就是kube-api-server

该部分代码的大图如下

kubelet-config源码分析

makePodSourceConfig函数首先调用了NewPodConfig函数,用于创建出podConfig。该函数非常重要,里面一层层通过newPodStorage将pod配置放到内存中的存储,它的merge方法用于计算pod最终体现出来到底是添加、删除还是更改或者恢复。按照kubelet启动参数的配置,通过对应的三个config的方法(config.NewSourceFileconfig.NewSourceURL以及config.NewSourceApiserver)分别初始化了三种Config关注的源头。

由于篇幅的原因,图上没有画出,但是却很重要(有助于理解代码)的是:这三个config方法分别运行了goRoutine作为守护进程,用于监听配置的变化。

当有检测到配置变化后,三种源头的handler会做一些处理,然后将pod spec及其操作动作写入到cfg.Channel中。而这个channel刚好就是后续我们在主loop中等到的配置更新的channel。在主loop中,会基于对pod执行的操作,再分走不通的路径去处理pod,最终保障kubelet上的pod与配置一致。

GarbageCollection(GC)

docker容器的本质是宿主机上的一个进程,为了将容器做差异化的封装,docker借助于类似AUFS之类的文件系统做了很多事情。容器停止执行后,这些文件系统并不会自动清除,通过docker ps -a也能够看到这些资源(这是为了下次可以快速启动)。kubelet有一套container gc的方案,专门用于清理宿主机上的非所需容器。

另外,容器镜像较耗存储资源,但是每一台k8s node的存储空间都是有限的,kubelet上运行的pod生命周期可能很短,但是每个pod可能都使用不同的镜像,这就会导致宿主机上会留下很多不再需要的容器镜像,为了将有限的空间腾出来高效利用,kubelet设计了一套image gc的方案。

下图是这部分代码的大致结构:

kubelet-gc源码分析

通过代码可以看到,GC机制即将被eviction替代,在kubelet参数中已经有对应的提示信息。

容器GC

容器GC的业务逻辑主要在(m *kubeGenericRuntimeManager)GarbageCollect中,下面是对m.containerGC.GarbageCollect注释的引用,描述了主要的业务逻辑。

GarbageCollect removes dead containers using the specified container gc policy. Note that gc policy is not applied to sandboxes. Sandboxes are only removed when they are not ready and containing no containers.

GarbageCollect consists of the following steps:

  • gets evictable containers which are not active and created more than gcPolicy.MinAge ago.
  • removes oldest dead containers for each pod by enforcing gcPolicy.MaxPerPodContainer.
  • removes oldest dead containers by enforcing gcPolicy.MaxContainers.
  • gets evictable sandboxes which are not ready and contains no containers.
  • removes evictable sandboxes.

上面注释中提到了三个指标,这里简单介绍一下这三个指标对应的参数:

  • minimum-container-ttl-duration (gcPolicy.MinAge)
    容器结束运行多久之后才能被回收;比如: ‘300ms’, ‘10s’ or ‘2h45m

  • maximum-dead-containers-per-container (gcPolicy.MaxPerPodContainer)
    基于容器为单位,指每个容器最大可以保存多少个已结束的容器,默认是1,负数表示不限制,这些容器会浪费磁盘空间

  • maximum-dead-containers (gcPolicy.MaxContainers)
    基于节点为单位,指节点上最多允许保留多少个已结束的容器,默认是-1,表示不做限制

于是,整个流程串起来就是这样的情景:

  1. 顶层函数会每分钟被调用,触发container gc操作;
  2. 该操作会以container的结束时间是否超过gcPolicy.MinAge为依据,查询出那些满足条件的容器,并组织成为按照pod为key,container列表为值的字典;这一步并没有做实际删除,但是其操作结果为后两部奠定了数据依据;
  3. 对字典中的每个pod的container做处理,找出该pod超过gcPolicy.MaxPerPodContainer的容器,然后对它们按照结束时间排序,执行删除,保障每个pod下已结束的container数满足配置参数;
  4. 经过上一部的删除后,针对node来讲,如果节点上待删除的容器数依然大于gcPolicy.MaxContainers, 就执行反向的运算。把node允许保留的最大容器数平分给每个pod,再按照该标准对每个pod执行一轮删除;
  5. 如果依然还不满足要求的数量(若kubelet有情感的话,已经崩溃掉了),就不再按照pod做key,直接将所有的container拍扁平,按照时间顺序先删除最旧的容器,直到满足总数小于gcPolicy.MaxContainers

镜像GC

分析完了容器的GC,我们再来聊聊镜像GC。kubelet也为镜像的GC提供了对应的配置参数,具体如下:

  • minimum-image-ttl-duration
    最少这么久镜像都未被使用,才允许清理;比如:’300ms’, ‘10s’ or ‘2h45m’.”

  • image-gc-high-threshold
    imageFS磁盘使用率的上限,当达到该值时触发镜像清理。默认值为 90%

  • image-gc-low-threshold
    imageFS磁盘使用率的下限,每次清理直到使用率低于这个值或者没有可以清理的镜像了才会停止。默认值为 80%

具体流程比较简单;

  1. 与容器GC比较起来,镜像GC顶层函数被触发的周期更长,为5分钟触发一次。
  2. 通过cadvisor获取到节点上imageFS的详情,得到capacity、avaiable,据此推算磁盘使用率等信息;
  3. 当磁盘使用率大于image-gc-high-threshold参数中指定的值时,触发镜像的GC操作;
  4. 找出当前未被使用的镜像列表并按时间排序,过滤掉那些小于minimum-image-ttl-duration的镜像;
  5. 正式从节点上删除镜像;每次都比较是否删除的镜像数以满足所需释放的bytesToFree,若满足就停止删除。

值得一提的是,这里的image-gc-low-threshold并非用于每次执行删除镜像时重复的去比较是否满足条件,而是在在触发GC的时候,用于计算需要删除多少bytes即bytesToFree

0%