Scheduler
Scheduler 是整个 kube-scheduler
的一个 structure,提供了 kube-scheduler
运行所需的组件。
|
|
作为实际执行的两个核心,SchedulingQueue
,与 scheduleOne
将会分析到这两个
SchedulingQueue
在知道 kube-scheduler
初始化过程后,需要对 kube-scheduler
的整个 structure 和 workflow 进行分析
在 Run 中,运行的是 一个 SchedulingQueue
与 一个 scheduleOne
,从结构上看是属于 Scheduler
|
|
SchedulingQueue 是一个队列的抽象,用于存储等待调度的Pod。该接口遵循类似于 cache.FIFO 和 cache.Heap 的模式。
|
|
而 PriorityQueue 是 SchedulingQueue
的实现,该部分的核心构成是两个子队列与一个数据结构,即 activeQ
、backoffQ
和 unschedulablePods
activeQ
:是一个 heap 类型的优先级队列,是 sheduler 从中获得优先级最高的Pod进行调度backoffQ
:也是一个 heap 类型的优先级队列,存放的是不可调度的PodunschedulablePods
:保存确定不可被调度的Pod
|
|
在New scheduler 时可以看到会初始化这个queue
|
|
而 NewSchedulingQueue 则是初始化这个 PriorityQueue
|
|
了解了Queue的结构,就需要知道 入队列与出队列是在哪里操作的。在初始化时,需要注册一个 addEventHandlerFuncs
这个时候,会注入三个动作函数,也就是controller中的概念;而在AddFunc中可以看到会入队列。
注入是对 Pod 的informer注入的,注入的函数 addPodToSchedulingQueue 就是入栈
|
|
而这个 SchedulingQueue
的实现就是 PriorityQueue
,而Add中则对 activeQ进行的操作
|
|
在上面看 scheduler 结构时,可以看到有一个 nextPod的,nextPod就是从队列中弹出一个pod,这个在scheduler 时会传入 MakeNextPodFunc 就是这个 nextpod
|
|
而这个 queue.Pop()
对应的就是 PriorityQueue
的 Pop() ,在这里会将作为 activeQ 的消费端
|
|
在上面入口部分也看到了,scheduleOne 和 scheduler,scheduleOne 就是去消费一个Pod,他会调用 NextPod,NextPod就是在初始化传入的 MakeNextPodFunc
,至此回到对应的 Pop来做消费。
schedulerOne是为一个Pod做调度的流程。
|
|
调度上下文
当了解了scheduler结构后,下面分析下调度上下文的过程。看看扩展点是怎么工作的。这个时候又需要提到官网的调度上下文的图。
而 scheduler 对于调度上下文来就是这个 scheduleOne
,下面就是看这个调度上下文
Sort
Sort
插件提供了排序功能,用于对在调度队列中待处理 Pod 进行排序。一次只能启用一个队列排序。
在进入 scheduleOne
后,NextPod
从 activeQ
中队列中得到一个Pod,然后的 frameworkForPod
会做打分的动作就是调度上下文的第一个扩展点 sort
|
|
回顾,因为在New scheduler时会初始化这个 sort 函数
|
|
preFilter
preFilter作为第一个扩展点,是用于在过滤之前预处理或检查 Pod 或集群的相关信息。这里会终止调度
|
|
schedulePod 尝试将给定的 pod 调度到节点列表中的节点之一。如果成功,它将返回节点的名称。
|
|
findNodesThatFitPod 会执行对应的过滤插件来找到最适合的Node,包括备注,以及方法名都可以看到,这里运行的插件😁😁,后面会分析算法内容,只对workflow学习。
|
|
filter
filter插件相当于调度上下文中的 Predicates
,用于排除不能运行 Pod 的节点。Filter 会按配置的顺序进行调用。如果有一个filter将节点标记位不可用,则将 Pod 标记为不可调度(即不会向下执行)。
对于代码中来讲,filter还是处于 findNodesThatFitPod 函数中,findNodesThatPassFilters
就是获取到 FN,即可行节点,而这个过程就是 filter 扩展点
|
|
Postfilter
当没有为 pod 找到FN时,该插件会按照配置的顺序进行调用。如果任何postFilter
插件将 Pod 标记为schedulable,则不会调用其余插件。即 filter
成功后不会进行这步骤,那我们来验证下这里把😊
还是在 scheduleOne 中,当我们运行的 SchedulePod 完成后(成功或失败),这时会返回一个err,而 postfilter
会根据这个 err进行选择执行或不执行,符合官方给出的说法。
|
|
PreScore,Score
可用于进行预Score工作,作为通知性的扩展点,会在在filter完之后直接会关联 preScore 插件进行继续工作,而不是返回,如果配置的这些插件有任何一个返回失败,则Pod将被拒绝。
|
|
priorityNodes 会通过配置的插件给Node打分,并返回每个Node的分数,将每个插件打分结果计算总和获得Node的分数,最后获得节点的加权总分数。
|
|
Reserve
Reserve 因为绑定事件时异步发生的,该插件是为了避免Pod在绑定到节点前时,调度到新的Pod,使节点使用资源超过可用资源情况。如果后续阶段发生错误或失败,将触发 UnReserve
回滚(通知性扩展点)。这也是作为调度周期中最后一个状态,要么成功到 postBind
,要么失败触发 UnReserve
。
|
|
permit
Permit 插件可以阻止或延迟 Pod 的绑定
|
|
Binding Cycle
在选择好 FN 后则做一个假设绑定,并更新到cache中,接下来回去执行真正的bind操作,也就是 binding cycle
|
|
调度上下文中的失败流程
上面说到的都是正常的请求,下面会对失败的请求是如何重试的进行分析,而 scheduler 中关于失败处理方面相关的属性会涉及到上面 scheduler 结构中的 backoffQ
与 unschedulablePods
backoffQ
:也是一个 heap 类型的优先级队列,存放的是不可调度的PodunschedulablePods
:保存确定不可被调度的Pod,一个map类型
backoffQ 与 unschedulablePods 会在初始化 scheduler 时初始化,
|
|
对于初始化 backoffQ 会产生的两个函数,getBackoffTime 与 calculateBackoffDuration
|
|
对于整个故障错误会按照如下流程进行,在初始化 scheduler 会注册一个 Error 函数,这个函数用作对不可调度Pod进行处理,实际上被注册的函数是 MakeDefaultErrorFunc。这个函数将作为 Error 函数被调用。
|
|
而在 调度周期中,也就是 scheduleOne 可以看到,每个扩展点操作失败后都会调用 handleSchedulingFailure 而该函数,使用了注册的 Error 函数来处理Pod
|
|
来到了注册的 Error 函数 MakeDefaultErrorFunc
|
|
下面来到 AddUnschedulableIfNotPresent
,这个也是操作 backoffQ
和 unschedulablePods
的真正的动作
AddUnschedulableIfNotPresent
函数会吧无法调度的 pod 插入队列,除非它已经在队列中。通常情况下,PriorityQueue
将不可调度的 Pod 放在 unschedulablePods
中。但如果最近有 move request,则将 pod 放入 podBackoffQ
中。
|
|
在启动 scheduler 时,会将这两个队列异步启用两个loop来操作队列。表现在 Run()
|
|
可以看到 flushBackoffQCompleted 作为 BackoffQ
实现;而 flushUnschedulablePodsLeftover 作为 UnschedulablePods
实现。
flushBackoffQCompleted
是用于将所有已完成回退的 pod 从 backoffQ
移到 activeQ
中
|
|
flushUnschedulablePodsLeftover
函数用于将在 unschedulablePods
中的存放时间超过 podMaxInUnschedulablePodsDuration
值的 pod 移动到 backoffQ
或 activeQ
中。
podMaxInUnschedulablePodsDuration
会根据配置传入,当没有传入,也就是使用了 Deprecated 那么会为5分钟。
|
|
对于 flushUnschedulablePodsLeftover
就是做一个时间对比,然后添加到对应的队列中
|
|
总结调度上下文流程
- 在构建一个 scheduler 时经历如下步骤:
- 准备cache,informer,queue,错误处理函数等
- 添加事件函数,会监听资源(如Pod),当有变动则触发对应事件函数,这是入站
activeQ
- 构建完成后会 run,run时会run一个
SchedulingQueue
,这个是作为不可调度队列BackoffQ
UnschedulablePods
- 不可调度队列会根据注册时定期消费队列中Pod将其添加到
activeQ
中
- 启动一个
scheduleOne
的loop,这个是调度上下文中所有的扩展点的执行,也是activeQ
的消费端scheduleOne
获取 pod- 执行各个扩展点,如果出错则 Error 函数
MakeDefaultErrorFunc
将其添加到不可调度队列中 - 回到不可调度队列中消费部分