目录前端
API Server在接受客户端提交Pod对象建立请求后,而后是经过调度器(kube-schedule)从集群中选择一个可用的最佳节点来建立并运行Pod。而这一个建立Pod对象,在调度的过程中有3个阶段:节点预选、节点优选、节点选定,从而筛选出最佳的节点。如图:node
当咱们有需求要将某些Pod资源运行在特定的节点上时,咱们能够经过组合节点标签,以及Pod标签或标签选择器来匹配特定的预选策略并完成调度,如MatchInterPodAfinity、MatchNodeSelector、PodToleratesNodeTaints
等预选策略,这些策略经常使用于为用户提供自定义Pod亲和性或反亲和性、节点亲和性以及基于污点及容忍度的调度机制。数据库
预选策略实际上就是节点过滤器,例如节点标签必须可以匹配到Pod资源的标签选择器(MatchNodeSelector实现的规则),以及Pod容器的资源请求量不能大于节点上剩余的可分配资源(PodFitsResource规则)等等。执行预选操做,调度器会逐一根据规则进行筛选,若是预选没能选定一个合适的节点,此时Pod会一直处于Pending状态,直到有一个可用节点完成调度。其经常使用的预选策略以下:vim
在上面的这些预选策略里面,CheckNodeLabelPressure和CheckServiceAffinity能够在预选过程当中结合用户自定义调度逻辑,这些策略叫作可配置策略。其余不接受参数进行自定义配置的称为静态策略。后端
预选策略筛选出一个节点列表就会进入优选阶段,在这个过程调度器会向每一个经过预选的节点传递一系列的优选函数来计算其优先级分值,优先级分值介于0-10之间,其中0表示不适用,10表示最适合托管该Pod对象。api
另外,调度器还支持给每一个优选函数指定一个简单的值,表示权重,进行节点优先级分值计算时,它首先将每一个优选函数的计算得分乘以权重,而后再将全部优选函数的得分相加,从而得出节点的最终优先级分值。权重可让管理员定义优选函数倾向性的能力,其计算优先级的得分公式以下:tomcat
finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + ......
下图是关于优选函数的列表图:安全
节点亲和性是用来肯定Pod对象调度到哪个节点的规则,这些规则基于节点上的自定义标签和Pod对象上指定的标签选择器进行定义。网络
定义节点亲和性规则有2种:硬亲和性(require)和软亲和性(preferred)app
定义节点亲和规则的两个要点:一是节点配置是否合乎需求的标签,而是Pod对象定义合理的标签选择器,这样才可以基于标签选择出指望的目标节点。
须要注意的是preferredDuringSchedulingIgnoredDuringExecution
和requiredDuringSchedulingIgnoredDuringExecution
名字中后半段字符串IgnoredDuringExecution
表示的是,在Pod资源基于节点亲和性规则调度到某个节点以后,若是节点的标签发生了改变,调度器不会讲Pod对象从该节点上移除,由于该规则仅对新建的Pod对象有效。
下面的配置清单中定义的Pod对象,使用节点硬亲和性和规则定义将当前Pod调度到标签为zone=foo的节点上:
apiVersion: v1 kind: Pod metadata: name: with-require-nodeaffinity spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - {key: zone,operator: In,values: ["foo"]} containers: - name: myapp image: ikubernetes/myapp:v1 #建立Pod对象 [root@k8s-master ~]# kubectl apply -f require-nodeAffinity-pod.yaml pod/with-require-nodeaffinity created #因为集群中并无节点含有节点标签为zone=foo,因此建立的Pod一直处于Pending状态 [root@k8s-master ~]# kubectl get pods with-require-nodeaffinity NAME READY STATUS RESTARTS AGE with-require-nodeaffinity 0/1 Pending 0 35s #查看Pending具体的缘由 [root@k8s-master ~]# kubectl describe pods with-require-nodeaffinity ...... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 3s (x21 over 1m) default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector. #给node01节点打上zone=foo的标签,能够看到成功调度到node01节点上 [root@k8s-master ~]# kubectl label node k8s-node01 zone=foo node/k8s-node01 labeled [root@k8s-master ~]# kubectl describe pods with-require-nodeaffinity Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 58s (x25 over 2m) default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector. Normal Pulled 4s kubelet, k8s-node01 Container image "ikubernetes/myapp:v1" already present on machine Normal Created 4s kubelet, k8s-node01 Created container Normal Started 4s kubelet, k8s-node01 Started container [root@k8s-master ~]# kubectl get pods with-require-nodeaffinity -o wide NAME READY STATUS RESTARTS AGE IP NODE with-require-nodeaffinity 1/1 Running 0 6m 10.244.1.12 k8s-node01
在定义节点亲和性时,requiredDuringSchedulingIgnoredDuringExecution
字段的值是一个对象列表,用于定义节点硬亲和性,它能够由一个或多个nodeSelectorTerms
定义的对象组成,此时值须要知足其中一个nodeSelectorTerms
便可。
而nodeSelectorTerms
用来定义节点选择器的条目,它的值也是一个对象列表,由1个或多个matchExpressions
对象定义的匹配规则组成,多个规则是逻辑与的关系,这就表示某个节点的标签必需要知足同一个nodeSelectorTerms
下全部的matchExpression
对象定义的规则才可以成功调度。以下:
#以下配置清单,必须存在知足标签zone=foo和ssd=true的节点才可以调度成功 apiVersion: v1 kind: Pod metadata: name: with-require-nodeaffinity spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - {key: zone, operator: In, values: ["foo"]} - {key: ssd, operator: Exists, values: []} #增长一个规则 containers: - name: myapp image: ikubernetes/myapp:v1 [root@k8s-master ~]# kubectl apply -f require-nodeAffinity-pod.yaml pod/with-require-nodeaffinity created [root@k8s-master ~]# kubectl get pods with-require-nodeaffinity NAME READY STATUS RESTARTS AGE with-require-nodeaffinity 0/1 Pending 0 16s [root@k8s-master ~]# kubectl label node k8s-node01 ssd=true node/k8s-node01 labeled [root@k8s-master ~]# kubectl get pods with-require-nodeaffinity NAME READY STATUS RESTARTS AGE with-require-nodeaffinity 1/1 Running 0 2m
在预选策略中,还能够经过节点资源的可用性去限制可以成功调度,以下配置清单:要求的资源为6核心CPU和20G内存,节点是没法知足该容器的资源需求,所以也会调度失败,Pod资源会处于Pending状态。
apiVersion: v1 kind: Pod metadata: name: with-require-nodeaffinity spec: containers: - name: myapp image: ikubernetes/myapp:v1 resources: requests: cpu: 6 memory: 20Gi
看下面一个配置清单:
apiVersion: apps/v1 kind: Deployment metadata: name: myapp-deploy-with-node-affinity spec: replicas: 5 selector: matchLabels: app: myapp template: metadata: name: myapp-pod labels: app: myapp spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 60 preference: matchExpressions: - {key: zone, operator: In, values: ["foo"]} - weight: 30 preference: matchExpressions: - {key: ssd, operator: Exists, values: []} containers: - name: myapp image: ikubernetes/myapp:v1 #首先先给node02和master都打上标签,master标签为zone=foo,node02标签为ssd=true,这里node01是没有对应标签的 [root@k8s-master ~]# kubectl label node k8s-node02 ssd=true node/k8s-node02 labeled [root@k8s-master ~]# kubectl label node k8s-master zone=foo node/k8s-master labeled #进行建立 [root@k8s-master ~]# kubectl apply -f deploy-with-preferred-nodeAffinity.yaml deployment.apps/myapp-deploy-with-node-affinity2 created #能够看到5个Pod分别分布在不一样的节点上,node01上没有对应的标签也会调度上进行建立Pod,体现软亲和性 [root@k8s-master ~]# kubectl get pods -o wide |grep deploy-with myapp-deploy-with-node-affinity2-75b8f65f87-2gqjv 1/1 Running 0 11s 10.244.2.4 k8s-node02 myapp-deploy-with-node-affinity2-75b8f65f87-7l2sg 1/1 Running 0 11s 10.244.0.4 k8s-master myapp-deploy-with-node-affinity2-75b8f65f87-cdrxx 1/1 Running 0 11s 10.244.2.3 k8s-node02 myapp-deploy-with-node-affinity2-75b8f65f87-j77f6 1/1 Running 0 11s 10.244.1.36 k8s-node01 myapp-deploy-with-node-affinity2-75b8f65f87-wt6tq 1/1 Running 0 11s 10.244.0.3 k8s-master #若是咱们给node01打上zone=foo,ssd=true的标签,再去建立时,你会发现全部的Pod都调度在这个节点上。由于节点的软亲和性,会尽力知足Pod中定义的规则,以下: [root@k8s-master ~]# kubectl label node k8s-node01 zone=foo [root@k8s-master ~]# kubectl label node k8s-node01 ssd=true [root@k8s-master ~]# kubectl get pods -o wide |grep deploy-with myapp-deploy-with-node-affinity2-75b8f65f87-4lwsw 0/1 ContainerCreating 0 3s <none> k8s-node01 myapp-deploy-with-node-affinity2-75b8f65f87-dxbxf 1/1 Running 0 3s 10.244.1.31 k8s-node01 myapp-deploy-with-node-affinity2-75b8f65f87-lnhgm 0/1 ContainerCreating 0 3s <none> k8s-node01 myapp-deploy-with-node-affinity2-75b8f65f87-snxbc 0/1 ContainerCreating 0 3s <none> k8s-node01 myapp-deploy-with-node-affinity2-75b8f65f87-zx8ck 1/1 Running 0 3s 10.244.1.33 k8s-node01
上面的实验结果显示,当2个标签没有都存在一个node节点上时,Pod对象会被分散在集群中的三个节点上进行建立并运行,之因此如此,是由于使用了 节点软亲和性的预选方式,全部的节点都可以经过MatchNodeSelector
预选策略的筛选。当咱们将2个标签都集合在node01上时,全部Pod对象都会运行在node01之上。
在出于高效通讯的需求,有时须要将一些Pod调度到相近甚至是同一区域位置(好比同一节点、机房、区域)等等,好比业务的前端Pod和后端Pod,此时这些Pod对象之间的关系能够叫作亲和性。
同时出于安全性的考虑,也会把一些Pod之间进行隔离,此时这些Pod对象之间的关系叫作反亲和性(anti-affinity)。
调度器把第一个Pod放到任意位置,而后和该Pod有亲和或反亲和关系的Pod根据该动态完成位置编排,这就是Pod亲和性和反亲和性调度的做用。Pod的亲和性定义也存在硬亲和性和软亲和性的区别,其约束的意义和节点亲和性相似。
Pod的亲和性调度要求各相关的Pod对象运行在同一位置,而反亲和性则要求它们不能运行在同一位置。这里的位置实际上取决于节点的位置拓扑,拓扑的方式不一样,Pod是否在同一位置的断定结果也会有所不一样。
若是基于各个节点的kubernetes.io/hostname
标签做为评判标准,那么会根据节点的hostname
去断定是否在同一位置区域。
Pod强制约束的亲和性调度也是使用requiredDuringSchedulingIgnoredDuringExecution
进行定义的。Pod亲和性是用来描述一个Pod对象和现有的Pod对象运行的位置存在某种依赖关系,因此若是要测试Pod亲和性约束,须要存在一个被依赖的Pod对象,下面建立一个带有app=tomcat
的Deployment资源部署一个Pod对象:
[root@k8s-master ~]# kubectl run tomcat -l app=tomcat --image=tomcat:alpine deployment.apps/tomcat created [root@k8s-master ~]# kubectl get pods -l app=tomcat -o wide NAME READY STATUS RESTARTS AGE IP NODE tomcat-75fd5cc757-w9qdb 1/1 Running 0 5m 10.244.1.37 k8s-node01
从上面咱们能够看到新建立的tomcat
pod对象被调度在k8s-node01上,再写一个配置清单定义一个Pod对象,经过labelSelector
定义的标签选择器挑选对应的Pod对象。
[root@k8s-master ~]# vim required-podAffinity-pod1.yaml apiVersion: v1 kind: Pod metadata: name: with-pod-affinity-1 spec: affinity: podAffninity: requiredDuringSchedulingIngnoreDuringExecution: - labelSelector: matchExpression: - {key: app , operator: In , values: ["tomcat"]} topologyKey: kubernetes.io/hostname containers: - name: myapp image: ikubernetes/myapp:v1
kubernetes.io/hostname
标签是Kubernetes集群节点的内建标签,它的值为当前节点的主机名,对于各个节点来讲都是不一样的。因此新建的Pod对象要被部署到和tomcat
所在的同一个节点上。
[root@k8s-master ~]# kubectl apply -f required-podAffinity-pod1.yaml pod/with-pod-affinity-1 created [root@k8s-master ~]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE with-pod-affinity-1 1/1 Running 0 31s 10.244.1.38 k8s-node01 tomcat-75fd5cc757-w9qdb 1/1 Running 0 5m 10.244.1.37 k8s-node01
基于单一节点的Pod亲和性相对来讲使用的状况会比较少,一般使用的是基于同一地区、区域、机架等拓扑位置约束。好比部署应用程序(myapp)和数据库(db)服务相关的Pod时,这两种Pod应该部署在同一区域上,能够加速通讯的速度。
同理,有硬亲和度即有软亲和度,Pod也支持使用preferredDuringSchedulingIgnoredDuringExecuttion
属性进行定义Pod的软亲和性,调度器会尽力知足亲和约束的调度,在知足不了约束条件时,也容许将该Pod调度到其余节点上运行。好比下面这一配置清单:
apiVersion: apps/v1 kind: Deployment metadata: name: myapp-with-preferred-pod-affinity spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: name: myapp labels: app: myapp spec: affinity: podAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 80 podAffinityTerm: labelSelector: matchExpressions: - {key: app, operator: In , values: ["cache"]} topologyKey: zone - weight: 20 podAffinityTerm: labelSelector: matchExpressions: - {key: app, operator: In, values: ["db"]} topologyKey: zone containers: - name: myapp image: ikubernetes/mapp:v1
上述的清单配置当中,pod的软亲和调度须要将Pod调度到标签为app=cache
并在区域zone当中,或者调度到app=db
标签节点上的,可是咱们的节点上并无相似的标签,因此调度器会根据软亲和调度进行随机调度到k8s-node01
节点之上。以下:
[root@k8s-master ~]# kubectl apply -f deploy-with-preferred-podAffinity.yaml deployment.apps/myapp-with-preferred-pod-affinity created [root@k8s-master ~]# kubectl get pods -o wide |grep myapp-with-preferred-pod-affinity myapp-with-preferred-pod-affinity-5c44649f58-cwgcd 1/1 Running 0 1m 10.244.1.40 k8s-node01 myapp-with-preferred-pod-affinity-5c44649f58-hdk8q 1/1 Running 0 1m 10.244.1.42 k8s-node01 myapp-with-preferred-pod-affinity-5c44649f58-kg7cx 1/1 Running 0 1m 10.244.1.41 k8s-node01
podAffinity
定义了Pod对象的亲和约束,而Pod对象的反亲和调度则是用podAntiAffinty
属性进行定义,下面的配置清单中定义了由同一Deployment建立可是彼此基于节点位置互斥的Pod对象:
[root@k8s-master ~]# cat deploy-with-required-podAntiAffinity.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp-with-pod-anti-affinity spec: replicas: 4 selector: matchLabels: app: myapp template: metadata: name: myapp labels: app: myapp spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - {key: app,operator: In,values: ["myapp"]} topologyKey: kubernetes.io/hostname containers: - name: myapp image: ikubernetes/myapp:v1 [root@k8s-master ~]# kubectl apply -f deploy-with-required-podAntiAffinity.yaml deployment.apps/myapp-with-pod-anti-affinity created [root@k8s-master ~]# kubectl get pods -l app=myapp NAME READY STATUS RESTARTS AGE myapp-with-pod-anti-affinity-79c7b6c596-77hrz 1/1 Running 0 8s myapp-with-pod-anti-affinity-79c7b6c596-fhxmv 0/1 Pending 0 8s myapp-with-pod-anti-affinity-79c7b6c596-l9ckr 1/1 Running 0 8s myapp-with-pod-anti-affinity-79c7b6c596-vfv2s 1/1 Running 0 8s
因为在配置清单中定义了强制性反亲和性,因此建立的4个Pod副本必须 运行在不一样的节点当中呢,可是集群中只存在3个节点,所以,确定会有一个Pod对象处于Pending的状态。
污点(taints)是定义在节点上的一组键值型属性数据,用来让节点拒绝将Pod调度到该节点上,除非该Pod对象具备容纳节点污点的容忍度。而容忍度(tolerations)是定义在Pod对象上的键值型数据,用来配置让Pod对象能够容忍节点的污点。
前面的节点选择器和节点亲和性的调度方式都是经过在Pod对象上添加标签选择器来完成对特定类型节点标签的匹配,实现的是Pod选择节点的方式。而污点和容忍度则是经过对节点添加污点信息来控制Pod对象的调度结果,让节点拥有了控制哪一种Pod对象能够调度到该节点上的 一种方式。
Kubernetes使用PodToleratesNodeTaints预选策略和TaintTolerationPriority优选函数来完成这种调度方式。
污点的定义是在节点的nodeSpec,而容忍度的定义是在Pod中的podSpec,都属于键值型数据,两种方式都支持一个effect
标记,语法格式为key=value: effect
,其中key和value的用户和格式和资源注解相似,而effect
是用来定义对Pod对象的排斥等级,主要包含如下3种类型:
在Pod对象上定义容忍度时,其支持2中操做符:Equal
和Exists
在使用kubeadm部署的集群中,master节点上将会自动添加污点信息,阻止不能容忍该污点的Pod对象调度到该节点上,以下:
[root@k8s-master ~]# kubectl describe node k8s-master Name: k8s-master Roles: master ...... Taints: node- role. kubernetes. io/ master: NoSchedule ......
而一些系统级别的应用在建立时,就会添加相应的容忍度来确保被建立时能够调度到master节点上,如flannel插件:
[root@k8s-master ~]# kubectl describe pods kube-flannel-ds-amd64-2p8wm -n kube-system ...... Tolerations: node-role.kubernetes.io/master:NoSchedule node.kubernetes.io/disk-pressure:NoSchedule node.kubernetes.io/memory-pressure:NoSchedule node.kubernetes.io/not-ready:NoExecute node.kubernetes.io/unreachable:NoExecute ......
使用命令行向节点添加污点
语法:kubectl taint nodes <nodename> <key>=<value>:<effect>...... #定义k8s-node01上的污点 [root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type=production:NoSchedule node/k8s-node01 tainted #查看节点污点信息 [root@k8s-master ~]# kubectl get nodes k8s-node01 -o go-template={{.spec.taints}} [map[effect:NoSchedule key:node-type value:production]]
此时,node01节点上已经存在的Pod对象不受影响,仅对新建Pod对象有影响,须要注意的是,若是是同一个键值数据,可是最后的标识不一样,也是属于不一样的污点信息,好比再给node01上添加一个污点的标识为:PreferNoSchedule
[root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type=production:PreferNoSchedule node/k8s-node01 tainted [root@k8s-master ~]# kubectl get nodes k8s-node01 -o go-template={{.spec.taints}} [map[value:production effect:PreferNoSchedule key:node-type] map[key:node-type value:production effect:NoSchedule]]
删除污点
语法:kubectl taint nodes <node-name> <key>[: <effect>]- #删除node01上的node-type标识为NoSchedule的污点 [root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type:NoSchedule- node/k8s-node01 untainted #删除指定键名的全部污点 [root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type- node/k8s-node01 untainted #补丁方式删除节点上的所有污点信息 [root@k8s-master ~]# kubectl patch nodes k8s-node01 -p '{"spec":{"taints":[]}}'
Pod对象的容忍度能够经过spec.tolerations
字段进行添加,同一的也有两种操做符:Equal
和Exists
方式。Equal等值方式以下:
tolerations: - key: "key1" operator: "Equal" value: "value1" effect: "Noexecute" tolerationSeconds: 3600
Exists方式以下:
tolerations: - key: "key1" operator: "Exists" effect: "NoExecute" tolerationSeconds: 3600