kubernetes源码-kube-scheduler 原理和源码分析(二)
kube-scheduler 原理和源码分析,源码为 kubernetes 的 release-1.22 分支 .
写在前面
kube-scheduler 的一些核心算法和逻辑,估计得分多篇来看,它涉及的内容比较多,自己语文又不好,可能也不太好总结。
控制器入口
继上篇我们看到了 sched.Run() 接口,它实际是调用 pkg/scheduler/scheduler.go 的 Run() 方法来的。
-
启动 Scheduler 的队列。队列有三种,activeQ 、 backoffQ 和 unschedulableQ 。
-
Scheduler 启动的时候所有等待被调度的 pod 都会进入 activieQ,activeQ 会按照 Pod 的 priority 进行排序,Scheduler 会从 activeQ 获取一个 pod 进行执行调度流程,当调度失败之后会直接根据情况选择进入 unschedulableQ 或者 backoffQ,如果在当前 pod 调度期间 Node Cache、pod Cache 等 Scheduler Cache 有变化就进入 backoffQ,否则进入 unschedulableQ 。
-
使用 wait.UntilWithContext() 函数去运行 sched.scheduleOne 接口,sched.scheduleOne 通过 sched.NextPod() (实际是调用 queue.Pop() )从队列弹出一个待调度的 pod 来执行调度,直到从队列拿不出需要调度的 pod 时,则阻塞等待 pod 的到来。
-
调度器退出,关闭 Scheduler 的队列。
|
|
scheduleOne()
顾名思义,每次拿出一个 pod 来进行调度,这里是核心逻辑。
我们之前使用调度框架实现自定义调度器的时候,我们了解到了调度器的拓展点知识点,在这里也一样,Scheduler 也是从这些拓展点上面一个个去实现它的逻辑的。
流程大体是这样的: PreFilter –> Filter –> PostFilter –> PreScore –> Score –> NormalizeScore –> Reserve –> Permit –> PreBind –> Bind –> PostBind 。
-
进入 scheduleOne() 函数。
-
检查 pod 是否存在指定的调度器名称字段 pod.Spec.SchedulerName ,必须要有。
-
检查 pod 是否需要跳过调度。处于正在被删除状态的 pod 跳过本次调度。 pod 在 AssumedPod 缓存里面,也跳过调度(AssumedPod,即完成调度后的 pod 会被加入到这个集合里面)。
-
初始化一个空的 podsToActivate 结构体,用于存放即将被调度的 pod 的信息。
-
调用 sched.Algorithm.Schedule() 接口开始对 pod 进行调度计算。
|
|
sched.Algorithm.Schedule()
我们先进入到 sched.Algorithm.Schedule() 接口看看。这里需要展开讲一下。
-
g.snapshot()
给 Scheduler 更新缓存,更新节点信息缓存、节点上面有哪些 pods 的缓存。
-
如果节点为 0 个,则说明 pod 无法被调度。
-
findNodesThatFitPod()
这里运行已经被注册过的 Prefilter 和 Filter 插件,需要展开讲讲。
-
如果 feasibleNodes 的数量为 1 ,那就是只有一个节点可以调度,如果是 0 ,则说明没有节点可以调度。
-
prioritizeNodes()
运行已经被注册过的打分插件给节点打分,这里后面也需要展开讲讲。
-
g.selectHost() 挑选出打分最高的一个节点,如果有多个最高打分的节点,则随机从里面选出一个。
|
|
接下来展开讲讲 g.findNodesThatFitPod() 。
-
prefilter阶段,运行 prefilter 插件。
-
fwk.RunPreFilterPlugins()
接口预处理 pod 的相关信息。实际上,这里就是 Prefilter 阶段的逻辑,主要对 node 上的 pods 进行 pod 亲和性检查等过滤性操作。
-
g.evaluateNominatedNode()
在 1.22 版本以后,调度器还默认开启了抢占特性: pod 被创建后会进入队列等待调度。 调度器从队列中挑选一个 pod 并尝试将它调度到某个节点上。 如果没有找到满足 pod 的所指定的所有要求的节点,则触发对悬决 pod 的抢占逻辑。 让我们将悬决 pod 称为 P。抢占逻辑试图找到一个节点, 在该节点中删除一个或多个优先级低于 P 的 pod,则可以将 P 调度到该节点上。 如果找到这样的节点,一个或多个优先级较低的 pod 会被从节点中驱逐。 被驱逐的 pod 消失后,P 可以被调度到该节点上。
-
-
g.findNodesThatPassFilters() 这里是 filter 阶段,在这个阶段运行 filter 插件。
-
g.numFeasibleNodesToFind()
如果节点的数量在 100 以内或,percentageOfNodesToScore 为 100% ,则返回所有节点参与调度。
percentageOfNodesToScore 默认值是 50 ,节点在 100 以内,则是 100% ,节点在 5000 时,取 10% ,并随集群节点数增对,该数值不停减少,最小取值是 5% 。
-
!fwk.HasFilterPlugins()
如果调度器没有 Filter 插件,如果集群节点大于 100 的情况下,每次都返回前 100 个节点,是不公平的,这里会设置一个 g.nextStartNodeIndex 标记位,每次都从上一次结束的位置拿节点。
-
fwk.Parallelizer().Until(ctx, len(nodes), checkNode)
开启 N 个线程并行运行 filter 插件,寻找符合条件的 node 节点,数量等于feasibleNodes 。一旦找到配置的可行节点数,就停止搜索更多节点,并设置 g.nextStartNodeIndex 标记位。
-
|
|
接下来展开讲讲 prioritizeNodes() ,这里就是我们所说的打分阶段了。
-
prescore
这个阶段主要运行 pod 亲和性、pod 拓扑/标签选择器打散算法、污点/容忍度相关的插件。
-
score
这个阶段同时启用多个线程对节点进行打分,打完分以后调用 NormalizeScore() 方法对各个插件的打分进行均衡,防止有点插件打分过高出现的一票否决这种情况。然后再给各个插件的打分加权重。返回一个打完分的节点列表。
-
汇总 scores
汇总每个节点的所有打分插件的打分,返回节点汇总后的打分列表,供后续的逻辑挑选出最合适的节点,也就是给我们前面看到过的 g.selectHost() 方法去挑选。
|
|
未完待续
由于我平时都是业余看的代码,断断续续花了几天理清了 prefilter 、 filter 、prescore 、 score 阶段的逻辑,我要在这做一个分割,主要是这代码阅读量太大了,需要点时间整理剩下的部分代码。