目录
第二个控制循环,调度器负责pod调度的主循环scheduling path
kube-scheduler是什么
一句话
调度器的指责主要是为新创建的pod在集群中寻找最合适的node,并将pod调度到Node上。
两层含义
- 从集群所有节点中,根据调度算法挑选出所有可以运行该pod的节点。
- 再根据调度算法从上述node节点选择最优节点作为最终结果。
Scheduler调度器运行在master节点,它的核心功能是监听apiserver来获取PodSpec.NodeName为空的pod,然后为pod创建一个binding指示pod应该调度到哪个节点上,调度结果写入apiserver。
两个注意
Pod.spec.nodeName用于强制约束将Pod调度到指定的Node节点上,强制匹配。(通过指定nodeName可直接绕过调度器,并不会做任何的资源过滤和检查)
pod.spec.nodeSelector是通过kubernetes的label-selector机制进行节点选择,由scheduler调度策略MatchNodeSelector进行label匹配,调度pod到目标节点,该匹配规则是强制约束。
Scheduler调度功能
主要职责
- 集群高可用:如果 kube-scheduler 设置了 leader-elect 选举启动参数,那么会通过 etcd 进行节点选主( kube-scheduler 和 kube-controller-manager 都使用了一主多从的高可用方案)
- 调度资源监听:通过 list-Watch 机制监听 kube-apiserver 上资源的变化,这里的资源主要指的是 Pod 和 Node
- 调度节点分配:通过预选(Predicates)与优选(Priorites)策略,为待调度的 Pod 分配一个 Node 进行绑定并填充nodeName,同时将分配结果通过 kube-apiserver 写入 etcd。
调度节点分配
调度节点分配主要可以分为预选(Predicates)与优选(Priorites)这两个环节,上面讲到的调度资源监听的实质就是为这两个环节提供输入。
- 预选:根据配置的 Predicates Policies(默认为 DefaultProvider 中定义的default predicates policies 集合)过滤掉那些不满足这些 Policies 的 Node,预选的输出作为优选的输入;
- 优选:根据配置的 Priorities Policies(默认为 DefaultProvider 中定义的 default priorities policies 集合)给预选后的 Node 进行打分排名,得分最高的 Node 即作为最适合的 Node ,该 Pod 就绑定(Bind)到这个 Node 。
注:如果经过优选将 Node 打分排名后,有多个 Node 并列得分最高,那么 kube-scheduler 将随机从中选择一个 Node 作为目标 Node 。
调度的两个过程
- 首先是过滤掉不满足条件的节点这个过程称为predicate
- 然后对通过的节点按照优先级排序,这个是priorlty
- 最后从中选择优先级最高的节点
好的调度器考虑的方向
- 尽可能地将 工作负载 平均到不同的节点,减少单个节点宕机造成的损失。
- 可扩展性。集群规模增大后,保证调度器不会成为性能的瓶颈。
- 高可用。调度器能做成集群,任何一个调度器出现问题,不会影响整个集群的调度。
- 灵活性。不同的用户会有不同的调度需求,需要考虑用户能够配置不同的调度算法。
- 资源合理和高效利用。调度器应该尽可能地提高集群的资源利用率,防止资源浪费。
scheduler 调度机制工作原理
框架图
调度核心:实际上就是两个相互独立的控制循环。 一个是Informer Path循环上list-watch机制和cache机制。 一个是scheduler Path循环上的预选优选机制和绑定回调机制。
第一个控制循环 Informer Path
主要目的启动一系列的Informer,用来监听(list-watch)Etcd 中Pod、Node、Sservice等与调度相关的API对象的变化。
例:当一个待调度pod(nodename字段为空)被创建,调度器会通过Pod Informer的Handler,将待调度的pod添加进调度队列。
- 默认情况,kubernetes调度队列是一个PriorityQueue(优先级队列),并且当某些集群信息发生变化的时候,调度器会对调度队列中的内容进行特殊操作。主要是出于调度优先级和抢占的考虑。
- 负责对调度器缓存(scheduler cache)进行更新。Kubernetes调度部分进行性能优化的最根本原则,尽最大可能将集群信息Cache化,提高Predicate和Priority调度算法的执行效率。
第二个控制循环,调度器负责pod调度的主循环scheduling path
- 主要逻辑:不断从调度队列出队Pod,调用Predicates算法进行“过滤”。“过滤”得到一组Node,就是所有可以运行这个Pod的宿主机列表。Predicates算法需要的Node信息,从scheduler cache直接获取,保障算法执行效率。
- 调度器会调用Priorities算法为上述列表的Node打分,分数从0-10。得分最高的Node,作为调度的结果。
调度算法执行完成之后,调度器就需要将Pod对象的nodeName字段的值,填充为Node名字。 ====> Bind阶段。
Assume方式API对象更新方式
为了不在关键调度路径里远程访问APIServer,k8s默认调度器在Bind阶段,只会更新Scheduler Cache里的Pod和Node信息。基于“乐观”假设的API对象更新方式,称作Assume。
Assume之后,调度器会创建Goroutinue来异步向APIServer发起更新pod的请求,来真正完成Bind操作。如果异步Bind过程失败了,其实也没有多大关系,等scheduler cache同步之后就会恢复正常。
二次确认
正是由于kubernetes调度器的“乐观”绑定设计,当一个新的pod完成调度需要在某个节点运行起来之前,该节点的kubelet执行Admit操作再次验证该Pod是否确实能够运行在该节点上。实际上就是把一组 叫做“GeneralPredicates”的最基本调度算法作为二次确认。
scheculer设计亮点
- Scheduler Cache化。将集群中的pod、node、service等根调度相关的对象信息cache化,并通过list-watch机制 根据API对象的变化去更新相应的缓存信息。提高算法的执行效率。
- 乐观绑定。避免远程访问APIServer
- 无锁化。在scheduler path上,调度器会启动多个Goroutinue以节点为粒度并发执行Predicates算法,提高执行效率。Priorities算法也会以MapReduce方式并行计算然后再进行汇总。需要并发的路径上,调度器会避免设置任何全局的竞争资源,从而避免使用锁进行同步带来的巨大性能损耗。
- 加锁:调度队列和scheduler cache操作。都不在 Scheduling Path 的算法执行路径上。
Kubernetes默认调度器调度策略解析
Predicates和Priorities两个调度策略主要发生作用的阶段。
Predicates
Fliter过滤器,即:按照调度策略,从当前集群的所有节点中,“过滤”出一系列符合条件的节点。待调度Pod的宿主机。
GeneralPredicates
负责的是最基础的调度策略。
- PodFitsResource 计算的就是宿主机的CPU和内存资源是否够用。检查的是pod的requests字段。Kubernetes 的调度器并没有为 GPU 等硬件资源定义具体的资源类型,而是统一用一种名叫Extended Resource的、key-value格式的扩展字段来描述的。
- PodFitsHost,宿主机名字是否和 pod的spec.nodeName一致。
- PodFitsHostPorts pod申请的宿主机端口 spec.nodePort 是不是跟已经使用的端口冲突。
- PodMatchNodeSelector pod的 nodeselector 或者 nodeAffinity 指定的节点,是否与待调度的节点label信息匹配。
上面的正是考察一个pod能否运行在一个node上的最基本过滤条件。 Kubelet启动pod前,会执行一个Admit操作二次确认。
volume相关的过滤规则
负责跟容器持久化volume相关的调度策略。
- NoDiskConflict检查的条件,是判断备选pod的getPersistentDisk或AWS EBS类型和备选节点中已存在的Pod是否冲突。AWS EBS类型的volume是不允许被两个pod同时使用的。(pod.spec.volumes)
- MaxPDVolumeCountPredicate:一个节点上某种类型的持久化volume是不是已经超过了一定数目,如果是的话,那么声明使用该类型持久化volume的pod就不能再调度到这个节点上了。
- VolumeZonePredicate检查持久化volume的Zone 高可用域 标签,是否与待调度节点的zone标签相匹配。
- VolumeBindingPredicate 检查的是 该pod对应PV的 nodeAffinity字段是否跟某个节点的标签相匹配。
- Local Persistent Volume 本地持久化卷,必须使用nodeAffinity来跟某个具体的节点绑定。根据pod的volume属性进行调度。
宿主机相关的过滤规则
主要考察待调度pod是否满足node本身的某些条件。
- PodToleratesNodeTaints,负责检查的就是Node的 污点 机制。
- NodeMemoryPressurePredicate 检查当前节点内存是不是已经不够充足。
Pod相关的过滤规则
- podAffinityPredicate 检验待调度的pod与Node上已有pod之间的亲和性和反亲和性。
- podAntiAffinity 规则,就指定了这个 Pod不希望跟任何携带了 x=y 标签的 Pod存在于同一个node上。
- podaffinitypredicate是有作用域的,仅对携带key是kubernetes.io/hostname标签的 Node有效。 正是topologyKey的作用。
- podAffinity亲和性:Pod,就只会被调度到已经有携带了 x=y 标签的 Pod 运行的 Node 上。而这条规则的作用域则是所有携带Key是failure-domain.beta.kubernetes.io/zone的Node。、
- requiredDuringSchedulingIgnoredDuringExecution字段含义:这条规则必须在pod调度时进行检查(requiredDuringScheduling);如果是已经运行的Pod发生变化,label修改,造成该pod不再适合运行在这个Node上的时候,kubernetes不会主动修改(IgnoredDuringExecution)。
上面四种类型predicates,构成调度器确定一个Node可以运行待调度Pod的基本策略。
注意:具体执行的时候,当开始调度一个pod时,kubernetes调度器会同时启动16个Goroutine并发的为集群里的所有Node计算Predicates,最后返回这个pod的宿主机列表。(宿主机相关的predicates会放在相对靠前的位置检查)
priorities
宿主机之间资源利用率均衡,最适合运行的node。节点打分阶段,得分最高的就是pod绑定的最佳节点。
- LeastRequestedPriority:从备选节点列表选出资源消耗最小的节点。选择空闲资源最多的宿主机。
score = (cpu((capacity-sum(requested))10/capacity) + memory((capacity-sum(requested))10/capacity))/2
- BalancedResourceAllocation:调度完成后,资源分配最均衡的节点。避免一个节点上CPU被大量分配而mem大量剩余的情况。
score = 10 - variance(cpuFraction,memoryFraction,volumeFraction)*10
每种资源的Fraction的定义是:Pod请求的资源/节点上的可用资源。而variance算法的作用,则是计算每两种资源Fraction之间的“距离”。最后选择资源Fraction差距最小的节点。
- NodeAffinityPriority、TaintTolerationPriority和InterPodAffinityPriority三种priority。一个node满足上述规则的字段数目越多,得分越高。
- ImageLocalityPriority策略,待调度pod使用的镜像很大,并且已经存在于某些Node上,得分会比较高。 可以通过为 Priorities 设置权重,来控制调度器的调度行为。
总结
本文主要介绍调度器的调度功能、设计原理和调度算法,帮助读者加深对调度器的整体认识。后续会对调度器的源码进行详细分析。