kubernetes 中的调度队列
kube-scheduler 中调度队列是一个重要的组成部分。它可以控制pod按照合适的顺序、合适的时间被scheduler 核心代码进行调度。pod可能需要在某种特定的条件才能进行调度,比如PV, pod的亲和度反亲和度或者容忍node上的污点,这时候就需要延迟调度的机制,只有等待所有条件具备才进入调度循环。kube-scheduler 中有三种不同的调度队列:
- activeQ: 是一个堆类型的队列,scheduler 能够主动的获取到需要调度的pod, 堆的顶部始终是优先级最高的pod。
- podBackoffQ: 也是一个堆类型的队列,根据backoff 的时间进行排序。backoff结束时将会从本队列中弹出,重新回到activeQ。
- unschedulableQ : 一个map,记录了那些经过重试但是最终决定无法调度的pod.
kubernetst/pkg/scheduler/internal/queue/scheduling_queue.go
type PriorityQueue struct {
...
//activeQ是一种堆结构,调度器会主动查看以找到要调度的pods。堆的头部是最高优先级的pod。
activeQ *heap.Heap
// podBackoffQ是一个按回退期限排序的堆。在调度器查看activeQ之前,backoff 结束的Pods会从堆中弹出
podBackoffQ *heap.Heap
// unschedulableQ 是一个map,保存了那些已经尝试过调度,但是失败了的pod
unschedulableQ *UnschedulablePodsMap
....
}
在kube-scheduler 启动的时候还会启动两个协程,负责将pod 移动到activeQ。

