和oom的对比
首先对比一下 evictionManager 和 oom
* System OOM events本来就是对资源敏感的,它会stall这个Node直到完成了OOM Killing Process。
* 当OOM Killer干掉某些containers之后,kubernetes Scheduler可能很快又会调度一个新的Pod到该Node上或者container 直接在node上restart,马上又会触发该Node上的OOM Killer启动OOM Killing Process,事情可能会没完没了的进行。
接下来看看 evictionManager
* Kubelet通过pro-actively监控并阻止Node上资源的耗尽,一旦触发Eviction Signals,就会直接Fail一个或者多个Pod以回收资源,而不是通过Linux OOM Killer这样本身耗资源的组件进行回收。
* 这样的Eviction Signals的可配置的,可以做到Pro-actively。
* 另外,被Evicted Pods会在其他Node上重新调度,而不会再次触发本Node上的再次Eviction。
工作机制
* kubelet预先监控本节点的资源使用,并且阻止资源被耗尽,这样保证node的稳定性。
kubelet会通过监控Eviction Signal的值,当达到配置的阈值时,就会触发Evict Pods。 kubelet对应的监控周期,就通过cAdvisor的 evictionMonitoringPeriod 配置的,默认10s。
如果一个Node上监控到的Soft Eviction Signals的值,一直在eviction-soft水平线上下波动,那么为了避免 Node Condition 在true和false频繁切换,配置了 EvictionPressureTransitionPeriod 的值。一旦 MemoryPressure Or DiskPressure为True的前提下, 发生了Soft Evction Singal低于Soft Eviction Thresholds的情况,则需要等待配置时间(默认5m)才会切回 false。
* kubelet会预先Fail N(>= 1)个Pod以回收出现紧缺的资源。
如果 eviction pods 回收了一小部分资源就满足了要求,可能随着资源使用的波动或者新的调度Pod使得在该Node上很快又会触发evict pods的动作,这个是非常不划算的。所以在回收资源的时候,不仅要保证比 Eviction Thresholds 低,还要比它低 EvictionMinimumReclaim 所配置的数量。
* kubelet会Fail一个Pod时,会将Pod内所有Containners都kill掉,并把PodPhase设为Failed。
在每一个监控周期内,如果Eviction Thresholds被触及,则:
获取候选Pod
Fail the Pod
等待该Pod被Terminated 如果该Pod由于种种原因没有被成功Terminated,Kubelet将会再选一个Pod进行Fail Operation。其中,Fail Pod的时候,Kubelet是通过调用容器运行时的KillPod接口,如果接口返回True,则认为Fail Pod成功,否则视为失败。
* kubelet通过事先人为设定Eviction Thresholds来触发Eviction动作以回收资源。
Eviction Thresholds 支持绝对值和相对百分比两种形式
* memory.available<10%
* memory.available<1Gi
Soft Eviction Thresholds 指的是,当Eviction Signal中值达到配置的值时,并不会马上触发Kubelet去Evict Pods,而是会等待一个用户配置的grace period之后,再触发。
Hard Eviction Thresholds 就是, 当Eviction Signal中值达到Hard Eviction Thresholds配置的值时,会立刻触发Kubelet去Evict Pods 。 这个值,要设置的比eviction-soft更低才有意义。
代码学习
1. evictionManager 的创建
首先,Kubelet 在初始化的时候,初始化一个 evictionManager 对象;
然后,在初始化 runtime 的时候启动这个 evictionManager;
接下来,看一下 manager 的定义;
managerImpl struct {
clock clock.Clock
config Config // 配置,包括
PressureTransitionPeriod( –eviction-pressure-transition-period)
MaxPodGracePeriodSeconds(–eviction-max-pod-grace-period)
Thresholds(–eviction-hard, –eviction-soft)
KernelMemcgNotification(–experimental-kernel-memcg-notification)
killPodFunc KillPodFunc // evict pod时kill pod的接口,kubelet NewManager的时候,赋值为killPodNow方法
imageGC ImageGC // 当node出现diskPressure condition时,imageGC进行unused images删除操作以回收disk space
containerGC ContainerGC // 使用 containerGC 来对容器进行删除操作
sync.RWMutex
nodeConditions []v1.NodeConditionType
nodeConditionsLastObservedAt nodeConditionsObservedAt
nodeRef *v1.ObjectReference // 记录有关节点的事件
recorder record.EventRecorder // used to measure usage stats on system
summaryProvider stats.SummaryProvider // 提供node和node上所有pods的最新status数据汇总,既NodeStats and []PodStats。
thresholdsFirstObservedAt thresholdsObservedAt // 记录threshold第一次观察到的时间
thresholdsMet []evictionapi.Threshold // 保存已经触发但还没解决的Thresholds,包括那些处于grace period等待阶段的Thresholds。
resourceToRankFunc map[v1.ResourceName]rankFunc // 定义各种Resource进行evict 挑选时的排名方法。
resourceToNodeReclaimFuncs map[v1.ResourceName]nodeReclaimFuncs // 定义各种Resource进行回收时调用的方法。
lastObservations signalObservations // 上一次获取的eviction signal的记录,确保每次更新thresholds时都是按照正确的时间序列进行。
notifiersInitialized bool // 表示threshold notifier是否已经初始化,以确定是否可以利用kernel memcg notification功能来提高evict的响应速度。目前创建manager时该值为false,是否要利用kernel memcg notification,完全取决于kubelet的--experimental-kernel-memcg-notification参数。
dedicatedImageFs *bool
}
NewManager不但返回evictionManager对象,还返回了一个lifecycle.PodAdmitHandler实例evictionAdmitHandler,它其实和evictionManager的内容相同,但是不同的两个实例。evictionAdmitHandler用来kubelet创建Pod前进行准入检查,满足条件后才会继续创建Pod,通过 Admit 方法来检查
这里EvictionManager就影响了 Pod调度的逻辑,在机器有压力的时候,禁止了新的pod产生。
2. evictionManager 的运行
start 方法,启动了一个goroutine,来调用 synchronize 方法,获取已经被驱逐的pods,并等待清理操作完成。
在 synchronize 方法中
注册Evict Pod时各种Resource的排名函数 (对于内存,硬盘的使用量进行排序)
m.resourceToRankFunc = buildResourceToRankFunc(hasImageFs)
注册回收Node Resource的Reclaim函数 (清理所有没有用到的容器和镜像)
m.resourceToNodeReclaimFuncs = buildResourceToNodeReclaimFuncs(m.imageGC, m.containerGC, hasImageFs)
通过makeSignalObservations从cAdvisor中获取Eviction Signal Observation和Pod的StatsFunc(后续对Pods进行Rank时需要用)
observations, statsFunc, err := makeSignalObservations(m.summaryProvider, capacityProvider, activePods)
如果kubelet配置了--experimental-kernel-memcg-notification且为true
if m.config.KernelMemcgNotification && !m.notifiersInitialized {
通过startMemoryThresholdNotifier启动soft & hard memory notification 当system usage第一时间达到soft & hard memory thresholds时,会立刻通知kubelet,并触发evictionManager.synchronize进行资源回收的流程。这样提高了eviction的实时性。(下面调用时候的最后一个参数即handler)
err = startMemoryThresholdNotifier(m.config.Thresholds, observations, false, func(desc string) { m.synchronize(diskInfoProvider, podFunc, capacityProvider)})
err = startMemoryThresholdNotifier(m.config.Thresholds, observations, true, func(desc string) { m.synchronize(diskInfoProvider, podFunc, capacityProvider)})
}
根据从cAdvisor数据计算得到的Observation(observasions)和配置的thresholds通过thresholdsMet计算得到此次Met的thresholds
thresholds = thresholdsMet(thresholds, observations, false)
再根据从cAdvisor数据计算得到的Observation(observasions)和thresholdsMet通过thresholdsMet计算得到已记录但还没解决的thresholds,然后与上一步中的thresholds进行合并。
thresholdsNotYetResolved := thresholdsMet(m.thresholdsMet, observations, true)
根据lastObservations中Signal的时间,对比observasions的中Signal中的时间,过滤thresholds。
thresholds = mergeThresholds(thresholds, thresholdsNotYetResolved)
更新thresholdsFirstObservedAt, nodeConditions
nodeConditionsLastObservedAt := nodeConditionsLastObservedAt(nodeConditions, m.nodeConditionsLastObservedAt, now)
nodeConditions = nodeConditionsObservedSince(nodeConditionsLastObservedAt, m.config.PressureTransitionPeriod, now)
过滤出那些从observed time到now,已经历过grace period时间的thresholds。
thresholds = thresholdsMetGracePeriod(thresholdsFirstObservedAt, now)
更新evictionManager对象的内部数据: nodeConditions,thresholdsFirstObservedAt,nodeConditionsLastObservedAt,thresholds,observations。
m.nodeConditions = nodeConditions
m.thresholdsFirstObservedAt = thresholdsFirstObservedAt
m.nodeConditionsLastObservedAt = nodeConditionsLastObservedAt
m.thresholdsMet = thresholds
m.lastObservations = observations
根据thresholds得到starvedResources
starvedResources := getStarvedResources(thresholds)
进行排序,如果memory属于starvedResources,则memory排序第一
sort.Sort(byEvictionPriority(starvedResources))
取starvedResources排第一的Resource,调用reclaimNodeLevelResources对Node上这种Resource进行资源回收。如果回收完后,available满足thresholdValue+evictionMinimumReclaim,则流程结束,不再evict user-pods
resourceToReclaim := starvedResources[0]
m.reclaimNodeLevelResources(resourceToReclaim, observations)
如果reclaimNodeLevelResources后,还不足以达到要求,则会继续evict user-pods,首先根据前面buildResourceToRankFunc注册的方法对所有active Pods进行排序
rank, ok := m.resourceToRankFunc[resourceToReclaim]
rank(activePods, statsFunc)
按照前面的排序,顺序的调用killPodNow将选出的pod干掉。如果kill某个pod失败,则会跳过这个pod,再按顺序挑下一个pod进行kill。只要某个pod kill成功,就返回结束,也就是说这个流程中,最多只会kill最多一个Pod
for i := range activePods {
m.killPodFunc(pod, status, &gracePeriodOverride)
return []*v1.Pod{pod}
}
其中,资源回收 reclaimNodeLevelResources 就是调用 GC 的方法进行资源的回收。
killPodNow 则调用了 podWorkers.UpdatePod 方法进行pod的驱逐。
一些总结
Kubelet通过Eviction Signal来记录监控到的Node节点使用情况。
Eviction Signal支持:memory.available, nodefs.available, nodefs.inodesFree, imagefs.available, imagefs.inodesFree。
通过设置Hard Eviction Thresholds和Soft Eviction Thresholds相关参数来触发Kubelet进行Evict Pods的操作。
Evict Pods的时候根据Pod QoS和资源使用情况挑选Pods进行Kill。
Kubelet通过eviction-pressure-transition-period防止Node Condition来回切换引起scheduler做出错误的调度决定。
Kubelet通过--eviction-minimum-reclaim来保证每次进行资源回收后,Node的最少可用资源,以避免频繁被触发Evict Pods操作。
当Node Condition为MemoryPressure时,Scheduler不会调度新的QoS Class为BestEffort的Pods到该Node。
当Node Condition为DiskPressure时,Scheduler不会调度任何新的Pods到该Node。
网友评论