preempt
kube-scheduler源码分析(六)之 preempt
以下代码分析基于
kubernetes v1.12.0版本。
本文主要分析调度中的抢占逻辑,当pod不适合任何节点的时候,可能pod会调度失败,这时候可能会发生抢占。抢占逻辑的具体实现函数为Scheduler.preempt。
1. 调用入口
当pod不适合任何节点的时候,可能pod会调度失败。这时候可能会发生抢占。
scheduleOne函数中关于抢占调用的逻辑如下:
此部分的代码位于/pkg/scheduler/scheduler.go
// scheduleOne does the entire scheduling workflow for a single pod. It is serialized on the scheduling algorithm's host fitting.
func (sched *Scheduler) scheduleOne() {
...
suggestedHost, err := sched.schedule(pod)
if err != nil {
// schedule() may have failed because the pod would not fit on any host, so we try to
// preempt, with the expectation that the next time the pod is tried for scheduling it
// will fit due to the preemption. It is also possible that a different pod will schedule
// into the resources that were preempted, but this is harmless.
if fitError, ok := err.(*core.FitError); ok {
preemptionStartTime := time.Now()
// 执行抢占逻辑
sched.preempt(pod, fitError)
metrics.PreemptionAttempts.Inc()
metrics.SchedulingAlgorithmPremptionEvaluationDuration.Observe(metrics.SinceInMicroseconds(preemptionStartTime))
metrics.SchedulingLatency.WithLabelValues(metrics.PreemptionEvaluation).Observe(metrics.SinceInSeconds(preemptionStartTime))
}
return
}
...
} 其中核心代码为:
2. Scheduler.preempt
当pod调度失败的时候,会抢占低优先级pod的空间来给高优先级的pod。其中入参为调度失败的pod对象和调度失败的err。
抢占的基本流程如下:
判断是否有关闭抢占机制,如果关闭抢占机制则直接返回。
获取调度失败pod的最新对象数据。
执行抢占算法
Algorithm.Preempt,返回预调度节点和需要被剔除的pod列表。将抢占算法返回的node添加到pod的
Status.NominatedNodeName中,并删除需要被剔除的pod。当抢占算法返回的node是nil的时候,清除pod的
Status.NominatedNodeName信息。
整个抢占流程的最终结果实际上是更新Pod.Status.NominatedNodeName属性的信息。如果抢占算法返回的节点不为空,则将该node更新到Pod.Status.NominatedNodeName中,否则就将Pod.Status.NominatedNodeName设置为空。
2.1. preempt
preempt的具体实现函数:
此部分的代码位于/pkg/scheduler/scheduler.go
以下对preempt的实现分段分析。
如果设置关闭抢占机制,则直接返回。
获取当前pod的最新状态。
GetUpdatedPod的实现就是去拿pod的对象。
接着执行抢占的算法。抢占的算法返回预调度节点的信息和因抢占被剔除的pod的信息。具体的抢占算法逻辑下文分析。
将预调度节点的信息更新到pod的Status.NominatedNodeName属性中。
SetNominatedNodeName的具体实现为:
接着删除因抢占而需要被剔除的pod。
PodPreemptor.DeletePod的具体实现就是删除具体的pod。
如果抢占算法得出的node对象为nil,则将pod的Status.NominatedNodeName属性设置为空。
RemoveNominatedNodeName的具体实现如下:
2.2. NominatedNodeName
Pod.Status.NominatedNodeName的说明:
nominatedNodeName是调度失败的pod抢占别的pod的时候,被抢占pod的运行节点。但在剔除被抢占pod之前该调度失败的pod不会被调度。同时也不保证最终该pod一定会调度到nominatedNodeName的机器上,也可能因为之后资源充足等原因调度到其他节点上。最终该pod会被加到调度的队列中。
其中加入到调度队列的具体过程如下:
addPodToSchedulingQueue:
PriorityQueue.Add:
addNominatedPodIfNeeded:
NominatedNodeName:
3. genericScheduler.Preempt
抢占算法依然是在ScheduleAlgorithm接口中定义。
Preempt的具体实现为genericScheduler结构体。
Preempt的主要实现是找到可以调度的节点和上面因抢占而需要被剔除的pod。
基本流程如下:
根据调度失败的原因对所有节点先进行一批筛选,筛选出潜在的被调度节点列表。
通过
selectNodesForPreemption筛选出需要牺牲的pod和其节点。基于拓展抢占逻辑再次对上述筛选出来的牺牲者做过滤。
基于上述的过滤结果,选择一个最终可能因抢占被调度的节点。
基于上述的候选节点,找出该节点上优先级低于当前被调度pod的牺牲者pod列表。
完整代码如下:
此部分代码位于pkg/scheduler/core/generic_scheduler.go
以下对genericScheduler.Preempt分段进行分析。
3.1. selectNodesForPreemption
selectNodesForPreemption并行地所有节点中找可能被抢占的节点。
selectNodesForPreemption主要基于selectVictimsOnNode构造一个checkNode的函数,然后并发执行该函数。
selectNodesForPreemption具体实现如下:
3.1.1. selectVictimsOnNode
selectVictimsOnNode找到应该被抢占的给定节点上的最小pod集合,以便给调度失败的pod安排足够的空间。该函数最终返回的是一个pod的数组。当有更低优先级的pod可能被选择的时候,较高优先级的pod不会被选入该待剔除的pod集合。
基本流程如下:
先检查当该节点上所有低于预被调度pod优先级的pod移除后,该pod能否被调度到当前节点上。
如果上述检查可以,则将该节点的所有低优先级pod按照优先级来排序。
3.2. processPreemptionWithExtenders
processPreemptionWithExtenders基于selectNodesForPreemption选出的牺牲者进行扩展的抢占逻辑继续筛选牺牲者。
processPreemptionWithExtenders完整代码如下:
3.3. pickOneNodeForPreemption
pickOneNodeForPreemption从筛选出的node中再挑选一个节点作为最终调度节点。
pickOneNodeForPreemption完整代码如下:
3.4. getLowerPriorityNominatedPods
getLowerPriorityNominatedPods的基本流程如下:
获取候选节点上的pod列表。
获取待调度pod的优先级值。
遍历该节点的pod列表,如果低于待调度pod的优先级则放入低优先级pod列表中。
genericScheduler.Preempt中相关代码如下:
getLowerPriorityNominatedPods代码如下:
此部分代码位于pkg/scheduler/core/generic_scheduler.go
4. 总结
4.1. Scheduler.preempt
当pod调度失败的时候,会抢占低优先级pod的空间来给高优先级的pod。其中入参为调度失败的pod对象和调度失败的err。
抢占的基本流程如下:
判断是否有关闭抢占机制,如果关闭抢占机制则直接返回。
获取调度失败pod的最新对象数据。
执行抢占算法
Algorithm.Preempt,返回预调度节点和需要被剔除的pod列表。将抢占算法返回的node添加到pod的
Status.NominatedNodeName中,并删除需要被剔除的pod。当抢占算法返回的node是nil的时候,清除pod的
Status.NominatedNodeName信息。
整个抢占流程的最终结果实际上是更新Pod.Status.NominatedNodeName属性的信息。如果抢占算法返回的节点不为空,则将该node更新到Pod.Status.NominatedNodeName中,否则就将Pod.Status.NominatedNodeName设置为空。
4.2. genericScheduler.Preempt
Preempt的主要实现是找到可以调度的节点和上面因抢占而需要被剔除的pod。
基本流程如下:
根据调度失败的原因对所有节点先进行一批筛选,筛选出潜在的被调度节点列表。
通过
selectNodesForPreemption筛选出需要牺牲的pod和其节点。基于拓展抢占逻辑再次对上述筛选出来的牺牲者做过滤。
基于上述的过滤结果,选择一个最终可能因抢占被调度的节点。
基于上述的候选节点,找出该节点上优先级低于当前被调度pod的牺牲者pod列表。
参考:
最后更新于
这有帮助吗?