flushBackoffQCompleted每秒执行一次, 检查所有在backoffQ的pod backoff 时候结束,若结束移动到activeQ
func (p *PriorityQueue) flushBackoffQCompleted() {
p.lock.Lock()
defer p.lock.Unlock()
for {
// 获取一个pod
rawPodInfo := p.podBackoffQ.Peek()
if rawPodInfo == nil {
return
}
pod := rawPodInfo.(*framework.QueuedPodInfo).Pod
// 获取backoff time
boTime := p.getBackoffTime(rawPodInfo.(*framework.QueuedPodInfo))
// 检查backoff是否结束
if boTime.After(p.clock.Now()) {
return
}
// 从backoffQ 删除
_, err := p.podBackoffQ.Pop()
if err != nil {
klog.Errorf("Unable to pop pod %v from backoff queue despite backoff completion.", nsNameForPod(pod))
return
}
// 添加到activeQ
p.activeQ.Add(rawPodInfo)
metrics.SchedulerQueueIncomingPods.WithLabelValues("active", BackoffComplete).Inc()
defer p.cond.Broadcast()
}
}
- flushUnschedulableQLeftover: 每隔30秒启动一次,查看pod 最近一次调度时间是否已经超过60 若是则移动到activeQ 或者backoffQ.
func (p *PriorityQueue) flushUnschedulableQLeftover() {
p.lock.Lock()
defer p.lock.Unlock()
var podsToMove []*framework.QueuedPodInfo
currentTime := p.clock.Now()
for _, pInfo := range p.unschedulableQ.podInfoMap {
lastScheduleTime := pInfo.Timestamp
// 距离上次调度时间是否超过60秒
if currentTime.Sub(lastScheduleTime) > unschedulableQTimeInterval {
podsToMove = append(podsToMove, pInfo)
}
}
if len(podsToMove) > 0 {
// 移动到activeQ 或者backoffQ
p.movePodsToActiveOrBackoffQueue(podsToMove, UnschedulableTimeout)
}
}
如图pod 在kube-scheduler的调度周期
active queue
在这个队列默认按照优先度的大小进行排序,也可以通过QueueSort 插件进行扩展。每当新建一个pod如果spec.nodeName 为空,它将会被加入到队列中。每一个调度循环都会取出该队列的第一个pod进行调度,经过调度算法出现任何失败都会进入Unschedulable queue. 或者在同一时间接收到moveRequest 也会被调度到backOffQ 。如果调度过程没有任何错误,则将该pod从队列中删除。
kube-scheduler启动时也会启动informer,来监听pod的各种事件。例如监听到了pod 添加事件,pod将会将其加入到ActiveQ 中。
kubernetes/pkg/scheduler/eventhandlers.go
func addAllEventHandlers(
sched *Scheduler,
informerFactory informers.SharedInformerFactory,
podInformer coreinformers.PodInformer,
) {
spc.nodeName 为空的pod
podInformer.Informer().AddEventHandler(
cache.FilteringResourceEventHandler{
FilterFunc: func(obj interface{}) bool {
switch t := obj.(type) {
case *v1.Pod:
// 判断spc.nodeName 字段是否为空
return assignedPod(t)
case cache.DeletedFinalStateUnknown:
if pod, ok := t.Obj.(*v1.Pod); ok {
return assignedPod(pod)
}
utilruntime.HandleError(fmt.Errorf("unable to convert object %T to *v1.Pod in %T", obj, sched))
return false
default:
utilruntime.HandleError(fmt.Errorf("unable to handle object in %T: %T", sched, obj))
return false
}
},
Handler: cache.ResourceEventHandlerFuncs{
// pod 添加事件
AddFunc: sched.addPodToCache,
// pod 更新事件
UpdateFunc: sched.updatePodInCache,
// pod 删除事件
DeleteFunc: sched.deletePodFromCache,
},
},
)
// spc.nodeName 不为空, 且指定了调度器
podInformer.Informer().AddEventHandler(
cache.FilteringResourceEventHandler{
FilterFunc: func(obj interface{}) bool {
switch t := obj.(type) {
case *v1.Pod:
return !assignedPod(t) && responsibleForPod(t, sched.Profiles)
case cache.DeletedFinalStateUnknown:
if pod, ok := t.Obj.(*v1.Pod); ok {
return !assignedPod(pod) && responsibleForPod(pod, sched.Profiles)
}
utilruntime.HandleError(fmt.Errorf("unable to convert object %T to *v1.Pod in %T", obj, sched))
return false
default:
utilruntime.HandleError(fmt.Errorf("unable to handle object in %T: %T", sched, obj))
return false
}
},
Handler: cache.ResourceEventHandlerFuncs{
AddFunc: sched.addPodToSchedulingQueue,
UpdateFunc: sched.updatePodInSchedulingQueue,
DeleteFunc: sched.deletePodFromSchedulingQueue,
},
},
)
...
}
backoff queue
该队列是保证一个pod调度失败后连续的重试添加了事件间隔得到缓冲。backoff 最短的pod将会在队列的最前面。失败次数的越多backoff的时长越大。该队列中的pod会通过上面所说的 flushBackoffQCompleted 重新回到activeQ 中。
backoff 的时长算法是根据默认时长x 失败次数y, x+2^y 计算出来的。
例如一个pod 重试了3次 默认时长为1s,那么它将需要等待9s 1+2^3 = 9s
当然backoff 不会超过最大值 默认配置是10s.
// getBackoffTime returns the time that podInfo completes backoff
func (p *PriorityQueue) getBackoffTime(podInfo *framework.QueuedPodInfo) time.Time {
duration := p.calculateBackoffDuration(podInfo)
backoffTime := podInfo.Timestamp.Add(duration)
return backoffTime
}
// calculateBackoffDuration is a helper function for calculating the backoffDuration
// based on the number of attempts the pod has made.
func (p *PriorityQueue) calculateBackoffDuration(podInfo *framework.QueuedPodInfo) time.Duration {
duration := p.podInitialBackoffDuration
for i := 1; i < podInfo.Attempts; i++ {
duration = duration * 2
if duration > p.podMaxBackoffDuration {
return p.podMaxBackoffDuration
}
}
return duration
}
unschedulable queue
这里记录了所有调度失败的pod(未接收到moveRequest 请求)。
move request
move request 会触发一个事件负责将pod充unschedulable queue 移动到backoff queue 或者active queue。 集群事件将会异步触发一个事件将所有处于该调度的pod重新可以调度。集群事件包括:pod本身改变,pv,pvc ,storage class, CSI node 的变化。例如一个pod A正在被调度,另外一个和A亲和度匹配的pod B在unscheduled queue 中,那么B将会通过move request 重新回到 ActiveQ 重新尝试调度。
1023

被折叠的 条评论
为什么被折叠?



