我们说了整体的调度过程中,分为了Predicates和Priorities两个调度策略作用阶段
我们首先看一下Predicates
Predicates在调度过程中的作用,可以理解为Filter,按照调度策略,从当前集群所有节点中,过滤出符合条件的节点,这些都是可以运行待调度Pod的宿主机
至于怎么筛选出来,默认的调度策略有如下的三种
第一种类型,叫做GeneralPredicates
最为基础的调度策略,比如其中的PodFitsResources计算出的就是宿主机的CPU和内存资源是否够用
当然,我们已经提到过,PodFitsResources检查只是Pod的request字段,Kubernetes的调度器并没有去为一些其他硬件资源定义具体的资源类型,而是统一的使用一种名为Extended Resource的Key-value格式的扩展字段来描述的
apiVersion: v1
kind: Pod metadata: name: extended-resource-demo spec: containers: – name: extended-resource-demo-ctr image: nginx resources: requests: alpha.kubernetes.io/nvidia-gpu: 2 limits: alpha.kubernetes.io/nvidia-gpu: 2 |
我们通过alpha.kubernetes.io/nvidia-gpu=2的定义方式,声明使用了两个NVDIA类型的GPU
在PodFitsResources中,调度器并不知道这个值对应的硬件资源是什么,只是直接使用后面的Value进行计算,在Node的Capacity字段,也必须要声明这个字段
而PodFitsHost规则检查的,是宿主机的名字和Pod的spec.nodeName是否一致
PodFitsHostPorts检查的是,Pod申请的宿主机端口是否已经跟被使用的端口有冲突
PodMatchNodeSelector检查的是,Pod的nodeSelector或者nodeAffinity指定的节点是否匹配,是否和待考察节点匹配等
像如上的GeneralPredicates,是判断一个Pod能否运行在一个Node上的基本过滤条件
正如之前说的,Kubelet在启动Pod之前,会执行一个Admit操作进行二次确认,就是执行的GeneralPredicates
第二组类型,则是和Volume相关的过滤规则
检查持久化Volume能否成功调度的策略
比如,NoDiskConflict检查的就是多个Pod挂载的持久化Volume是否有冲突,AWS类型的Volume,不被允许同时被两个Pod使用的,如果一个A的EBS已经挂载到了某个节点了,其他声明使用A的就不能在调度到相同节点了
而在MaxPDVolumeCountPredicate检查的条件中,则是某一个节点的特定类型的持久化Volume是不是超过了一定数目,如果是的话,声明使用该类型持久化的Pod就不能再调度到这个节点上了
而VolumeZonePredicate,就是检查持久化Volume的Zone标签是否相匹配
还有一个专门给本地化存储使用的VolumeBindingPredicate的规则,就是这个Pod对应的PV的nodeAffinity,是否和某个节点标签对应
因为Local Persistent Volue,必须要使用nodeAffininty来根据某个具体的节点绑定,在Predicates阶段,Kubernetes要根据Pod的Volume排除某些不能调度的节点
如果对应的Pod的pvc 没有和具体的PV绑定,调度器还要检查所有待绑定的PV,有可用的PV并且该PV的nodeAffinity和待考察的一致,这个规则才会成功
apiVersion: v1
kind: PersistentVolume metadata: name: example-local-pv spec: capacity: storage: 500Gi accessModes: – ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-storage local: path: /mnt/disks/vol1 nodeAffinity: required: nodeSelectorTerms: – matchExpressions: – key: kubernetes.io/hostname operator: In values: – my-node |
而上面的PV声明的nodeAffinity,是出现在my-node宿主机上,所以任何通过PVC使用这个PV的Pod,都需要调度到my-node上才可以正常工作,VolumeBindingPredicate,就是筛选这些节点的规则
最后是宿主机相关的规则
比如,PodToleratesNodeTaints,就是检查之前说的Node上的污点机制,只有当Pod的Toleration字段和Node的Taint能够匹配的时候,才能进行调度上去
同样,还有着NodeMemoryPressurePredicate,检查的是当前节点的内存是不是还够,如果是的话,待调度的Pod就不能被调度在这个节点上
最后是Pod相关的过滤规则
这一部分和GeneralPredicates很大一部分重合,其中比较特殊的,是PodAffinityPredicate,这个规则的作用,是检查Pod和Node上的Pod的亲密和反亲密关系
apiVersion: v1
kind: Pod metadata: name: with-pod-antiaffinity spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: – weight: 100 podAffinityTerm: labelSelector: matchExpressions: – key: security operator: In values: – S2 topologyKey: kubernetes.io/hostname containers: – name: with-pod-affinity image: docker.io/ocpqe/hello-pod |
上面给出的PodAntiAffinity,指定了不希望和带有S2的标签的Pod放在同一个Node上,PodAffinityPredicate的作用于是对key为kubrnetes.io/hostname标签的Node有效,也是topologyKey这个关键词的作用
PodAntiAffinity相反的是podAffinity,比如下面的例子
apiVersion: v1
kind: Pod metadata: name: with-pod-affinity spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: – labelSelector: matchExpressions: – key: security operator: In values: – S1 topologyKey: failure-domain.beta.kubernetes.io/zone containers: – name: with-pod-affinity image: docker.io/ocpqe/hello-pod |
上面的规则就是携带了Security=S1标签的Pod运行的Node上
上面例子中requiredDuringSchedulingIgnoredDuringExection字段的含义是,就是在Pod调度的时候进行检查,如果已经在运行的Pod发生了变化,比如Label被修改,造成了该Pod不再适合运行在这个Node上的时候,Kubernetes不会进行主动修正
我们上述的四个Predicates,构成了调度器确立了一个Node可以运行待调度Pod的基本策略
在具体执行的时候,当开始调度一个Pod时候,Kubernetes调度器同时启动16个Gorooutine,并发地为集群内所有的Node计算Predicates,最后返回所有可以运行的Pod宿主列表
需要注意的是,每个Node执行Predicates,调度器会按照固定的顺序来进行检查,比如优先去检查宿主机相关的Predicates会优先检查,不然的话,一台资源已经严重不足的宿主机的话,没有必要再去看PodAffinityPredicate
然后是Priorities
在Predicates阶段完成了节点的过滤后,Priorities阶段的工作,就从选出的节点列表中挑选出最佳的节点
常用的一个打分规则,就是LeastRequestedPriority,计算方式可以简单的列为如下的公式
score = (cpu((capacity-sum(requested))10/capacity) + memory((capacity-sum(requested))10/capacity))/2 |
这个公式是为了计算空闲资源最多的宿主机
然后就是公式 BalancedResourceAllocation,计算公式如下
score = 10 – variance(cpuFraction,memoryFraction,volumeFraction)*10 |
其中的Faction的定义,是 Pod所需资源除以节点可用资源,variance算法的作用,是计算两种资源之间的距离,最后选择的时候,两个资源差距最小的节点
BalancedResourceAllocation选择的,是调度完成后,所有节点中资源分配最均衡的节点,避免一个节点上CPU大量被使用,Memory大量剩余的情况.
此外,还有着 NodeAffinityPriority TaintTolerationPriority.和 InterPodAffinityPriority三种Priority,这和前面的几种Predicate的含义和计算方法是类似的
还有就是新出的ImageLocalityPriority策略,是在Kubernetes1.12后新的调度规则.待调度Pod需要使用的镜像很大,并且已经存在于某些Node上,会给这些Node多一些分
这些规则执行完成,就能获得一个节点的最终得分
而且,调度器中关于集群和Pod的信息已经缓存起来了,这些算法的执行过程还是比较快的
那么,在本章中,我们讲述了Kubernetes默认调度器中主要的调度算法
这些规则的开启或者关闭,是可以通过kube-scheduler指定一个配置文件或者创建一个ConfigMap,来进行指定的开启或者关闭,我们或者可以设置权重来控制调度行为
那么我们如何控制将Pod分布在不同的机器上,避免堆叠呢?
1.我们可以为pod.yaml设置PreferredDuringSchedulingIgnoredDuringExection,指定不想和相同label的pod放一起,这样在podAntiAffinity在安排Pod的生效节点时候,不想在一起的pod数量越多分越低,打散同一个Service下多个Pod的分布
K8S在官网给出了对应的应用实例,
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: – labelSelector: matchExpressions: – key: security operator: In values: – S1 |
2.SelectorSpreadPriority,Kubernetes内置的一个priority,于Services上其他的pod尽可能的不再同一个节点,节点上同一个Service的pod数量越少得分越高
3.自定义策略,实现自己的负载均衡算法