kubernetes调度的目的就是为pod寻找一个最合适的node。
最合适node的挑选过程分为两步:
1)从集群所有的节点中根据调度算法选出所有可运行该pod的节点
2)从上一步的结果中,再根据调度算法挑选出一个最符合条件的节点运行pod
在调度过程中,默认调度器首先会调用一组叫做Predicates的调度算法选出一组合适的node,然后再运行Priority调度算法,来给通过Predicates算法选出的node进行打分,最终分数最高的那个节点即是pod运行的节点。
声明一个点:调度器对一个pod调度成功,实际上就是将它的spec.nodeName字段填上调度结果的node名字。
调度原理示意图如下所示:
上图中informer path启动一系列informer,用来监听etcd中的Pod、Node、Service等与调度相关的API对象,当一个pod被创建出来之后,就将这个pod添加到待调度队列(优先级队列)。
上面informer path将pod加入到待调度队列中,下面的scheduling path从调度队列中取出pod,通过Predicates调度算法,从cache中拿到node信息,选出pod可运行的node,然后再调用Priorities算法为选出的node进行打分,从0到10,得分最高者,就是pod最终运行的node。
上述挑选过程执行完成之后,将pod中的nodename设置为node的名字,这个步骤被称为Bind。
上述bind阶段只会更新cache中node和pod的信息。这种“乐观”假设的API对象的更新方式,在kubernetes里被称作Assume。
当assume完成之后,调度器才会创建一个Goroutine来异步的向APIServer发起更新请求,来完成真正的Bind操作。当调度器的“乐观”绑定之后,当pod在node上运行之后,该节点上的kubelet还会执行一次操作,确认该pod真正可以在该节点上运行。
上面描述了pod的调度过程,提到了Predicates调度算法和Priorities调度算法,下面是对Predicates调度算法的一些描述。
Predicates可以看过是Filter,通过Filter选出当前pod可运行的node,是硬性条件。在kubernetes中,默认的调度策略有如下几种:
第一种:GeneralPredicates;主要是对宿主机的CPU和内存资源以及其他各种基础条件进行检测。
第二种:与volume相关的过滤规则;
第三种:是宿主机相关的过滤规则。
第四种:是pod相关的过滤规则。比如pod之间的亲和性和反亲和性。
上述是Predicates算法相关的过滤规则,执行完之后,再执行Priorities算法对选出的node进行优选。主要是通过一些算法对node进行打分,选出分数得分最高的node去运行pod。
上述是pod的大致调度流程,此外默认调度器还有优先级和抢占机制。
首先优先级和抢占机制是解决pod调度失败的问题。
当一个pod调度失败之后它会被暂时搁置起来,知道pod更新或者集群状态发生变化,调度器才会对这个pod重新进行调度。
此时kubernetes有一个优先级的概念,当优先级高的pod调度失败之后该pod不会被搁置,而是会挤走某个node上优先级低的pod,这样保证高优先级pod被调度成功。这个机制在1.10之后才逐步可用。要使用这个机制,首先要在集群中提交一个PriorityClass:
apiVersion: sceduling.k8s.io/v1beat1
kind: PriorityClass
metadata:
name: hige-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for high priority service pods only."
上述指定了一个优先级为1000000的PriorityClass。在kubernetes中,优先级是一个32bit的整数,最大值不能超哥10亿,并且数字越大优先级越高,超出10亿部分作为保留部分被k8s自身使用。如果没有声明的话默认是0,可以通过以下命令在集群中查看:
kubectl describe po redis-cluster-operator-7b987d6477-xdrhw | grep Pri
Priority: 0
定义了优先级在声明pod时指定priorityClassName即可:
spec: containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
上述是关于优先级的描述。
当一个高优先级的pod调度失败的时候,调度器的抢占能力就会被触发。这时调度器从集群中寻找一个节点,使得当这个节点上的一个pod被删除,待调度的高优先级pod就可以被调度到这个节点上,这就是抢占。
抢占过程发生时,并不会立刻被调度到被强占的Node上,而是将抢占者的nominatedNodeName字段设置为被强占的Node的名字。然后强占者会重新进入下一个调度周期,然后在调度周期来决定是不是要运行在被强占的节点上。
调度器的抢占机制原理:
调度器抢占算法的一个最重要的设计就是在调度队里,使用了两个不同的队列,第一个叫做ActiveQ,凡是在这个队列中的pod都是下一个调度周期需要调度的对象。第二个叫做unschedulableQ,专门用来存放调度失败的队列。当unschedulableQ中的pod被更新之后,调度器会自动把这个pod移动到activeQ中,从而给pod重新调度的机会。
在抢占者调度失败之后,抢占者被放倒unschedulableQ中,这时会触发会抢占者寻找失败者的流程:
第一:调度器会检查这次失败事件的原因,来确认抢占是不是可以帮助抢占者找到一个新节点。
第二:如果确定抢占可以发生,那么调度器就会把自己缓存的所有节点信息复制一份,然后使用这个副本来模拟抢占过程。检查副本中的每一个节点,确认该节点上的pod删除之后抢占者是否可以在这个节点上运行,一旦可以运行,记录下来,这就是抢占的过程。
当模拟抢占过程成功之后调度器就会开始真正的抢占操作:
第一步:调度器会检查牺牲者列表,清理这些 Pod 所携带的 nominatedNodeName 字段。
第二步:调度器会把抢占者的 nominatedNodeName,设置为被抢占的 Node 的名字
第三部:调度器会开启一个 Goroutine,同步地删除牺牲者。
接下来调度器就通过正常的调度流程调度抢占者。
关于pod的优先级以及抢占调度在kubernetes1.11之后是beta版本,可以根据个人需要来决定是否开启。
以上整理自极客时间“深入剖析kubernetes”
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。