Kubernetes 默认调度器调度策略概述
Kubernetes 默认调度器调度策略概述
在上一篇文章Kubernetes Scheduler 概述 中有讲述 Kubernetes scheduler 的工作原理和架构,其中有讲述到 kube-scheduler 的核心就是通过调度算法(预选算法和优选算法)来选出合适的节点供 pod 节点使用。本篇文章主要介绍汇总下 Kubernetes 默认调度器中使用到的 Predicates(预选算法) 和 Priorities(优选算法)。
1. Predicates 预选算法
Predicates 预选算法在调度过程中可以理解为 Filter 的功能,即:它按照指定的调度策略,从当前集群的所有节点中,“过滤”出一系列符合条件的节点。这些节点,都是可以运行待调度 Pod 的宿主机。在 Kubernetes 中,默认的调度策略按照类型分类可以有如下四种:
- 第一种类型,叫作 GeneralPredicates 类型的过滤策略:这种类型的策略主要负责最基础的调度策略。
- 第二种是与 Volume 相关的过滤规则:主要负责的是跟容器持久化 Volume 相关的调度策略。
- 第三种是与宿主机相关的过滤规则:主要考察待调度 Pod 是否满足 Node 本身的某些条件。
- 第四种是与 Pod相关的过滤规则。这一组规则,跟 GeneralPredicates 大多数是重合的。另有比较特殊的是 PodAffinityPredicate(pod亲和性相关的过滤规则)。
1.1. GeneralPredicates 过滤规则
这一组过滤规则,负责的是最基础的调度策略。部分规则统计如下表所示:
规则 | 说明 |
---|---|
PodFitsResources | 节点上剩余的资源(如 CPU 和内存)是否满足 Pod 请求的资源,计算时会根据 Pod 的 requests 字段进行判断求值 |
PodFitsHost | 如果 Pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配。 |
PodFitsHostPorts | 如果 Pod 中定义了 spec.hostPort 属性,那么需要先检查这个指定端口是否已经被节点上其他服务占用了 |
PodMatchNodeSelector | 检查 Pod 的 nodeSelector 或者 nodeAffinity 指定的节点,是否与node 的标签值匹配。 |
像上面这样一组 GeneralPredicates,正是 Kubernetes 考察一个 Pod 能不能运行在一个 Node 上最基本的过滤条件。所以,GeneralPredicates 也会被其他组件(比如kubelet)直接调用。在文章Kubernetes Scheduler 概述 有提到过,kubelet 在启动 Pod 前,会执行一个 Admit 操作来进行二次确认。这里二次确认的规则,就是执行一遍 GeneralPredicates 过滤规则。
1.2. 与 Volume 相关的过滤规则
这一组过滤规则,负责的是跟容器持久化 Volume 相关的调度策略。部分规则统计如下:
规则 | 说明 |
---|---|
NoDiskConflict | 检查是否多个 Pod 之间声明挂载的持久化 Volume 是否有冲突。比如,AWS EBS 类型的 Volume,是不允许被两个 Pod 同时使用的。 |
NoVolumeZoneConflict | 检测 Pod 请求的 Volumes 在节点上是否可用,因为某些存储卷存在区域调度约束 |
MaxPDVolumeCountPredicate | 检查一个节点上某种类型的持久化 Volume 是不是已经超过了一定数目,如果是的话,那么声明使用该类型持久化 Volume 的 Pod 就不能再调度到这个节点了。 |
VolumeZonePredicate | 检查持久化 Volume 的 Zone(高可用域)标签,是否与待考察节点的 Zone 标签相匹配。 |
VolumeBindingPredicate | 检查 Pod 对应的 PV 的 nodeAffinity 字段,是否跟某个节点的标签相匹配。这个过滤规则在 local PV 的延迟绑定中非常有用,Local Persistent Volume(本地持久化卷),必须使用 nodeAffinity 来跟某个具体的节点绑定。此外,如果该 Pod 的 PVC 还没有跟具体的 PV 绑定的话,调度器还要负责检查所有待绑定 PV,当有可用的 PV 存在并且该 PV 的 nodeAffinity 与待考察节点一致时,这条规则才会返回“成功”。 |
1.3. 与宿主机 node 节点相关的过滤规则
这一组规则,主要考察待调度 Pod 是否满足 Node 本身的某些条件。部分规则统计如下:
规则 | 说明 |
---|---|
CheckNodeDiskPressure | 检查节点磁盘空间是否符合要求 |
PodToleratesNodeTaints | 负责检查我们经常用到的 Node 的“污点”机制。只有当 Pod 的 Toleration 字段与 Node 的 Taint 字段能够匹配的时候,这个 Pod 才能被调度到该节点上。 |
NodeMemoryPressurePredicate | 检查当前节点的内存是不是已经不够充足,如果是的话,那么待调度 Pod 就不能被调度到该节点上。 |
CheckNodeCondition | Node 可以上报其自身的状态,如磁盘、网络不可用,表明 kubelet 未准备好运行 Pod,如果节点被设置成这种状态,那么 Pod 不会被调度到这个节点上 |
1.4. 与 pod 相关的过滤规则(主要是 pod 亲和性过滤)
这一组规则,跟 GeneralPredicates 大多数是重合的。另外一个比较特殊的是 PodAffinityPredicate
这个规则,检查待调度 Pod 与 Node 上的已有 Pod 之间的亲密(affinity)和反亲密(anti-affinity)关系,即检查 pod 之间的亲和性和反亲和性关系。
1 |
|
如上这个例子中所示定义了 pod 之间否反亲和性,表示这个 Pod 不希望跟任何携带了 security=S2 标签的 Pod 存在于同一个 Node 上。注意: 对于 PodAffinityPredicate 可以通过 topologyKey
关键字指定规则生效的作用域,比如上面这条规则,只会对携带了 Key 是kubernetes.io/hostname 标签的 Node 上的所有 pods 执行pod 反亲和性过滤判断。而后面的 podAffinity(pod 亲和性判断)表示该 pod只会被调度到已经有携带了 security=S1 标签的 Pod 运行的 Node上,同时这条规则的作用域是所有携带 Key 是failure-domain.beta.kubernetes.io/zone 标签的 Node 上操作。
同时 requiredDuringSchedulingIgnoredDuringExecution
字段表示的含义是:调度器在调度 pod 时必须考虑这个过滤规则,即对 pod 进行过滤检查(requiredDuringScheduling);但如果是已经运行的 Pod 发生变化(如 Label 被修改),导致该 Pod 不再适合运行在这个 Node 上了,Kubernetes 不会对 pod进行主动修正(IgnoredDuringExecution),即 kubelet 不会 pod 执行驱逐操作。
注意: 调度器在执行上面的过滤规则时,会异步启动 16 个 goroutine
来执行过滤算法,最终会得到满足条件的 node 列表。同时在为每个 Node 执行 Predicates 时,调度器会按照固定的顺序来进行检查。这个顺序是按照 Predicates 本身的含义来确定的。比如,宿主机相关的 Predicates 会被放在相对靠前的位置进行检查。要不然的话,在一台资源已经严重不足的宿主机上,上来就开始计算 PodAffinityPredicate,是没有实际意义的。
除了这些过滤算法之外,还有一些其他的算法,更多更详细的我们可以查看源码文件:k8s.io/kubernetes/pkg/scheduler/algorithm/predicates/predicates.go。
2. Priorities 优选算法
在预选阶段完成了节点的“过滤”之后,优选阶段的工作就是为这些节点打分,这里打分的范围是 0-10 分,得分最高的节点就是最后被 Pod 绑定的最佳节点。部分规则统计如下:
规则 | 说明 |
---|---|
LeastRequestedPriority | 通过计算node cpu 和内存的剩余资源数来打分,剩余资源多的分数越高,即这个算法实际上就是在选择空闲资源(CPU 和 Memory)最多的宿主机。 |
BalancedResourceAllocation | 通过计算cpu、memory、磁盘等资源分配之间的距离,资源分配差距越小的节点分数越高。即调度时会从所有节点中优先选择各种资源分配最均衡的那个节点,从而避免一个节点上 CPU 被大量分配、而 Memory 大量剩余的情况出现。常与 LeastRequestedPriority 一起配合使用。 |
ImageLocalityPriority | 如果待调度 Pod 需要使用的镜像很大,并且已经存在于某些 Node上,那么这些 Node 的得分就会比较高。这个算法的缺点是会造成调度pod的堆叠现象,即所有使用该镜像的pod 都会调度到该 node 上。为了避免引发调度堆叠,调度器在计算得分的时候必须根据镜像的分布进行优化,即:如果大镜像分布的节点数目很少,那么这些节点的权重就会被调低,从而降低引起调度堆叠的风险。 |
SelectorSpreadPriority | 为了更好的高可用,对同属于一个 Deployment 或者 RC 下面的多个 Pod 副本,尽量调度到多个不同的节点上,当一个 Pod 被调度的时候,会先去查找该 Pod 对应的 controller,然后查看该 controller 下面的已存在的 Pod,运行 Pod 越少的节点权重越高 |
InterPodAffinityPriority | 遍历 Pod 的亲和性条目,并将那些能够匹配到给定节点的条目的权重相加,结果值越大的节点得分越高 |
MostRequestedPriority | 空闲资源比例越低的 Node 得分越高,这个调度策略会把你的所有的工作负载(Pod)调度到尽量少的节点上,类似于资源集中使用 |
RequestedToCapacityRatioPriority | 为 Node 上每个资源占用比例设定得分值,给资源打分函数在打分时使用 |
NodePreferAvoidPodsPriority | 这个策略将根据 Node 的注解信息中是否含有 scheduler.alpha.kubernetes.io/preferAvoidPods 来计算其优先级,使用这个策略可以将两个不同 Pod 运行在不同的 Node 上 |
NodeAffinityPriority | 基于 Pod 属性中 PreferredDuringSchedulingIgnoredDuringExecution 来进行 Node 亲和性调度 |
TaintTolerationPriority | 基于 Pod 中对每个 Node 上污点容忍程度进行优先级评估,这个策略能够调整待选 Node 的排名 |
ServiceSpreadingPriority | 这个调度策略的主要目的是确保将归属于同一个 Service 的 Pod 调度到不同的 Node 上,这个策略的最终目的是:即使在一个 Node 宕机之后 Service 也具有很强容灾能力。 |
CalculateAntiAffinityPriorityMap | 这个策略主要是用来实现 Pod 反亲和的 |
EqualPriorityMap | 将所有的 Node 设置成相同的权重,默认为 1 |
除了这些策略之外,还有很多其他的策略,同样我们可以查看源码文件:k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/ 了解更多信息。每一个优选函数会返回一个 0-10 的分数,分数越高表示节点越优,同时每一个函数也会对应一个表示权重的值。最终主机的得分用以下公式计算得出:
1 |
|
3. 总结
本文主要讲述了 Kubernetes 调度器的 Predicates 和 Priorities 里默认调度规则的主要工作原理。在实际的执行过程中,调度器里关于集群和 Pod 的信息都已经缓存化,所以这些算法的执行过程还是比较快的。此外,对于比较复杂的调度算法来说,比如 PodAffinityPredicate,它们在计算的时候不只关注待调度 Pod 和待考察 Node,还需要关注整个集群的信息,比如,遍历所有节点,读取它们的 Labels。这时候,Kubernetes 调度器会在为每个待调度 Pod 执行该调度算法之前,先将算法需要的集群信息初步计算一遍,然后缓存起来。这样,在真正执行该算法的时候,调度器只需要读取缓存信息进行计算即可,从而避免了为每个 Node 计算 Predicates 的时候反复获取和计算整个集群的信息。
此外,除了本篇讲述的这些规则,Kubernetes 调度器里还有一些默认不会开启的策略。你可以通过为 kube-scheduler 指定一个配置文件或者创建一个 ConfigMap ,来配置哪些规则需要开启、哪些规则需要关闭。并且,你可以通过为 Priorities 设置权重,来控制调度器的调度行为。