(十四)Kubernetes Pod调度器

网友投稿 247 2022-09-11

(十四)Kubernetes Pod调度器

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.yamlapiVersion: v1kind: Podmetadata: name: with-required-nodeaffinity namespace: default labels: app: myappspec: 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.yamlpod/with-required-nodeaffinity created# kubectl get podsNAME READY STATUS RESTARTS AGEwith-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=foonode/k8s-node1 labeled# kubectl get podsNAME READY STATUS RESTARTS AGEwith-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=truenode/k8s-node1 labeled# kubectl label node k8s-node2 ssd=truenode/k8s-node2 labeled# kubectl label node k8s-node2 zone=barnode/k8s-node2 labeled查看节点标签# kubectl get nodes --show-labelsNAME STATUS ROLES AGE VERSION LABELSk8s-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=fook8s-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.yamlapiVersion: v1kind: Podmetadata: name: with-required-nodeaffinity-2 namespace: default labels: app: myappspec: 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.yamlpod/with-required-nodeaffinity-2 created# kubectl get podsNAME READY STATUS RESTARTS AGEwith-required-nodeaffinity-2 1/1 Running 0 4s# kubectl describe pods with-required-nodeaffinity-2Events: 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.yamlapiVersion: v1kind: Podmetadata: name: with-required-nodeaffinity-3 namespace: default labels: app: myappspec: 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.yamlpod/with-required-nodeaffinity-3 created# kubectl get podsNAME READY STATUS RESTARTS AGEwith-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.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: myapp-deploy-with-node-affinity namespace: defaultspec: 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.yamldeployment.apps/myapp-deploy-with-node-affinity created# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESmyapp-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.yamlapiVersion: v1kind: Podmetadata: name: with-pod-paffinity-1 namespace: defaultspec: 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.yamlpod/with-pod-paffinity-1 created# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATEStomcat-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 wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESdb-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.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: myapp-with-pod-affinityspec: 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.yamldeployment.apps/myapp-with-pod-affinity created# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESdb-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.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: myapp-with-preferred-pod-affinityspec: 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.yamldeployment.apps/myapp-with-preferred-pod-affinity created# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESdb-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.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: myapp-with-pod-anti-affinity namespace: defaultspec: 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.yamldeployment.apps/myapp-with-pod-anti-affinity created# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESmyapp-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-masterName: k8s-masterRoles: 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:NoSchedulenode/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:PreferNoSchedulenode/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.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: deploy-taint-demo-pod namespace: defaultspec: 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.yamldeployment.apps/deploy-taint-demo-pod created# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESdeploy-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.yamldeployment.apps/deploy-taint-demo-pod configured[root@k8s-master1 schedule]# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESdeploy-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 wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESdeploy-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​​由外部的云环境程序启动时,它将自动为节点添加此污点,待到云控制器管理器中的控制器初始化此节点时再将其删除。

人生是条无名的河,是浅是深都要过; 人生是杯无色的茶,是苦是甜都要喝; 人生是首无畏的歌,是高是低都要唱。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:(十三)Kubernetes Dashboard
下一篇:广告情报局:好文案,不会 “对牛弹琴”。
相关文章

 发表评论

暂时没有评论,来抢沙发吧~