调度器功能
默认调度器的主要职责,就是为一个新创建出来的Pod寻找一个最合适的节点(Node)
调度器对一个 Pod 调度成功,实际上就是将它的 spec.nodeName 字段填上调度结果的节点名字
预选节点
从集群所有的节点中,根据调度算法挑选出所有可以运行该 Pod 的节点默认调度器会首先调用一组叫作 Predicate 的调度算法,来检查每个Node
优选节点
从预选的结果中,再根据调度算法挑选一个最符合条件的节点作为最终结果.再调用一组叫作 Priority 的调度算法,来给上一步得到的结果里的每个Node打分.最终的调度结果,就是得分最高的那个Node
调度器实现机制
调度器实现的核心就是由两个相互独立运行的控制循环
第一个控制循环,我们可以称之为Informer Path.当一个待调度Pod(它的nodeName字段是空的)被创建出来之后,调度器就会通过Pod Informer的Handler,将这个待调度Pod添加进调度队列.Kubernetes 的调度队列是一个 PriorityQueue(优先级队列)
第二个控制循环,是调度器负责Pod调度的主循环,我们可以称之为 Scheduling Path.scheduling Path的主要逻辑,就是不断地从调度队列里出队一个Pod.然后,调用 Predicates 算法进行“过滤”.这一步“过滤”得到的一组 Node,就是所有可以运行这个 Pod 的宿主机列表.当然Predicates算法需要的Node信息,都是从Scheduler Cache 里直接拿到的,这是调度器保证算法执行效率的主要手段之一.接下来,调度器就会再调用 Priorities 算法为上述列表里的 Node 打分,分数从0到10.得分最高的 Node,就会作为这次调度的结果.
Kubernetes 的默认调度器还要负责对调度器缓存(即:scheduler cache)进行更新.事实上Kubernetes 调度部分进行性能优化的一个最根本原则,就是尽最大可能将集群信息 Cache 化,以便从根本上提高Predicate和 Priority调度算法的执行效率
为了不在关键调度路径里远程访问APIServer,Kubernetes的默认调度器在Bind阶段,只会更新Scheduler Cache里的Pod和Node的信息.这种基于“乐观”假设的 API 对象更新方式,在Kubernetes里被称作Assume
默认调度器可扩展性设计
默认调度器的可扩展机制,在 Kubernetes里面叫作 Scheduler Framework.这个设计的主要目的,就是在调度器生命周期的各个关键点上,为用户暴露出可以进行扩展和实现的接口,从而实现由用户自定义调度器的能力
每一个绿色的箭头都是一个可以插入自定义逻辑的接口.比如,上面的 Queue 部分,就意味着你可以在这一部分提供一个自己的调度队列的实现,从而控制每个Pod 开始被调度(出队)的时机
Predicates部分,则意味着你可以提供自己的过滤算法实现,根据自己的需求,来决定选择哪些机器
上述这些可插拔式逻辑,都是标准的Go语言插件机制(Go plugin 机制),也就是说,你需要在编译的时候选择把哪些插件编译进去
调度器的默认调度策略
Predicates预选机制()
GeneralPredicates策略
PodFitsResources 计算的就是宿主机的CPU和内存资源等是否够用.PodFitsResources检查的只是Pod的requests字段
PodFitsHost 检查的是宿主机的名字是否跟Pod的spec.nodeName一致
PodFitsHostPorts 检查的是Pod申请的宿主机端口(spec.nodePort)是不是跟已经被使用的端口有冲突
PodMatchNodeSelector检查的是,Pod的nodeSelector或者nodeAffinity指定的节点,是否与待考察节点匹配,等等
kubelet在启动Pod前,会执行一个Admit操作来进行二次确认.这里二次确认的规则,就是执行一遍 GeneralPredicates
Volumes相关的策略
NoDiskConflict 检查的条件,是多个Pod声明挂载的持久化Volume是否有冲突
maxPDVolumeCountPredicate检查的条件,则是一个节点上某种类型的持久化Volume是不是已经超过了一定数目.如果是的话,那么声明使用该类型持久化Volume的 Pod 就不能再调度到这个节点了
VolumeZonePredicate则是检查持久化Volume的Zone(高可用域)标签,是否与待考察节点的 Zone 标签相匹配
VolumeBindingPredicate的规则 它负责检查的,是该Pod对应的PV的nodeAffinity字段,是否跟某个节点的标签相匹配
在Predicates阶段Kubernetes就必须能够根据Pod的Volume属性来进行调度
宿主机相关的过滤策略
PodToleratesNodeTaints负责检查的就是我们前面经常用到的Node的“污点”机制.只有当Pod的Toleration字段与Node的Taint字段能够匹配的时候,这个Pod才能被调度到该节点上
NodeMemoryPressurePredicate检查的是当前节点的内存是不是已经不够充足,如果是的话,那么待调度Pod就不能被调度到该节点上
pod相关过滤策略
跟GeneralPredicates大多数是重合的.而比较特殊的是PodAffinityPredicate.这个规则的作用是检查待调度Pod与Node上的已有Pod之间的亲密(affinity)和反亲密(anti-affinity)关系
在为每个Node执行Predicates时调度器会按照固定的顺序来进行检查.这个顺序是按照Predicates本身的含义来确定的.比如宿主机相关的Predicates会被放在相对靠前的位置进行检查.要不然的话在一台资源已经严重不足的宿主机上,上来就开始计算PodAffinityPredicate是没有实际意义的
Priorities优选机制
Priorities阶段的工作就是为这些节点打分.这里打分的范围是 0-10 分,得分最高的节点就是最后被Pod绑定的最佳节点
Priorities里最常用到的一个打分规则
LeastRequestedPriority=score = (cpu((capacity-sum(requested))10/capacity) + memory((capacity-sum(requested))10/capacity))/2
这个算法实际上就是在选择空闲资源(CPU 和 Memory)最多的宿主机
BalancedResourceAllocation=score =10-variance(cpuFraction,memoryFraction,volumeFraction)*10
选择的其实是调度完成所有节点里各种资源分配最均衡的那个节点,从而避免一个节点上CPU被大量分配而 Memory大量剩余情况
Priorities里有一个叫ImageLocalityPriority的策略,如果待调度Pod需要使用的镜像很大并且已经存在于某些 Node上那么这些Node的得分就高
在实际的执行过程中,调度器里关于集群和Po 的信息都已经缓存化,所以这些算法的执行过程还是比较快的.Kubernetes调度器里其实还有一些默认不会开启的策略.可以通过为 kube-scheduler指定一个配置文件或者创建一个ConfigMap ,来配置哪些规则需要开启、哪些规则需要关闭.并且可以通过为Priorities设置权重来控制调度器的调度行为
Predicates和Priorities的区别
Predicates是为Pod过滤不符合条件的节点 返回的结果是一个满足Pod运行条件的节点列表
Priorities是为Predicates后的节点列表中的节点打分 返回的结果是为这些节点打得0-10的分数