Pod调度器
API Server
接收客户端提交Pod对象创建请求后的操作过程中,有一个重要的步骤是由调度程序(kube-scheduler
)从当前集群中选择一个可用的最佳节点来接收并运行它,通常是默认的调度器(default-scheduler
)负责执行此类任务。对于每个待创建的Pod
对象来说,调度过程通常分为三个阶段——预选、优选和选定三个步骤,以筛选执行任务的最佳节点。
Kubernetes 调度器概述
Kubernetes
系统的核心任务在于创建客户端请求创建的Pod
对象并确保其以期望的状态运行。创建Pod
对象时,调度器scheduler
负责为每一个未经调度的Pod
资源、基于一系列的规则集从集群中挑选一个合适的节点来运行它,因此也称为Pod
调度器。调度过程中,调度器不会修改Pod
资源,而是从中读取数据,并根据配置的策略挑选出最适合的节点,而后通过API
调用将Pod
绑定至挑选出的节点之上以完成调度过程。
Pod
调度器其核心目标是基于资源可用性将各Pod
资源公平地分布于集群节点之上。通过三个步骤完成调度操作:节点预选(Predicate)、节点优先级排序(Priority)及节点择优(Select);如下图所示
节点预选
基于一系列预选规则(如PodFitsResources和MatchNode-Selector等)对每个节点进行检查,将那些不符合条件的节点过滤掉从而完成节点预选。
节点优选
对预选出的节点进行优先级排序,以便选出最适合运行Pod对象的一个节点。
节点择优
从优选级排序结果中挑选出优先级最高的节点运行Pod对象,当此类节点多于一个时,则从中随机选择一个。
常用的预选策略
简单来说,预选策略就是节点过滤器,列如节点标签必须能够匹配到Pod资源的标签选择器(由
MatchNodeSelector
实现的规则),以及Pod
容器的资源请求量不能大于节点上剩余的可分配资源(由PodFitsResources
实现的规则)等。执行预选操作时,调度器将每个节点基于配置使用的预选策略以特定次序逐一筛选,并根据一票否决制进行节点淘汰。若预选后不存在任何一个满足条件的节点,则Pod
处于Pending
状态,直到至少有一个节点可用为止。
常用的预选策略如下:
✏️ CheckNodeCondition:检查是否可以在节点报告磁盘、网络不可用或未准备好的情况下将Pod
对象调度于其上。
✏️ HostName:若Pod
对象拥有spec.hostname
属性,则检查节点名称字符串与此属性值是否匹配。
✏️ PodFitsHostPorts:若Pod
容器定义了ports.hostPort
属性,则检查其值指定的端口是否已被节点上的其他容器或服务占用。
✏️ MatchNodeSelector:若Pod
对象定义了sepc.nodeSelector
属性,则检查节点标签是否能匹配此属性值。
✏️ NoDiskConflict:检查Pod
对象请求的存储卷在此节点是否可用,若不存在冲突则通过检查。
✏️ PodFitsResources:检查节点是否有足够的资源(如CPU
、内存和GPU
等)满足Pod
对象的运行需求。
✏️ PodToleratesNodeTaints:若Pod
对象定义了spec.tolerations
属性,则检查其值是否能够接纳节点定义的污点(taints),不过,它仅关注具有NoSchedule
和NoExecute
两个效用标识的污点。
✏️ PodToleratesNodeNoExecuteTaints:若Pod
对象定义了spec.tolerations
属性,则检查其值是否能够接纳节点定义的NoExecute
类型的污点。
✏️ CheckNodeLabelPresence:仅检查节点上指定的所有标签的存在性,要检查的标签以及其可否存在取决于用户的定义。
✏️ CheckServiceAffinity:根据当前Pod
对象所属的Service
已有的其他Pod
对象所运行的节点进行调度,其目的在于将相同的Service
的Pod
对象放置在同一个或同一类节点上以提高效率。
✏️ MaxEBSVolumeCount:检查节点上已挂载的EBS
存储卷数量是否超过了设置的最大值,默认值为39。
✏️ MaxGCEPDVolumeCount:检查节点上已挂载的GCE PD
存储卷数量是否超过了设置的最大值,默认值为16。
✏️ MaxAzureDiskVolumeCount:检查节点上已挂载的Azure Disk
存储卷数量是否超过了设置的最大值,默认值为16。
✏️ CheckVolumeBinding:检查节点上已绑定和未绑定的PVC
是否能够满足Pod
对象的存储卷需求,对于已绑定的PVC
,此预选策略将检查给定的节点是否能够兼容相应的PV
,而对于未绑定的PVC
,预选策略将搜索那些可满足PVC
申请的可用的PV
,并确保它可与给定的节点兼容。
✏️ NoVolumeZoneConflict:在给定了区域(zone
)限制的前提下,检查在此节点上部署Pod
对象是否存在存储卷冲突。某些存储卷存在区域调度约束,于是,此类存储卷的区域标签(zone-labels
)必须与节点上的区域标签完全匹配方可满足绑定条件。
✏️ CheckNodeMemoryPressure:若给定的节点已经报告了存在内存资源压力过大的状态,则检查当前Pod
对象是否可调度至此类节点之上。
✏️ CheckNodePIDPressure:若给定的节点已经报告了存在PID
资源压力过大的状态,则检查当前Pod
对象是否可调度至此节点之上。
✏️ CheckNodeDiskPressure:若给定的节点已经报告了存在磁盘资源压力过大的状态,则检查当前Pod
对象是否可调度至此节点之上。
✏️ MatchInterPodAffinity:检查给定节点是否能够满足Pod
对象的亲和性或反亲和性条件,以用于实现Pod
亲和性调度或反亲和性调度。
常用的优选函数
预选策略筛选并生成一个节点列表后即进入第二阶段的优选过程。在这个过程中,调度器向每个通过预选的节点传递一系列的优选函数来计算其优先级分值,优先级分值介于0到10之间,其中0表示不适合,10表示最适合托管该
Pod
对象。
常见的优选函数相关说明如下:
✏️ LeastRequestedPriority:由节点空闲资源与节点总容量的比值计算而来,即由CPU
或内存资源的总容量减去节点上已有Pod
对象需求的容量总和,再减去当前要创建的Pod
对象的需求容量得到的结果除以总容量。CPU
和内存具有相同的权重,资源空闲比例越高的节点得分就越高,其计算公式为:(cpu((capacity - sum(requested)) * 10/capacity) + memory((capacity - sum(requested)) * 10/capacity))/2 。
✏️ BalancedResourceAllocation:以CPU
和内存资源占用率的相近程序作为评估标准,二者越接近的节点权重越高。该优选函数不能单独使用,需要和LeastRequestedPriority
组合使用来平衡优化节点资源的使用情况,以选择那些在部署当前Pod
资源后系统资源更为均衡的节点。
✏️ NodePreferAvoidPodsPriority:此优选级函数权限默认为10000,根据节点是否设置了注解信息"scheduler.alpha.kubernetes.io/preferAvoidPods"来计算其优选级。计算方式是:给定的节点无此注解信息时,其得分为10乘以权重10000;存在此注解信息时,对于那些由ReplicationController
或ReplicaSet
控制器管控的Pod
对象的得分为0,其他Pod
对象会被忽略(得最高分)。
✏️ NodeAffinityPriority:基于节点亲和性调度偏好进行优先级评估,它将根据Pod
资源中的nodeSelector
对给定节点进行匹配度检查,成功匹配到的条目越多则节点的分越高。
✏️ TaintTolerationPriority:基于Pod
资源对节点的污点容忍调度偏好进行其优先级评估,它将Pod
对象的tolerations
列表与节点的污点进行匹配度检查,成功匹配的条目越多,则节点得分越低。
✏️ SelectorSpreadPriority:首先查找与当前Pod
对相匹配的Service、ReplicationController、ReplicaSet(RS)和StatefulSet
,而后查找与这些选择器匹配的现存Pod
对象及其所在的节点,则运行此类Pod
对象越少的节点得分将越高。简单来说,会尽量将同一标签选择器匹配到的Pod
资源分散到不同的节点之上来运行。
✏️ InterPodAffinityPriority:遍历Pod
对象的亲和性条目,并将那些能够匹配给定节点的条目的权重相加,结果值越大的节点得分越高。
✏️ MostRequestedPriority:与优选函数LeastRequestedPriority
的评估节点得分的方法相似,不同的是,资源站用比例越大的节点,其得分越高。
✏️ NodeLabelPriority:根据节点是否拥有特定的标签来评估其得分,而无论其值为何。需要其存在时,拥有相应标签的节点将获得优先级,否则,不具有相应标签的节点将获得优先级。
✏️ ImageLocalItyPriority:基于给定节点上拥有的运行当前Pod
对象中的容器所依赖到的镜像文件来计算节点得分,不具有Pod
依赖到的任何镜像文件的节点其得分为0,而拥有相应镜像文件的各节点中,所拥有的被依赖到的镜像文件其体积之和越大则节点得分越高。
节点亲和调度
节点亲和性是调度程序用来确定
Pod
对象调度位置的一组规则,这些规则基于节点上的自定义标签和Pod
对象上指定的标签选择器进行定义。节点亲和性允许Pod
对象定义针对一组可以调度于其上的节点的亲和性或反亲和性,不过,它无法具体到某个特定的节点。如将Pod
调度至有着特殊CPU
的节点或一个可用区域内的节点之上。
节点亲和性规则有两种类型:硬亲和性(required)和软亲和性(preferred)。硬亲和性实现的是强制性规则,
Pod
调度时必须要满足的规则,而在不存在满足规则的节点时,Pod
对象会被置为Pending
状态。而软亲和性规则实现的是一种柔和性调度限制,倾向于将Pod
对象运行于某类特定的节点之上,而调度器也将尽量满足此需求,但在无法满足调度需求时它将退而求其次地选择一个不匹配规则的节点。
定义节点亲和性规则的关键点有两个,一是为节点配置合乎需求的标签,另一个是为
Pod
对象定义合理的标签选择器,从而能够基于标签选择出符合期望的目标节点。不过preferredDuringSchedulingIgnoredDuringExecution
和requiredDuringSchedulingIgnoredDuringExecution
名字中的后半段字符串IgnoredDuringExecution
隐含的意义所指,在Pod
资源基于节点亲和性规则调度至某节点之后,节点标签发生了改变而不再符合此节点亲和性规则时,调度器不会将Pod
对象从此节点上移除,因为它仅对新建的Pod
对象生效。节点亲和性模型如下:
节点硬亲和性
示例一
下面的配置清单示例中定义的Pod
对象,其使用节点硬亲和性规则定义可将当前Pod
对象调度至拥有zone
标签其值为foo
的节点之上:
# vim required-nodeaffinity-demo-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity
namespace: default
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
接下来创建pod
,并查看其状态
# kubectl apply -f required-nodeaffinity-demo-pod.yaml
pod/with-required-nodeaffinity created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
with-required-nodeaffinity 0/1 Pending 0 4s
# kubectl describe pods with-required-nodeaffinity
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
Warning FailedScheduling default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
通过kubectl get pods
和kubectl describe pods
查看pod
,可以发现pod
一直处于Pending
状态,调度失败,这是由于强制型的节点亲和性和限制场景中不存在能够满足匹配条件的节点所致。
接下来给node
节点设置节点标签,这也是设置节点亲和性的前提之一。
# kubectl label node k8s-node1 zone=foo
node/k8s-node1 labeled
# kubectl get pods
NAME READY STATUS RESTARTS AGE
with-required-nodeaffinity 1/1 Running 0 1m
# kubectl describe pods with-required-nodeaffinity
.....
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
Warning FailedScheduling default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
Normal Scheduled default-scheduler Successfully assigned default/with-required-nodeaffinity to k8s-node1
Normal Pulled 9s kubelet, k8s-node1 Container image "blwy/myapp:v1" already present on machine
Normal Created 9s kubelet, k8s-node1 Created container myapp
Normal Started 9s kubelet, k8s-node1 Started container myapp
通过给其中一个node
节点打上标签后,可以看出pod
状态一下就变成了Running
状态,这是因为节点硬亲和性匹配到了规则。
在定义节点亲和性时,
requiredDuringSchedulingIgnoredDuringExecution
字段的值是一个对象列表,用于定义节点硬亲和性,可以定义多个nodeSelectorTerm
对象,彼此之间为“逻辑或“的关系,进行匹配度检查时,在多个nodeSelectorTerm
之间只要满足其中之一即可。nodeSelectorTerm
用于定义节点选择器条目,其值为对象列表,由一个或多个matchExpressions
对象定义的匹配规则组成,多个规则彼此之间为”逻辑与“的关系,意味着某节点的标签需要完全匹配同一个nodeSelectorTerm
下所有的matchExpression
对象定义的规则才算成功通过节点选择器条目的检查。而matchExpressions
又可由一到多个标签选择器组成,多个标签选择器彼此间为”逻辑与“的关系。
示例二
下面的资源配置清单示例中定义了调度拥有两个标签选择器的节点选择头目。两个标签选择器彼此间为“逻辑与”的关系。
为了测试,这里先给节点多打几个标签
# kubectl label node k8s-node1 ssd=true
node/k8s-node1 labeled
# kubectl label node k8s-node2 ssd=true
node/k8s-node2 labeled
# kubectl label node k8s-node2 zone=bar
node/k8s-node2 labeled
查看节点标签
# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8s-master1 Ready master 47d v1.16.6 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master1,kubernetes.io/os=linux,node-role.kubernetes.io/master=
k8s-node1 Ready 47d v1.16.6 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node1,kubernetes.io/os=linux,ssd=true,zone=foo
k8s-node2 Ready 47d v1.16.6 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node2,kubernetes.io/os=linux,ssd=true,zone=bar
编写yaml
文件
# vim required-nodeaffinity-demo-pod2.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity-2
namespace: default
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
- {key: ssd, operator: Exists, values: []}
接下来创建pode2
,可以明确知道满足条件的只有node2
节点,因为同时满足了zone=foo
和ssd=true
# kubectl apply -f required-nodeaffinity-demo-pod2.yaml
pod/with-required-nodeaffinity-2 created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
with-required-nodeaffinity-2 1/1 Running 0 4s
# kubectl describe pods with-required-nodeaffinity-2
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled default-scheduler Successfully assigned default/with-required-nodeaffinity-2 to k8s-node1
Normal Pulled 15s kubelet, k8s-node1 Container image "blwy/myapp:v1" already present on machine
Normal Created 15s kubelet, k8s-node1 Created container myapp
Normal Started 15s kubelet, k8s-node1 Started container myapp
❗️ 注意:调度器在调度Pod
资源时,节点亲和性仅是其节点预选策略中遵循的预选机制之一,其他配置使用的预选策略依然正常参与节点预选过程。例如下面的测试,将上面的资源配置清单修改如下
# vim required-nodeaffinity-demo-pod3.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity-3
namespace: default
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
resources:
requests:
cpu: 6
memory: 20Gi
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
- {key: ssd, operator: Exists, values: []}
# kubectl apply -f required-nodeaffinity-demo-pod3.yaml
pod/with-required-nodeaffinity-3 created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
with-required-nodeaffinity-3 0/1 Pending 0 4s
# kubectl describe pods with-required-nodeaffinity-3
....
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling default-scheduler 0/3 nodes are available: 2 node(s) didn't match node selector, 3 Insufficient cpu, 3 Insufficient memory.
Warning FailedScheduling default-scheduler 0/3 nodes are available: 2 node(s) didn't match node selector, 3 Insufficient cpu, 3 Insufficient memory.
上面的with-required-nodeaffinity-3
pod资源一直处于Pending
状态,这是因为这里所有节点都无法满足需求,这里所有节点配置在2核心CPU
和2GB
内存,无法满足容器myapp
的需求,所以调度失败。
节点软亲和性
节点软亲和性为节点选择机制提供了一种柔性控制逻辑,被调度的
Pod
对象不再是"必须"而是"应该"放置于某些特定的节点之上,当条件不满足时,也能够接受编排于其他不符合条件的节点之上。还提供了weight
属性以便用户自定义其优先级,取值范围是1~100
,数字越大优先级越高。
示例
# vim deploy-with-preferred-nodeaffinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy-with-node-affinity
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 60
preference:
matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
- weight: 30
preference:
matchExpressions:
- {key: ssd, operator: Exists, values: []}
上面资源配置清单中定义了节点软亲和性以选择运行在拥有zone=foo
和ssd
标签(无论其值为何)的节点之上,其中zone=foo
是更为重要的倾向性规则,权重为60,相比ssd
标签就没有那么关键,权重为30。这样一来,如果集群中拥有足够多的节点,那么它将被此规则分为四类:同时满足拥有zone=foo
和ssd
标签、仅具有zone=foo
标签、仅具有ssd
标签,以及不具备此两个标签,如下图所示:
# kubectl apply -f deploy-with-preferred-nodeaffinity-demo.yaml
deployment.apps/myapp-deploy-with-node-affinity created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-deploy-with-node-affinity-6d7884b4c8-kdg5n 1/1 Running 0 116s 10.244.1.11 k8s-node1
myapp-deploy-with-node-affinity-6d7884b4c8-rjn99 1/1 Running 0 116s 10.244.1.10 k8s-node1
myapp-deploy-with-node-affinity-6d7884b4c8-rnd4r 1/1 Running 0 116s 10.244.2.10 k8s-node2
这里只有两个工作节点,可以从上面的结果看出,node1
节点运行了两个pod
,这是因为上面定义的规则中,node1
节点同时具有zone=foo
标签和ssd
标签,且权重为60。
Pod 资源亲和调度
出于高效通信的需求,偶尔需要把一些
Pod
对象组织在相近的位置(同一节点、机架、区域或地区等),如一个业务的前端Pod
和后端Pod
等,此时可以将这些Pod
对象间的关系称为亲和性。偶尔,出于安全或分布式等原因也有可能需要将一些Pod
对象在其运行的位置上隔离开来,如在每个区域运行一个应用代理Pod
对象等,此时可以把这些Pod
对象间的关系称为反亲和性(anti-affinity)
Kubernetes
调度器通过内建的MatchInterPodAffinity
预选策略为这种调度方式完成节点预选,并给予InterPodAffinityPriority
优选函数进行各节点的优选级评估。
位置拓扑
Pod
亲和性调度需要各相关的Pod
对象运行于"同一位置",而反亲和性调度则需要它们不能运行于"同一位置"。何谓同一位置?事实上,取决于节点的位置拓扑,拓扑的方式不同,如下图"Pod资源与位置拓扑"中的Pod-A
和Pod-B
是否在同一位置的判定结果也可能有所不同
如果以基于各节点的kubernetes.io/hostname
标签作为评判标准,那么"同一位置"意味着同一节点,不同节点即不同的位置,如"基于节点的位置拓扑"图所示
如果是基于"故障转移域"来进行评判,那么server1
和server4
属于同一位置,而server2
和server3
属于另一个意义上的同一位置。
故此,在定义Pod
对象的亲和性与反亲和性时,需要借助于标签选择器来选择被依赖的Pod
对象 ,并根据选出的Pod
对象所在的节点的标签来判定"同一位置"的具体意义。
Pod 硬亲和调度
Pod硬亲和性调度也是用
requiredDuringSchedulingIgnoredDuringExecution
属性进行定义。Pod
亲和性用于描述一个Pod
对象与具有某特征的现存Pod
对象运行位置的依赖关系,因此,测试使用Pod
亲和性约束,需要事先存在被依赖的Pod
对象,具有特别的识别标签。
示例1
下面创建一个有着标签app=tomcat
的deployment资源部署一个Pod
对象:
# kubectl run tomcat -l app=tomcat --image tomcat:alpine
下面资源配置清单中定义了一个Pod
对象,通过labelsSelector
定义的标签选择器挑选感兴趣的现存Pod
对象,然后根据挑选出的Pod
对象所在节点的标签kubenetes.io/hostname
来判断同一位置的具体含义,并将当前Pod
对象调度至这一位置的某节点之上:
# vim required-podAffinity-pod1.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-pod-paffinity-1
namespace: default
spec:
containers:
- name: myapp
image: blwy/myapp:v1
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["tomcat"]}
topologyKey: kubernetes.io/hostname
事实上,kubernetes.io/hostname
标签是kubernetes
集群节点的内建标签,它的值为当前节点的节点主机名称标识,各个节点均不相同。因此,新建的Pod
对象将部署至被依赖的Pod
对象的同一节点之上。requiredDuringSchedulingIgnoredDuringExecution
表示这种亲和性为强制约束。
# kubectl apply -f required-podAffinity-pod1.yaml
pod/with-pod-paffinity-1 created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
tomcat-6c999f4c7d-x5x95 1/1 Running 0 12m 10.244.1.41 k8s-node1
with-pod-paffinity-1 1/1 Running 0 25s 10.244.2.42 k8s-node2
基于单一节点的Pod
亲和性只在极个别的情况下才可能会用到,较为常用的是基于同一地区、区域或几家的拓扑位置约束。例如部署应用程序myapp
与数据库db
服务相关的Pod
时,db pod
可能会部署于如下图"pod硬亲和性"所示的foo
或bar
这两个区域中的某个节点之上,依赖与数据服务的myapp pod
对象可部署于 db pod
所在区域的节点上。如果db pod
在两个区域foo
和bar
中各有副本运行,那么myapp
将可以运行于这两个区域的任何节点之上。
示例2
创建具有两个拥有标签为"app=db"的副本Pod
作为被依赖的资源,这里随机分配给了两个节点。
# kubectl run db -l app=db --image=redis:alpine --replicas=2
# kubectl get pods -l app=db -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
db-86f4768855-cdtq7 1/1 Running 0 32s 10.244.1.43 k8s-node1
db-86f4768855-pftfv 1/1 Running 0 32s 10.244.2.32 k8s-node2
于是,依赖于亲和于这两个Pod
的其他pod
对象可运行于zone
标签值为foo
和bar
的区域内的所有节点之上。下面进行定义一个Deployment
控制器管理的Pod
资源示例
# cat deploy-with-required-podAffinity-pod2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
在调度器示例中的Deployment控制器创建的Pod
资源时,调度器首先会基于标签选择器查询拥有标签"app=db"的所有Pod
资源,接着获取它们分别所属的节点的zone
标签值,接下来再查询拥有匹配这些标签值的所有节点,从而完成节点预选。根据优选函数计算这些节点的优先级,从而挑选出运行新建Pod
对象的节点。
# kubectl apply -f deploy-with-required-podAffinity-pod2.yaml
deployment.apps/myapp-with-pod-affinity created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
db-86f4768855-cdtq7 1/1 Running 0 132m 10.244.1.43 k8s-node1
db-86f4768855-pftfv 1/1 Running 0 132m 10.244.2.32 k8s-node2
myapp-with-pod-affinity-c775766d5-86c48 1/1 Running 0 5s 10.244.1.44 k8s-node1
myapp-with-pod-affinity-c775766d5-gdfxp 1/1 Running 0 5s 10.244.2.39 k8s-node2
myapp-with-pod-affinity-c775766d5-ptcfd 1/1 Running 0 5s 10.244.2.40 k8s-node2
❗️ 注意:如果节点上的标签在运行时发生了变更,以致不再满足Pod
上的亲和性规则,但该Pod
还将继续在该节点上运行,因此它仅会影响新建的Pod
资源;另外、labelSelector
属性仅匹配与被调度器的Pod
在同一名称空间中的Pod
资源,不过也可以通过为其天剑namespace
字段以指定其他名称空间。
Pod 软亲和调度
类似于节点亲和性机制,
Pod
也支持使用preferredDuringSchedulingIgnoredDuringExecution
属性定义柔性亲和性机制,调度器会尽力确保满足亲和性约束的调度逻辑,然而在约束条件不能得到满足时,它也允许将Pod
对象调度至其他节点运行。
示例
# vim deploy-with-perferred-podAffinity-pod.yaml
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:
containers:
- name: myapp
image: blwy/myapp:v1
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
上面资源配置清单中定义了两组亲和性判定机制,一个是选出cache Pod
所在节点的zone
标签,并赋予了较高的权重80,另一个是选择db Pod
所在节点的zone
标签,有着略低的权重20。于是,调度器会将目标节点分为四类:cache Pod
和db Pod
同时所属的zone
、cache Pod
单独所属的zone
、db Pod
单独所属的zone
,以及其他所有的zone
。
# kubectl apply -f deploy-with-perferred-podAffinity-pod.yaml
deployment.apps/myapp-with-preferred-pod-affinity created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
db-86f4768855-cdtq7 1/1 Running 0 160m 10.244.1.43 k8s-node1
db-86f4768855-pftfv 1/1 Running 0 160m 10.244.2.32 k8s-node2
myapp-with-preferred-pod-affinity-79b96db78b-24mnl 1/1 Running 0 7s 10.244.1.45 k8s-node1
myapp-with-preferred-pod-affinity-79b96db78b-l9rt9 1/1 Running 0 7s 10.244.2.41 k8s-node2
myapp-with-preferred-pod-affinity-79b96db78b-vwfbw 1/1 Running 0 7s 10.244.2.42 k8s-node2
Pod 反亲和调度
podAffinity
用于定义Pod
对象的亲和约束,对应地,将其替换为podAntiAffinity
即可用于定义Pod
对象的反亲和约束。反亲和性调度一般用于分散同一类应用的Pod
对象等,也包括将不同安全级别的Pod
对象调度至不同的区域、机架或节点等。
示例
这里将定义由同一Deployment
创建但彼此基于节点位置互斥的Pod
对象:
# vim deploy-with-required-podAntiAffinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-pod-anti-affinity
namespace: default
spec:
replicas: 4
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["myapp"]}
topologyKey: kubernetes.io/hostname
由于定义的强制性反亲和约束,因此,创建的4个Pod
副本必须运行于不同的节点中,不过,由于这里实验只有两台工作节点,因此,必然会有两个Pod
对象处于Pending
状态。
# kubectl apply -f deploy-with-required-podAntiAffinity-demo.yaml
deployment.apps/myapp-with-pod-anti-affinity created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-with-pod-anti-affinity-5d7ddf7766-8rql7 0/1 Pending 0 5s
myapp-with-pod-anti-affinity-5d7ddf7766-b85c6 1/1 Running 0 5s 10.244.2.25 k8s-node2
myapp-with-pod-anti-affinity-5d7ddf7766-t25xt 0/1 Pending 0 5s
myapp-with-pod-anti-affinity-5d7ddf7766-tbb94 1/1 Running 0 5s 10.244.1.15 k8s-node1
Pod
反亲和性调度也支持使用柔性约束机制,在调度时,将尽量满足不把位置相斥的Pod
对象调度于同一位置,但是,当约束条件无法得到满足时,也可以违反约束而调度。
污点和容忍度
污点(taints)是定义节点之上的键值型属性数据,用于让节点拒绝将
Pod
调度运行于其上,除非该Pod
对象具有接纳节点污点的容忍度。而容忍度(tolerations)是定义在Pod
对象上的键值型属性数据,用于配置其可容忍的节点污点,而且调度器仅能将Pod
对象调度至其能够容忍该节点污点的节点之上,如下图所示:
上面的节点选择器(nodeSelector)和节点亲和性(nodeAffinity)两种调度方式都是通过在
Pod
对象上添加标签选择器来完成对特定类型节点标签的匹配,它们实现的是由Pod
选择节点的机制。而污点和容忍度则是通过向节点添加污点信息来控制Pod
对象的调度结果,从而赋予了节点控制何种Pod
对象能够调度于其上的主控权。简单来说,节点亲和性使得Pod
对象被吸引到一类特定的节点,而污点则相反,提供了让节点排斥特定Pod
对象的能量。
Kubernetes
使用PodToleratesNodeTaints
预选策略和TaintTolerationPriority
优选函数来完成此种类型的高级调度机制。
定义污点和容忍度
污点定义在节点的nodeSpec
中,容忍度定义在Pod
的podSpec
中,都是键值型数据,都额外支持(effect)标记,语法格式为"key=value:effect",其中key
和value
的用法及格式与资源注解信息相似,而effect
则用于定义对Pod
对象的排斥等级,主要包含以下三种类型:
- NoSchedule:不能容忍此污点的新
Pod
对象不可调度至当前节点,属于强制型约束关系,节点上现存的Pod
对象不受影响。 - PreferNoSchedule:
NoSchedule
的柔性约束版本,即不能容忍此污点的新Pod
对象尽量不要调度至当前节点,不过无其它节点可供调度时也允许接受相应的Pod
对象。节点上现存的Pod
对象不受影响。 - NoExecute:不能容忍此污点的新
Pod
对象不可调度至当前节点,属于强制型约束关系,而且节点上现存的Pod
对象因节点污点变动或Pod
容忍度变动而不再满足匹配规则时,Pod
对象将被驱逐。
在Pod
对象上定义容忍度时,支持两种操作符:一种是等值比较(Equal),表示容忍度与污点必须在key
、value
和effect
三者之上完全匹配;另一种是存在性判断(Exists),表示二者的key
和effect
必须完全匹配,而容忍度中的value
字段要使用空值。
❗️ 注意:一个节点可以配置使用多个污点,一个Pod
对象也可以有多个容忍度,不过二者在进行匹配检查时遵循如下逻辑:
-
首先处理每个有着与之匹配的容忍度的污点。
-
不能匹配到的污点上,如果存在了一个使用
NoSchedule
效用标识,则拒绝调度Pod
对象至此节点。 -
不能匹配到的污点上,若没有任何一个使用了
NoSchedule
效用标识,但至少有一个使用了PreferNoScheduler
,则应尽量避免将Pod
对象调度至此节点。 -
如果至少有一个不匹配的污点使用了
NoExecute
效用标识,则节点将立即驱逐Pod
对象,或者不予调度至给定节点;另外,即便容忍度可以匹配到使用了NoExecute
效用标识的污点,若在定义容忍度时还同时使用tolerationSeconds
属性定义了容忍时限,则超出时限后期也将被节点驱逐。
使用kubeadm
部署的Kubernetes
集群,其Master
节点将自动添加污点信息以阻止不能容忍此污点的Pod
对象调度至此节点,因此,用户手动创建的未特意添加容忍此污点容忍度的Pod
对象将不会被调度至此节点:
# kubectl describe node k8s-master
Name: k8s-master
Roles: master
......
Taints: node-role.kubernetes.io/master:NoSchedule
不过,有些系统级应用,如kube-proxy
或者kube-flannel
等,都在资源创建时就添加上了相应的容忍度以确保它们被DaemonSet
控制器创建时能够调度至Master
节点运行一个实例:
# kubectl describe pods kube-flannel-ds-amd64-bg7gc -n kube-system
......
Node-Selectors:
Tolerations: :NoSchedule
node.kubernetes.io/disk-pressure:NoSchedule
node.kubernetes.io/memory-pressure:NoSchedule
node.kubernetes.io/network-unavailable:NoSchedule
node.kubernetes.io/not-ready:NoExecute
node.kubernetes.io/pid-pressure:NoSchedule
node.kubernetes.io/unreachable:NoExecute
node.kubernetes.io/unschedulable:NoSchedule
另外,这类Pod
是构成Kubernetes
系统的基础且关键性的组件,它们甚至还定义了更大的容忍度。通过上面的kube-flannel
实例的容忍度定义来看,它还能容忍那些报告了磁盘压力或内存压力的节点,以及未就绪的节点和不可达的节点,以确保它们能在任何状态下正常调度至集群节点上运行。
管理节点的污点
任何符合其键值规范要求的字符串均可用于定义污点信息:仅可使用字母、数字、连接符、点号和下划线,且仅能以字母或数字开头,其中键名长度上限为253个字符,值最长为63个字符。可以用污点用来描述具体的部署规划,键名比如node-type、node-role、node-project或node-geo等,还可以在必要时戴上域名以描述其额外的信息,如node-type.ilinux.io等。
添加污点
kubectl taint
命令即可向节点添加污点,命令格式如下:
kubectl taint nodes =: ...
例如,使用"node-type=production:NoSchedule"定义节点k8s-node1
# kubectl taint nodes k8s-node1 node-type=production:NoSchedule
node/k8s-node1 tainted
此时,k8s-node1
上已有的Pod
对象不受影响,但新建的Pod
若不能容忍此污点将不能再被调度至此节点。通过下面的命令可以查看节点的污点信息:
# kubectl get nodes k8s-node1 -o go-template={{.spec.taints}}
[map[effect:NoSchedule key:node-type value:production]]
需要注意的是,即便是同一个键值数据,若其效用标识不同,则其也分属于不同的污点信息,例如,将上面命令中的效用标识定义为PreferNoSchedule
再添加一次:
# kubectl taint nodes k8s-node1 node-type=production:PreferNoSchedule
node/k8s-node1 tainted
删除污点
删除污点,仍通过kubectl taint
命令进行,但是要使用如下的命令格式,省略效用标识则表示删除使用指定键名的所有污点,否则就只是删除指定健名上对应效用标识的污点:
kubectl taint nodes [:]-
例如,删除k8s-node1
上node-type
键的效用标识为NoSchedule
的污点信息:
# kubectl taint nodes k8s-node1 node-type:NoSchedule-
node/k8s-node1 untainted
若要删除使用指定健名的所有污点,则在删除命令中省略效用标识即能实现,例如:
# kubectl taint nodes k8s-node1 node-type-
node/k8s-node1 untainted
删除节点上的全部污点信息,通过kubectp patch
命令将节点属性spec.taints
的值直接置空,例如:
# kubectl patch nodes k8s-node1 -p '{"spce":{"taints":[]}}'
节点污点的变动会影响到新建Pod
对象的调度结果,而且使用NoExecute
进行标识时,还会影响到节点上现有的Pod
对象。
Pod 对象的容忍度
Pod
对象的容忍度可通过其spec.tolerations
字段进行添加,根据使用的操作符不同,主要有两种可用的形式:一种是与污点信息完全匹配的等值关系;另一种是判断污点信息存在性的匹配方式。使用Equal
操作符的示例如下所示,其中tolerationSeconds
用于定义延迟驱逐当前Pod
对象的时长:
示例
首先给两个工作节点添加不同的污点
# kubectl taint node k8s-node1 node-type=production:NoSchedule
# kubectl taint node k8s-node2 node-type=dev:NoExecute
编写示例yaml文件
# vim deploy-taint-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-taint-demo-pod
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: blwy/myapp:v1
ports:
- name:
containerPort: 80
tolerations:
- key: "node-type"
operator: "Equal"
value: "production"
effect: "NoExecute"
tolerationSeconds: 30
创建资源,可以发现目前pod一直处于Pending
状态,这是因为两个节点都不能容忍。
# kubectl apply -f deploy-taint-demo.yaml
deployment.apps/deploy-taint-demo-pod created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-taint-demo-pod-7459f65d64-426vg 0/1 Pending 0 4s
deploy-taint-demo-pod-7459f65d64-h6fbp 0/1 Pending 0 4s
deploy-taint-demo-pod-7459f65d64-k4xjw 0/1 Pending 0 4s
修改资源配置清单,将容忍度改为NoSchedule
, 这个示例中表示容忍key为node-type
值为production
的污点
# vim deploy-taint-demo.yaml
tolerations:
- key: "node-type"
operator: "Equal"
value: "production"
effect: "NoSchedule"
重新创建,可以发现已经调度到k8s-node1
节点上
# kubectl apply -f deploy-taint-demo.yaml
deployment.apps/deploy-taint-demo-pod configured
[root@k8s-master1 schedule]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-taint-demo-pod-87cffffdd-9tm29 1/1 Running 0 3s 10.244.1.24 k8s-node1
deploy-taint-demo-pod-87cffffdd-qt8wf 1/1 Running 0 2s 10.244.1.25 k8s-node1
deploy-taint-demo-pod-87cffffdd-zgmrl 1/1 Running 0 4s 10.244.1.23 k8s-node1
还可以通过Exists
进行判断,修改资源配置清单如下,下面这段表示,只要有污点key为node-type
,不论值为什么且是什么类型的污点,都能容忍
# vim deploy-taint-demo.yaml
tolerations:
- key: "node-type"
operator: "Exists"
value: ""
effect: ""
查看结果可以看出,两个节点都已经进行调度了。
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-taint-demo-pod-7c87f4957f-4njh7 1/1 Running 0 16s 10.244.2.27 k8s-node2
deploy-taint-demo-pod-7c87f4957f-mj46d 1/1 Running 0 14s 10.244.1.26 k8s-node1
deploy-taint-demo-pod-7c87f4957f-vp9t2 1/1 Running 0 13s 10.244.2.28 k8s-node2
问题节点标识
Kubernetes
从1.6
版本起支持使用污点自动标识问题节点,通过节点控制器在特定条件下自动为节点添加污点信息实现。它们都是用NoExecute
效用标识,因此不能容忍此类污点的现有Pod
对象也会遭到驱逐。目前,内建使用的此类污点包含如下几个:
✏️ node.kubernetes.io/not-ready:节点进入“NotReady”状态时被自动添加的污点。
✏️ node.alpha.kubernetes.io/unreachable:节点进入“NotReachable”状态时被自动添加的污点。
✏️ node.kubernetes.io/out-of-disk:节点进入“OutOfDisk”状态时被自动添加的污点。
✏️ node.kubernetes.io/memory-pressure:节点内存资源面临压力。
✏️ node.kubernetes.io/disk-pressure:节点磁盘资源面临压力。
✏️ node.kubernetes.io/network-unavailable:节点网络不可用。
✏️ node.cloudprovider.kubernetes.io/uninitialized:kubelet
由外部的云环境程序启动时,它将自动为节点添加此污点,待到云控制器管理器中的控制器初始化此节点时再将其删除。