在上一篇文章中,我们提到container GC在后续kuberntes版本会放入eviction manager中来处理。今天我们就来简单的分析一下kubelet eviction的实现机制。
理论基础
kubelet支持两种eviction的方式,每一种方式有支持多种指标类型。
驱逐方式
--eviction-hard
只要达到eviction中特定类型定义的指标(此处为峰值就干掉),直接干掉pod或者image--eviction-soft
驱逐条件达到且持续指定时间--eviction-soft-grace-period
才开始真正干掉pod;这里还有一个真正用于kill pod优雅退出的时间指标--eviction-max-pod-grace-period
kubelet的两种驱逐方式均支持以下指标类型:
指标类型 | 用途 |
---|---|
memory.available | 可用的内存 |
nodefs.available | 系统可用的volume空间 |
nodefs.inodesFree | inode可用的volume空间 |
imagefs.available | 系统可用的镜像空间 |
imagefs.inodesFree | inode可用的镜像空间 |
allocatableMemory.available | 可分配给pod的内存大小 |
allocatableNodeFs.available | 可分配给pod的存储大小 |
避免波动
内部
--eviction-minimum-reclaim
在执行驱逐的时候,为了避免资源在thresholds指标的上下波动,kubelet引入了该参数。该参数定义了每次驱逐操作必须至少清理出多少资源才能够停止执行。(比如:imagefs.available=2Gi)外部
--eviction-pressure-transition-period
该参数定义了 kubelet 多久才上报api-server当前节点的状态,这将避免scheduler将驱逐的pod立即重新调度到该节点,再次触发资源压力的死循环。
Pod优先级
1 | ~ kc explain pod.spec.priority |
实现逻辑
条件准备
eviction manager是在UpdateRuntimeUp
中被启动的,该函数只会调用一次initializeRuntimeDependentModules
。在开始eviction manager前,先启动了cadvisor和container manager。其中cadvisor为container manager获取系统的资源使用状况,包括内存和磁盘信息等;container manager起来之后,会校验系统上是否有可分配资源,其算法就是:
1 | available = capacity - system reserved - kube reserved - hard eviction threshold(百分比或者资源量) |
最后,就进入到我们今天的主角,启动eviction manager。具体流程如下图:
evictionManager的start函数会启动一个goRoutine来处理eviction(synchronize函数);如果synchronize函数没有找到需要eviction的pods的话,goRoutine中会等待10秒后再继续调用synchronize。
synchronize函数
eviction的核心逻辑在synchronize函数里实现,我将其繁琐的操作整理成以下几个大的方面,具体如下:
信息获取
- 对应不同的资源类型,生成对应的排序函数(用于排序pod)和资源回收(处理资源的释放操作)的函数;
- 从cadvisor采集的数据中,获取汇总的统计信息;并从汇总信息中转换出observations数据;
计算资源
- 基于用户配置的thresholds与observations比较,计算出满足超标的thresholdsMet,我们叫它
A
; - 将先前扫描中超标,但是未满足grace-period时间的thresholds与observations比较,得到这次扫描仍然超标的thresholds(此处引入了eviction-minimum-reclaim参数计算),我们叫它
B
; - 最后将
B
与上一步的结果A
做合并,得到最终的thresholds;
- 基于用户配置的thresholds与observations比较,计算出满足超标的thresholdsMet,我们叫它
nodeCondition计算
- 基于thresholds计算出nodeCondition,该过程会比较出现该nodeCondition的持续时间是否超过用户配置的参数eviction-pressure-transition-period,如果不超过就将其零时存起来供下次再比较;
释放资源
- 通过thresholds获取到当前饥饿的资源列表,并对资源做排序,找出最饥饿的资源;
- 在node级别,如果该资源有节点级别的资源释放函数,直接在节点级别释放资源;
- 在Pod级别针,使用该资源对应的排序函数对当前active的pod进行排序;
- 针对排序后的active pods列表,从中获取最前面的pod执行killPodFunc;
Pod排序算法
下面来重点分析一下在“计算资源”阶段使用到的pod基于饥饿资源的排序算法:
内存
- 按照pod使用的内存是否超过pod对内存的request值;
- 按照设置的pod优先级;
- 按照pod消耗的内存数值与request差值(标准为 consume-request)
1
2
3func rankMemoryPressure(pods []*v1.Pod, stats statsFunc) {
orderedBy(exceedMemoryRequests(stats), priority, memory(stats)).Sort(pods)
}磁盘/镜像
这里需要首先提到一个公共的排序函数,因为在基于磁盘和镜像对pod的排序中都使用到了它:
1
2
3return func(pods []*v1.Pod, stats statsFunc) {
orderedBy(exceedDiskRequests(stats, fsStatsToMeasure, diskResource), priority, disk(stats, fsStatsToMeasure, diskResource)).Sort(pods)
}该函数的逻辑同内存,首先比较超过request的值,然后是pod优先级,最后是资源使用量与request的差值。
磁盘和镜像的排序其实比较的指标都是文件系统,这里就需要区分镜像是否使用了独立的文件系统(ImageFs)。
- 使用ImageFs
资源 | 评估指标 |
---|---|
nodeFS | logs + localVolume |
imageFS | rootfs |
- 未使用ImageFs
资源 | 评估指标 |
---|---|
nodeFS | rootfs + logs + localVolume |
imageFS | rootfs + logs + localVolume |
三种指标的描述如下:
localVolumeSource
identifies stats for pod local volume sources.logs
identifies stats for pod logs.rootfs
identifies stats for pod container writable layers.
最新版本
由于引入了用户自定义的priority,再结合QOS,调度逻辑如下:
- 对于BestEffort和Burstable的pod,基于priority以及所使用资源量来做驱逐;
- 理论上Guaranteed类别的pod需要保障的,但是一旦系统服务受到资源稀缺影响时,依然按照priority和所用资源量来对齐做驱逐;