一, 概述
Kubernetes 是 Google 开源的容器集群管理系统(谷歌内部: Borg), 而今天要介绍的 kube-scheduler 是 k8s 系统的核心组件之一, 其主要职责就是通过自身的调度算法, 为新创建的 Pod 寻找一个最合适的 Node.
主要包含如下几个步骤:
通过一组叫做谓词 的过滤算法, 先挑出满足条件的 Node;
通过一组叫做优先级 的打分算法, 来给上一步符合条件的每个 Node 进行打分排名;
最终选择得分最高的节点, 当然如果得分一样就随机一个节点, 填回 Pod 的 spec.nodeName 字段.
官方流程图如下:
- For given pod:
- +---------------------------------------------+
- | Schedulable nodes: |
- | |
- | +--------+ +--------+ +--------+ |
- | | node 1 | | node 2 | | node 3 | |
- | +--------+ +--------+ +--------+ |
- | |
- +-------------------+-------------------------+
- |
- |
- v
- +-------------------+-------------------------+
- Pred. filters: node 3 doesn't have enough resource
- +-------------------+-------------------------+
- |
- |
- v
- +-------------------+-------------------------+
- | remaining nodes: |
- | +--------+ +--------+ |
- | | node 1 | | node 2 | |
- | +--------+ +--------+ |
- | |
- +-------------------+-------------------------+
- |
- |
- v
- +-------------------+-------------------------+
- Priority function: node 1: p=2
- node 2: p=5
- +-------------------+-------------------------+
- |
- |
- v
- select max{node priority} = node 2
scheduler 的工作看似很简单, 但其实不然. 考虑的问题非常多, 比如要保证每个节点被公平调度, 提高资源利用率, 提高 pod 调度效率, 提升调度器扩展能力等等.
可涉及的内容非常多, 接下来会围绕两个核心步骤对 k8s 的 默认调度策略 深入了解.
参考 Kubernetes 版本: v1.12
二, Predicates
Predicates 在调度过程中的作用就是先进行 过滤 , 过滤掉所有不符合条件的节点后, 剩下的所有节点就都是可以运行带调度 Pod.
Predicates 的可以分为如下四类:
GeneralPredicates : 负责最基础的调度策略, 比如 PodFitsResources 计算宿主机资源是否够用.
与 Volume 相关的过滤规则 : 负责与容器持久化 Volume 相关的调度策略.
与宿主机相关的过滤规则 : 负责考察待调度 Pod 是否满足 Node 本身的一些条件.
与已运行 Pod 相关的过滤规则 : 负责检查待调度 Pod 与 Node 上已有 Pod 之间的亲和性关系.
具体的 Predicates 默认策略, 可以参考: 默认调度策略
当开始调度一个 Pod 的时候, 调度器会同时开启多个协程并发的进行 Node Predicates 过滤, 最后返回一个可以运行 Pod 的节点列表. 每个协程都是按照固定的顺序进行计算过滤的.
接下来, 我们看下四大类具体运行的调度策略内容.
1. GeneralPredicates
看字面意思就知道 GeneralPredicates 负责的是最基础的调度策略, 其包含的具体策略如下:
PodFitsResources : 计算宿主机的 CPU, 内存, 扩展资源 (如 GPU) 等是否够用.
PodFitsHost : 检查宿主机的名字是否跟 Pod 的 spec.nodeName 匹配.
PodFitsHostPorts : 检查 Pod 申请的宿主机端口有没有冲突.
PodMatchNodeSelector : 检查节点是否能匹配 Pod 的 nodeSelector 和 nodeAffinity.
因为 GeneralPredicates 是最基础的调度策略, 所以该接口也会被别的组件直接调用, 比如 kubelet,daemonSet controller.kubelet 在启动 pod 之前, 还会再执行一遍 GeneralPredicates, 用于二次确认.
2. 与 Volume 相关的过滤规则
不废话就直接列举具体的策略了:
NoDiskConflict : 检查该节点上所有的 Pods 是否与待调度的 Pod 的 Volume 有冲突, 比如 AWS,GCE 的 Volume 是不允许被两个 Pod 同时使用的.
VolumeZonePredicate : 检查 Pod Volume 的 zone 标签是否与节点的 zone 标签匹配. 如果 Node 没有 zone 标签则认定为匹配.
MaxPDVolumeCountPredicate : 检查节点上某种类型的 Volume 是否已经超过指定数目.
CSIMaxVolumeLimitPredicate : 检查 csi volume 相关的限制
VolumeBindingPredicate : 检查 Pod 对应的 Local PV 的 nodeAffinity 字段, 是否跟某个节点的标签相匹配. 如果该 Pod PVC 还没有绑定 PV 的话, 则调度器还要负责检查所有待绑定的 PV, 且该 PV 的 nodeAffinity 是否与节点标签匹配.
3. 与宿主机相关的过滤规则
这些规则主要考察待调度的 Pod 是否满足 Node 本身的一些条件.
具体的策略如下:
NodeConditionPredicate : 检查 Node 是否还未准备好或者处于 NodeOutOfDisk,NodeNetworkUnavailable 状态, 又或者 Node spec.Unschedulable 设置为 true, 那该节点都将无法被调度.
PodToleratesNodeTaints : 检查 Node 的 taint(污点)机制. 只有当 Pod 的 Toleration 与 Node 的 Taint 匹配时, Pod 才能调度到该节点上.
NodeMemoryPressurePredicate : 检查当前节点的内存是否已经不够使用.
NodeDiskPressurePredicate : 检查当前节点的磁盘是否已经不够使用.
NodePIDPressurePredicate : 检查当前节点的 PID 是否已经不够使用.
4. 与已运行 Pod 相关的过滤规则
该规则主要就是 PodAffinityPredicate, 用于检查待调度 Pod 与 Node 上已有的 Pod 之间的亲和性和反亲和性关系.
具体的亲和性相关的调度, 后面会单独拿一篇文章进行介绍.
三, Priorities
完成了前一个阶段的节点 "过滤" 之后, 便需要通过 Priorities 为这些节点打分, 选择得分最高的节点, 作为调度对象.
打分函数很多, 总得分可以参考:
总分 = (权重 1 * 打分函数 1) + (权重 2 * 打分函数 2) + ... + (权重 n * 打分函数 n) .
每一次打分的范围是 0 - 10 分. 10 表示非常合适, 0 表示非常不合适.
并且每个打分函数都可以配置对应的权重值, 下面介绍 调度器策略配置 时, 也会涉及权重值的配置. 默认权重值是 1, 如果觉得某个打分函数特别重要, 便可以加大该权重值.
具体的 Priorities 默认策略可以参考: defaultPriorities .
Priorities 最常用到的一个打分规则是 LeastRequestedPriority , 该算法用于选出空闲资源 (CPU & memory) 最多的宿主机.
还有一个常见的是 BalancedResourceAllocation , 该规则主要目的是资源平衡. 在所有节点里选择各种资源分配最均衡的节点, 避免出现某些节点 CPU 被大量分配, 但是 Memory 大量剩余的情况.
此外, 还有 InterPodAffinityPriority , NodeAffinityPriority , TaintTolerationPriority , 与亲和性与污点调度有关, 后面会有单独的文章进行介绍. 这里表示节点满足的规则越多, 那得分就越高.
在 K8S v1.12 版本还引入了一个调度策略, 即 ImageLocalityPriority . 该策略主要目的是优先选择那些已经存有 Pod 所需 image 的节点, 可以避免实际运行 Pod 时, 再去下载 image.
注意: pod 运行时是否会下载 image, 还跟 Pod ImagePullPolicy 配置有关.
可以看到 k8s scheduler 完成一次调度所需的信息非常之多. 所以在实际的调度过程中, 大量的信息都事先已经缓存, 提高了 Pod 的调度效率.
四, 调度策略配置
Kubernetes 调度器有默认的调度策略, 具体可以参考 . 当然用户也可以修改调度策略, 可以通过命令行参数 policy-config-file 指定一个 JSON 文件来描述哪些 predicates 和 priorities 在启动 k8s 时被使用, 通过这个参数调度就能使用管理者定义的策略了.
示例如下:
- {
- "kind" : "Policy",
- "apiVersion" : "v1",
- "predicates" : [
- {
- "name" : "PodFitsHostPorts"
- },
- {
- "name" : "PodFitsResources"
- },
- {
- "name" : "NoDiskConflict"
- },
- {
- "name" : "NoVolumeZoneConflict"
- },
- {
- "name" : "MatchNodeSelector"
- },
- {
- "name" : "HostName"
- }
- ],
- "priorities" : [
- {
- "name" : "LeastRequestedPriority", "weight" : 1
- },
- {
- "name" : "BalancedResourceAllocation", "weight" : 1
- },
- {
- "name" : "ServiceSpreadingPriority", "weight" : 1
- },
- {
- "name" : "EqualPriority", "weight" : 1
- }
- ],
- "hardPodAffinitySymmetricWeight" : 10,
- "alwaysCheckAllPredicates" : false
- }
五, 自定义调度器
前面提到了调度器的扩展能力, 除了使用 k8s 自带的调度器, 你也可以编写自己的调度器. 通过修改 Pod 的 spec.schedulername 参数来指定调度器的名字.
参考资料
- - The Kubernetes Scheduler
- - Scheduler Algorithm in Kubernetes
来源: http://www.tuicool.com/articles/VfEbUbi