K8s预选策略和优选函数简介
调度器选择策略:
预选策略(Predicate)
1. 根据运行Pod的资源限制来排除不符合要求的Node
2. 根据运行Pod时,是否要求共享宿主机的网络名称空间来判断,如: 某Pod启动要共享宿主机的网络名称空间,启动80端口,而某些Node的80已经被占用,那它就不符合,就也要不排除。
优选策略(Priority):
此阶段会经过一系列函数,会把预选出来的节点的相关属性都输入到这些函数中,通过计算来获取每个节点的优先分值,然后在倒序排序,取得分最高者,作为运行Pod的节点,若计算后,发现有多个Node得分相同,此时将随机选取一个Node作为该Pod运行的Node。
选定(Select):
当优选选出一个Node后,该Pod将会被绑定到该Node上运行。
我们在定义一个Pod要运行在那个Node,是有一定偏向性的,比如有些Pod需要运行的有SSD硬盘的Node上,而有些Pod则需要运行在有GPU的节点上等,这时就需要我们给Node上打上不同的标签,来标识整个集群中不同Node的特性,这些特性包括,特有硬件标识,地理位置标识,不同机房标识等。
我们使用kubectl explain pod.spec 可以看到其中有nodeName 和 nodeSelector 两个属性,第一个是明确指定Node节点名,我就要运行在这个Node上,第二个就是通过匹配Node标签,来选择运行的Node。
调度方式有以下几类:
1. 节点倾向性(或叫 节点亲和性调度)
这种调度通常会采用 nodeSelector 来完成调度。
2. Pod倾向性 和 Pod非倾向性(这也称为Pod亲和性和非亲和性)
有时候我们需要让某些Pod能运行在同一处时,就需要使用Pod亲和性来定义,如:我们要定义一个NMT(Nginx+Tomcat+MySQL),若这三个Pod分别被调度三个不同地域的机房中,比如北京,上海,深圳等地,那肯定不是我们所希望的,这时我们就需要使用Pod亲和性来定义这三个Pod能运行在相同的Node 或 相邻的Node上。
而在另外一些情况下,我们又希望某些Pod一定不能运行在相同Node上,这时就需要使用Pod的非亲和性了。
3. 污点 和 污点容忍调度
Taints(污点):是定义在Node上的
Tolerations(容忍): 是定义在Pod上的
污点:指有时我们希望不将某些Pod调度到某些Node上,这时我们可对某些Node上打上污点,然后,当我们创建Pod时,我们可定义此Pod能容忍的污点范围。
假如:NodeA上有5个污点: "吃喝嫖赌抽",而此次要调度的Pod比较大度,能容忍5个污点,而NodeA的污点正好是Pod能容忍污点的范围内,于是Pod就能"嫁入"该NodeA。
假如,Pod运行到NodeA上后,后期我们又给该NodeA打上了新污点如:"坑蒙拐骗偷",此时Pod发现NodeA的恶性原来有这么多,怎么办?通常有两种选择策略:
1. Pod继续运行在NodeA上"艰苦度日"
2. Pod被驱逐,或者叫Pod逃离。
默认调度器的调度规则是:根据Node最大资源空闲率来进行均衡调度。
调度器:
预选策略
【注意:下面这些预选策略仅是其中一部分,并且k8s在进行预选时,它会是综合评估的,即所有启用的策略规则都要做判断,所得结果越符合就越会被挑选出来,进入优选策略中】:
CheckNodeCondition: 检查节点是否符合调度条件
GeneralPredicates:
HostName: 这种是判断Pod是否定义了pod.spec.hostname属性,若定义了,就在预选时,看看这些Node上是否存在相同主机名的Pod,若有,就排除该Node; 这可能是因为,某些Pod是需要有固定主机名,才会使用。
PodFitsHostPorts: 此预选策略是判断 pods.spec.containers.ports.hostPort 属性是否定义了,若定义了就表示该Pod要绑定到Node上指定的Port上,这时在进行预选时,就要判断这个端口是否被占用了,若占用就会排除该Node。
MatchNodeSeletctor:此预选策略会判断 pods.spec.nodeSelector 属性是否定义了,若定义了就根据Pod所定义的NodeSelector来选出匹配指定标签的Node。
PodFitsResources: 此预选策略会判断 Node上是否符合运行Pod所需的最小空闲资源。
NoDiskConfict: 用于判断若Pod定义了存储卷,则要检查该存储卷在该Node上是否可用,若Node能满足Pod存储卷的使用需求,则表示此Node可用。
PodToleratesNodeTaints:检查Pod上的spec.tolerations可容忍的污点是否完全包含Node上定义的污点,若完全包含,则表示Pod能容忍该Node节点的污点,否则该Node不会通过预选。
PodToleratesNodeNoExecuteTaints:
#首先,污点并非单一属性,它有三种属性,NoExcute是污点的一种属性。
#此检查是,若Pod被调度到某Node上时,最初Node上没有Pod不能容忍的污点,但后来Node的污点改变了,多了Pod不能容忍的污点,这时要怎么处理那?默认是继承既定事实,继续在该Node上运行。
# 第二种是不接受,也就是检查此属性,此时Node会驱逐该Pod,让该Pod重新调度到新Node上。
CheckNodeLabelPresence:这是定义在选择Node时,检查Node上是否存在指定的标签,若存在则可调度。此规则默认不启用。
CheckServiceAffinity: 当我们需要创建一些Pod时,这些Pod都使用相同的Serivce,这时,若启用该预选策略,则将这些Pod优先调度到 已存在较多该Service下的Pod的Node上。
#下面三个默认启用,因为这三个是最大的三个云运营商
MaxEBSVolumeCount:此预选策略是指若Pod需要使用亚马逊的EBS(弹性块存储服务),则检查该EBS是否已经达到最大运行挂载数39个,若没有,则表示还有存储空间可用。
MaxGCEPDVolumeCount: GCEPD:是Google的云环境中的持久存储,默认是16个
MaxAzureDiskVolumeCount: 最大是16个
CheckVolumeBinding:检查Node上是否存在以绑定 和 未绑定的PVC,若Node上有PVC,并且没有被绑定则能满足Pod运行的需求。
NoVolumeZoneConflict:它是在给定了区域限制的情况下,Zone在IDC机房是有限制的,他们通常会按房间,机柜,机架来划分范围,假如机架上有20台服务器,每2台一个区域,这个就是一个逻辑区域,此配置项的含义就是检查区域中Node上是否存在逻辑卷冲突。
CheckNodeMemoryPressure:检查Node上是否存在内存压力,即若某Node上已经出现内存紧张的情况了,那就不要在往它上面调度了。
CheckNodePIDPressure:检查Node上是否存在PID压力过大的情况,即若某Node上运行的进程过多,导致PID可能紧张,这时在选择调度时,也不会选择它。
CheckNodeDiskPressure:检查Node上是否存在磁盘使用压力过大的情况
MatchInterPodAffinity:检查Node上是否满足Pod的亲和性,假如Pod是Nginx,它是要为Tomcat Pod做代理的,那么在调度tomcat Pod时,就会检查Node上是否有Nginx Pod,若有这个非常亲和的Pod则优先调度到该Node上。
优选函数:
默认启用了7个红色的优选函数,它们的得分相加值越高,该节点越优选被选中。
LeastRequested: 【与它相反对一个优选函数:MostRequested,它是Node资源占用越高得分越高,有点像是,先将某Node的资源先全部占满的意味,然后空出部分Node.】
计算得分的算法公式=(cpu((capacity - sum(requested))*10 / capacity) + memory((capacity - sum(requested))*10 / capacity))/2
此公式的含义:
假如当前有16个核心,已经用了8个核心,那么总的使用率就是 (16-8)/16 = 0.5 ,而乘以10,是为了计算得分方便,将其转化为0~10之间的数,
而内存的计算含义一样,假如内存32G,已经用了8G,则使用率为 (32-8)/32 = 0.75 ,乘以10
将CPU + Memory/2 就可以得到一个平均值。该平均值越高,表示该节点的资源空闲率越高。
BalanceResourceAllocation:
它表示CPU和内存资源占用率的相近程度,作为评估标准,越接近也优选选择,比如: Node1的CPU和内存使用率一个48%一个50%,而Node2的CPU和内存使用率都是50%,则Node2胜出。
#它通常要和LeastRequested 结合来评估Node的资源利用率的。
#两个组合主要是为了平衡各Node上资源的利用率。
NodePreferAvoidPods:
根据Node节点上是否定义了注解信息"scheduler.alpha.kubernetes.io/preferAvoidPods",若没有则该Node得分为10,而且权重为1万,有此注解则说明该Node是适合运行Pod的。而对于那些有Replicas Controller的控制器则,该Node的得分为0,此时则表示Pod不能运行在该Node上,但这不能决定Pod一定不能运行在该Node上,因为最终是要看总体得分的。
注解信息:
# kubectl describe node node1.zcf.com
Name: node1.zcf.com
Roles: node
..........
Annotations: node.alpha.kubernetes.io/ttl: 0 #这就是所谓的node上的注解信息。
volumes.kubernetes.io/controller-managed-attach-detach: true
TaintToleration:
基于Pod对调度Node上的污点容忍度来评估是否可调度到该Node上。
简单说:就是取出Pod对象中定义的spec.tolerations列表,查看其中能容忍的污点,然后,一个一个去对比Node上存在的污点,若匹配的越多,则该Node的得分越低,就越不会调度到该Node上。
SelectorSpreading:
此优化函数是希望将相同标签选择器选取的Pod尽可能的散开到多个Node上。因此假如:ReplicaSet控制器,已经在B上创建了一个Pod,那么它要再创建一个Pod时,此优选函数在计算A,B,C三个Node的得分时,会根据那个Node上拥有此ReplicaSet控制器的标签选择器所匹配的Pod数量,来评分,数量越少,分数越高,反之越低。
而使用标签选择器的资源对象有:Service,Replication Controller,ReplicaSet,StatefulSet。
InterPodAffinity:
遍历对象的亲和性条目,并将能够匹配到给定节点的条目的条目数相加结果值越大得分越高。
简单说:NodeA上运行了3个Pod,NodeB上运行了6个Pod,NodeC上运行了5个Pod,现在要调度一个Pod到其中一个Node上,而该Pod的比较亲和某类Pod,此优选函数就会将这些Node上,所有匹配该Pod亲和条目的数量相加,值越大,则越得分越高。其中条目的理解,我还不是很懂。
NodeAffinity:
它是根据pod中定义的nodeSeletor来对Node做亲和性检查, 能成功匹配的数量越多,则得分越高。
NodeLabel:
根据Node是否有某些标签来做评估,然后计算得分的,此优选函数只关注标签,不关注值,只有Node上有这个标签就可以得分。
ImageLocality:
它评分的标准是,判断Node上是否有运行该Pod的镜像,若有则得分,否则不得分。
但它评估镜像是根据镜像总的体积大小来计算得分的,例如:NodeA上有1个镜像1G,NodeB上有2镜像,NodeC上有3个镜像,而运行此Pod需要4个镜像,其中一个镜像1G,其它都是比较小,这时NodeA的得分会最高。
#实验:
1. 定义一个Pod,并设置其nodeSelector,若指定的nodeSeletor标签没有匹配到任何Node,则Pod将处于Pinding状态,只有你给某个Node打上指定的标签和值后,该Pod才会被调度上去。
#实验2:
kubectl explain pods.spec.affinity
nodeAffinity: 定义Node的亲和性
podAffinity: 定义Pod的亲和性
podAntiAffinity:定义Pod非亲和性
nodeAffinity:
# 软亲和性,即若能满足则一定运行在满足条件的Node上,否则运行在其它Node上也不是不可以。
preferredDuringSchedulingIgnoredDuringExecution
#硬亲和性,若不能满足运行条件,则不运行Pod。
requiredDuringSchedulingIgnoredDuringExecution
nodeSelectorTerms:
matchExpressions : 这是更强大的一种方式,它是配表达式的。
matchFields:
key: 要对那个label key做匹配操作
operator:指定你是做什么比较操作,支持: In/NotIn(包含/不包含), Exists/DoesNotExist(存在/不存在) , Gt/Lt(大于/小于)
values:若操作符为Exists/DoesNotExists则,values必须为空。
vim pod-node-required-affinity.yaml apiVersion: v1 kind: Pod metadata: name: pod-node-affinity-1 labels: app: myapp tier: frontend spec: containers: - name: myapp image: ikubernetes/myapp:v1 affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: zone operator: In values: - foo - bar #默认node上没有打上zone=foo 或 zone=bar的标签,此时是硬亲和,若不能满足条件,则无法调动. vim pod-node-affinity.yaml apiVersion: v1 kind: Pod metadata: name: pod-node-affinity-1 labels: app: myapp tier: frontend spec: containers: - name: myapp image: ikubernetes/myapp:v1 affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - preference: matchExpressions: - key: zone operator: In values: - foo - bar weight: 60 # 此为软亲和,若node上有zone=foo 或 zone=bar,则优先调度上去,若全部都没有,勉强选一个调度上去也不是不可以
为啥有了节点亲和性,还要有Pod亲和性?
假如我们有一个分布式跨地域的大集群,现在想构建一个NMT的架构,为了实现这个目的,我完全可以给Node上打标签,然后,让NMT这些Pod都匹配这些标签,然后被调度到这些Node上去运行,这不是也可以实现我们的目的吗?
看上去没有问题,但问题是,假如你跨地域的分布式K8s集群,分别分布在北京,上海,深圳,杭州等地,你就不得不精心规划不同地域,不同IDC,不同机房,不同机柜的Node标签规划,否则你很可能在将来实现这个目的是变得困难不堪,你可以想象一下,你希望让一组NMT运行在北京某IDC的某机房中,这样一个NMT架构在工作时,才能更加高效的通信,你想实现这样的控制,你不规划Node的标签,你让预选策略如何按照你的想法去调度那?这还仅是一方面,你为了NMT架构能分离开,北京一套,上海一套.....你也要配置Pod的在选择Node标签时,设定它亲和哪些标签,所以工作量你可自己评估。
Pod亲和性的逻辑时,我要部署NMT环境,我的MySQL Pod第一个被调用,我不管调度器会把MySQL Pod调度到哪里,反正只要调度完成,并且运行了,我后续的Nginx,Tomcat的Pod是亲和MySQL Pod的,MySQL Pod在哪个Node上,那在调度NT时,就更加优先调度到那个Node上。
这样说看上去很简单,但仔细想想,MySQL被调度到某Node上,要是将NT也调度到那个Node上合适吗?若那个Node没有这么多资源运行它们怎么办?能不能将NT调度到M所在Node旁边的Node上,或同机房的Node中?那这算亲和吗? 其实也算,但怎么知道运行MySQL Pod的Node ,它旁边的Node是否那个主机?这其实还是需要借助于Node标签来实现,因此为集群标签的规划是在所难免的,因为你必须给预选策略一种判断标准,哪些是相同机柜,哪些是不同机房对吧,否则鬼知道集群中那么多Node那个和那个是邻居对吧,当然若你靠主机名来判断也不是不可以,你就要定义根据主机名的判断标准了。所以,比较通用的方法是,给每个Node打上不同的标签,如:北京机房1 机柜20 机架号等等来定义,这样根据这些标签值,就可以判断Node和Node之间的临近关系了。
打个比方: MySQL Pod被调度到 rack=bjYZ1 ,那后续调度NT时,就会优选rack=bjYZ1的Node,只要有这个标签值得Node会被优选调度,这样NMT它们就都可以运行在北京亦庄的机房中了。
kubectl pods.spec.affinity.podAffinity.
requiredDuringSchedulingIgnoredDuringExecution : 硬限制
labelSelector: 指定亲和Pod的标签
namespaces: 你要指定亲和Pod,你就需要指定你亲和的Pod属于哪个名称空间,否则默认是Pod创建在那个名称空间,就匹配那个名称空间的Pod
Pod亲和性示例: vim pod-required-affinity.yaml apiVersion: v1 kind: Pod metadata: name: web1-first labels: app: web1 tier: frontend spec: containers: - name: myapp image: harbor.zcf.com/k8s/myapp:v1 --- apiVersion: v1 kind: Pod metadata: name: db1-second labels: app: db1 tier: db1 spec: containers: - name: busybox image: busybox: latest imagePullPolicy: IfNotPresent command: ["sh", "-c", "sleep 3600"] affinity: podAffinity: #若需要测试Pod的反亲和性,只需要修改podAffinity为 podAntiAffinity 即可测试 requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - {key: app, operator: In, values: ["web1","web2"]} topologyKey: kubernetes.io/hostname #通过默认Node上主机名标签来临时代表地区标签,即:只有主机名相同,就认为是在相同位置的。 #Pod反亲和测试: Pod亲和性示例: vim pod-required-affinity.yaml apiVersion: v1 kind: Pod metadata: name: web1-first labels: app: web1 tier: frontend spec: containers: - name: myapp image: harbor.zcf.com/k8s/myapp:v1 --- apiVersion: v1 kind: Pod metadata: name: db1-second labels: app: db1 tier: db1 spec: containers: - name: busybox image: busybox: latest imagePullPolicy: IfNotPresent command: ["sh", "-c", "sleep 3600"] affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - {key: app, operator: In, values: ["web1","web2"]} topologyKey: kubernetes.io/hostname #第一次测试: 使用存在的标签来测试,web1会运行在一个节点上,db1一定不会运行在web1所在的Node上。 topologyKey: zone #第二次测试: 使用此key,因为两个Node上都没有此标签key,因此预选策略将认为,没有找到不同区域的Node,因此db1将处于Pinding状态,无法启动。
vim myapp-toleration.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp-toler1 spec: replicas: 2 selector: matchLabels: app: myapp release: canary template: metadata: labels: app: myapp release: canary spec: containers: - name: myapp image: ikubernetes/myapp:v2 ports: - name: http containerPort: 80 tolerations: - key: "node-type" operator: "Equal" value: "production" effect: "NoSchedule" #说明: 在定义Pod的容忍度时,若指定为NoSchedule,则不能定义tolerationSeconds,即容忍不了时,可宽限多久被驱逐。 # 若定义为NoExecute 则可以定义tolerationSeconds。 # Equal:做等值比较,即node-type标签 和 其值必须完全匹配。 # Exists :做存在性比较,即只要node-type标签存在,即可匹配,不看其标签值是否相同。 #测试NoExecute影响 #目前node2.zcf.com 上定义了node-type污点的影响为: NoExecute ............ tolerations: - key: "node-type" operator: "Exists" value: "" #设置node-type的值为空,因为Exists是做标签判断,只要node-type标签存在,即可. effect: "NoSchedule" #定义污点影响度为NoSchedule,即若Node上没有这个node-type污点标签,就不调度到其上. #若Node上污点影响(effect)为NoExecute,它是不包含NoSchedule的,即Node是不允许不能容忍NoExecute的Pod调度到自己上面的。 #若Pod能容忍node-type标签的污点,无论它是什么值,什么影响,都可以容忍 ............... tolerations: - key: "node-type" operator: "Exists" value: "" effect: "" #删除节点上的污点: kubectl taint node node01.zcf.com node-type- #注意:node-type是:node01上的标签,最后的“-”是删除该标签的意思。