Kubernetes-高级调度
九、高级调度
在第八章中,介绍了调度器的工作过程,以及调度器的过滤和打分行为,这些操作都是k8s的自主行为,用户并不需要干预。而高级调度就是指Kubernetes允许你去影响pod被调度到哪个节点。
1 污点和容忍度
1.1 介绍污点和容忍度
首先要介绍的高级调度的两个特性是节点污点, 以及pod对于污点的容忍度,这些特性可以阻止pod调度到特定节点。
先来看看现有节点的污点信息,默认情况下,一个集群中的主节点需要设置污点,这样才能保证只有控制面板pod才能部署在主节点上:
1 | kubectl describe node k8s-master |
输出的结果如下(部分):
1 | Name: k8s-master |
Taints就是污点信息。主节点包含一个污点,污点包含了一个key、value,以及一个effect。
这里,主节点包含一个为node-role.kubernetes.io/master的key ,一个空的value,以及值为NoSchedule的effect。这个污点将阻止pod调度到这个节点上面,除非有pod能容忍这个污点,而通常容忍这个污点的pod都是系统级别pod。如下图所示,—个pod只有容忍了节点的污点,才能被调度到该节点上面:

前面提到,kube-proxy集群组件以pod的形式运行在每个节点上,其中也包括主节点。为了确保kube-proxy pod也能够运行在主节点上,该pod需要添加相应的污点容忍度。我们可以查看一下:
1 | kubectl describe pod kube-proxy-k4nlq -n kube-system |
在输出的结果中,查看Toleration列:
1 | ... |
每一个污点都可以关联一个效果, 效果包含了以下三种
- NoSchedule表示如果pod没有容忍这些污点, pod则不能被调度到包含这些污点的节点上。
- PreferNoSchedule是NoSchedule的一个宽松的版本,表示尽量阻止pod被调度到这个节点上,但是如果没有其他节点可以调度, pod 依然会被调度到这个节点上。
- NoExecute不同于NoSchedule以及PreferNoSchedule,后两者只在调度期间起作用,而NoExecute也会影响正在节点上运行着的pod。如果在一个节点上添加了NoExecute污点,那些在该节点上运行着的pod,如果没有容忍这个NoExecute污点,将会从这个节点驱逐。
1.2 在节点上添加自定义污点
可以使用命令kubectl taint给节点增加一个污点。比如:
1 | kubectl taint nodes k8s-node-1 key1=value1:NoSchedule |
给节点k8s-node-1增加一个污点,它的键名是key1,键值是value1,效果是NoSchedule。这表示只有拥有和这个污点相匹配的容忍度的pod才能够被分配到k8s-node-1这个节点。如果现在你部署一个常规pod的多个副本,你会发现没有一个pod被部署到你添加了污点信息的节点上面。
现在尝试通过Deployment部署10个副本的pod,资源清单如下:
1 | apiVersion: apps/v1 |
运行成功后,查看pod信息:
1 | kubectl get po -o wide |
结果如下图所示,所有的pod都没有被部署在拥有污点的节点上。

若要移除上述命令所添加的污点,你可以执行:
1 | kubectl taint nodes k8s-node-1 key1=value1:NoSchedule- |
1.3 在pod上添加污点容忍度
如果想要pod部署到这些添加污点的节点,那就需要pod能容忍那些你添加在节点上的污点。在pod的资源清单中添加容忍度:
1 | apiVersion: apps/v1 |
再次查看结果如下图,添加了与污点相匹配的容忍度,使得pod可以部署在k8s-node-1节点:

operator 的默认值是 Equal。
一个容忍度和一个污点相“匹配”是指它们有一样的键名(key)和效果(effects),并且:
- 如果
operator是Exists(此时容忍度不能指定value),或者 - 如果
operator是Equal,则它们的value应该相等
存在两种特殊情况:
如果一个容忍度的
key为空且 operator 为Exists, 表示这个容忍度与任意的 key 、value 和 effect 都匹配,即这个容忍度能容忍任意 taint。如果
effect为空,则可以与所有键名key1的效果相匹配。
可以给一个节点添加多个污点,也可以给一个 Pod 添加多个容忍度设置。 Kubernetes 处理多个污点和容忍度的过程就像一个过滤器:从一个节点的所有污点开始遍历,过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了 Pod 是否会被分配到该节点,特别是以下情况:
- 如果未被过滤的污点中存在至少一个 effect 值为
NoSchedule的污点, 则 Kubernetes 不会将 Pod 分配到该节点。 - 如果未被过滤的污点中不存在 effect 值为
NoSchedule的污点, 但是存在 effect 值为PreferNoSchedule的污点, 则 Kubernetes 会尝试不将 Pod 分配到该节点。 - 如果未被过滤的污点中存在至少一个 effect 值为
NoExecute的污点, 则 Kubernetes 不会将 Pod 分配到该节点(如果 Pod 还未在节点上运行), 或者将 Pod 从该节点驱逐(如果 Pod 已经在节点上运行)。
例如,假设给一个节点添加了如下污点
1 | kubectl taint nodes node1 key1=value1:NoSchedule |
假定有一个Pod,它有两个容忍度:
1 | tolerations: |
在这种情况下,上述 Pod 不会被分配到上述节点,因为其没有容忍度和第三个污点相匹配。 但是如果在给节点添加上述污点之前,该 Pod 已经在上述节点运行, 那么它还可以继续运行在该节点上,因为第三个污点是三个污点中唯一不能被这个 Pod 容忍的。
通常情况下,如果给一个节点添加了一个 effect 值为 NoExecute 的污点, 则任何不能忍受这个污点的 Pod 都会马上被驱逐, 任何可以忍受这个污点的 Pod 都不会被驱逐。 但是,如果 Pod 存在一个 effect 值为 NoExecute 的容忍度指定了可选属性 tolerationSeconds 的值,则表示在给节点添加了上述污点之后, Pod 还能继续在节点上运行的时间。例如,
1 | tolerations: |
这表示如果这个 Pod 正在运行,同时一个匹配的污点被添加到其所在的节点, 那么 Pod 还将继续在节点上运行 3600 秒,然后被驱逐。 如果在此之前上述污点被删除了,则 Pod 不会被驱逐。
1.4 基于污点的驱逐
前文提到过污点的 effect 值 NoExecute会影响已经在节点上运行的 Pod
- 如果 Pod 不能忍受 effect 值为
NoExecute的污点,那么 Pod 将马上被驱逐 - 如果 Pod 能够忍受 effect 值为
NoExecute的污点,但是在容忍度定义中没有指定tolerationSeconds,则 Pod 还会一直在这个节点上运行。 - 如果 Pod 能够忍受 effect 值为
NoExecute的污点,而且指定了tolerationSeconds, 则 Pod 还能在这个节点上继续运行这个指定的时间长度。
当某种条件为真时,节点控制器会自动给节点添加一个污点。当前内置的污点包括:
node.kubernetes.io/not-ready:节点未准备好。这相当于节点状态Ready的值为 “False”。node.kubernetes.io/unreachable:节点控制器访问不到节点. 这相当于节点状态Ready的值为 “Unknown”。node.kubernetes.io/memory-pressure:节点存在内存压力。node.kubernetes.io/disk-pressure:节点存在磁盘压力。node.kubernetes.io/pid-pressure: 节点的 PID 压力。node.kubernetes.io/network-unavailable:节点网络不可用。node.kubernetes.io/unschedulable: 节点不可调度。node.cloudprovider.kubernetes.io/uninitialized:如果 kubelet 启动时指定了一个 “外部” 云平台驱动, 它将给当前节点添加一个污点将其标志为不可用。在 cloud-controller-manager 的一个控制器初始化这个节点后,kubelet 将删除这个污点。
在节点被驱逐时,节点控制器或者 kubelet 会添加带有 NoExecute 效应的相关污点。 如果异常状态恢复正常,kubelet 或节点控制器能够移除相关的污点。
使用这个功能特性,结合 tolerationSeconds,Pod 就可以指定当节点出现一个 或全部上述问题时还将在这个节点上运行多长的时间。
比如,一个使用了很多本地状态的应用程序在网络断开时,仍然希望停留在当前节点上运行一段较长的时间, 愿意等待网络恢复以避免被驱逐。在这种情况下,Pod 的容忍度可能是下面这样的:
1 | tolerations: |
Kubernetes 会自动给 Pod 添加一个 key 为
node.kubernetes.io/not-ready的容忍度 并配置tolerationSeconds=300,除非用户提供的 Pod 配置中已经已存在了 key 为node.kubernetes.io/not-ready的容忍度。同样,Kubernetes 会给 Pod 添加一个 key 为
node.kubernetes.io/unreachable的容忍度 并配置tolerationSeconds=300,除非用户提供的 Pod 配置中已经已存在了 key 为node.kubernetes.io/unreachable的容忍度。这种自动添加的容忍度意味着在其中一种问题被检测到时 Pod 默认能够继续停留在当前节点运行 5 分钟。
DaemonSet中的 Pod 被创建时, 针对以下污点自动添加的 NoExecute 的容忍度将不会指定 tolerationSeconds:
node.kubernetes.io/unreachablenode.kubernetes.io/not-ready
这保证了出现上述问题时 DaemonSet 中的 Pod 永远不会被驱逐。
2 节点亲和性/反亲和性
在介绍节点亲和性之前,先来看一种更加简单的节点约束方式nodeSelector。
2.1 nodeSelector
来看一个使用 nodeSelector 的例子。
执行 kubectl get nodes 命令获取集群的节点名称。 选择一个你要增加标签的节点,然后执行 kubectl label nodes <node-name> <label-key>=<label-value> 命令将标签添加到你所选择的节点上。 例如,如果你的节点名称为k8s-node-1并且想要的标签是 disktype=ssd,则可以执行下面的命令。
1 | kubectl label nodes k8s-node-1 disktype=ssd |
重新运行 kubectl get nodes --show-labels,查看节点当前具有了所指定的标签来验证它是否有效。 你也可以使用 kubectl describe node "nodename" 命令查看指定节点的标签完整列表。
然后,选择任何一个想运行的Pod的资源清单,并且在其中添加一个nodeSelector部分。 比如利用Deployment部署pod:
1 | apiVersion: apps/v1 |
然后部署这个Deployment,查看部署情况:
1 | kubectl get pod -o wide |
如下图,所有的pod都被部署在满足标签disktype=ssd的节点上(即k8s-node-1)

2.2 节点亲和性/反亲和性
nodeSelector要求节点必须包含所有pod对应字段中的指定标签,才能成为pod调度的目标节点。节点选择器实现简单,但是它不能满足你的所有需求。正因为如此,一种更强大的机制被引入。就是节点亲和性/反亲和性。
亲和性/反亲和性功能极大地扩展了你可以表达约束的类型。关键的增强点包括:
- 语言更具表现力(不仅仅是“对完全匹配规则的 AND”)
- 你可以发现规则是“软需求”/“偏好”,而不是硬性要求,因此, 如果调度器无法满足该要求,仍然调度该 Pod
- 你可以使用节点上(或其他拓扑域中)的 Pod 的标签来约束,而不是使用 节点本身的标签,来允许哪些 pod 可以或者不可以被放置在一起。
亲和性功能包含两种类型的亲和性,即“节点亲和性”和“Pod 间亲和性/反亲和性”。 节点亲和性就像现有的 nodeSelector(但具有上面列出的前两个好处),然而 Pod 间亲和性/反亲和性约束 Pod 标签而不是节点标签。
目前有两种类型的节点亲和性,分别为
requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution
这是一个极其长的名字,所以,让我们先重点关注这个名字。让我们把这个名字分成两部分,然后分别看下它们的含义:
requiredDuringScheduling...表明了该宇段下定义的规则,为了让pod能调度到该节点上,明确指出了该节点必须包含的标签preferredDuringScheduling...表明了该宇段下定义的规则,优先尝试调度到该节点,如果不可行,调度到其它节点也可以接受...IgnoredDuringExecution表明了该字段下定义的规则,不会影响己经在节点上运行着的pod
这二者的区别可以看做是“硬需求”和“软需求”。节点亲和性通过 PodSpec 的 affinity 字段下的 nodeAffinity 字段进行指定。
下面是一个使用节点亲和性的例子:
1 | apiVersion: apps/v1 |
此节点亲和性规则表示,Pod 只能放置在具有标签键 disktype 且标签值为 ssd 或 ssd2 的节点上。 另外,在满足这些标准的节点中,具有标签键为 another-node-label-key 且标签值为 another-node-label-value 的节点应该优先使用。
你可以在上面的例子中看到 In 操作符的使用。新的节点亲和性语法支持下面的操作符:
In,标签的值在某个列表中NotIn,标签的值不在某个列表中Exists,标签存在DoesNotExist,标签不存在Gt,标签的值大于某个值Lt,标签的值小于某个值
你可以使用 NotIn 和 DoesNotExist 来实现节点反亲和性行为,或者使用污点将 Pod 从特定节点中驱逐。
如果你同时指定了
nodeSelector和nodeAffinity,两者必须都要满足,才能将 Pod 调度到候选节点上。如果你指定了多个与
nodeAffinity类型关联的nodeSelectorTerms,则如果其中一个nodeSelectorTerms满足的话,pod将可以调度到节点上。如果你指定了多个与
nodeSelectorTerms关联的matchExpressions,则只有当所有matchExpressions满足的话,Pod 才会可以调度到节点上。如果你修改或删除了 pod 所调度到的节点的标签,Pod 不会被删除。 换句话说,亲和性选择只在 Pod 调度期间有效。
preferredDuringSchedulingIgnoredDuringExecution中的weight字段值的范围是 1-100。 对于每个符合所有调度要求(资源请求、RequiredDuringScheduling 亲和性表达式等) 的节点,调度器将遍历该字段的元素来计算总和,并且如果节点匹配对应的 MatchExpressions,则添加“权重”到总和。 然后将这个评分与该节点的其他优先级函数的评分进行组合。 总分最高的节点是最优选的。
2.3 pod间亲和性/反亲和性
Pod 间亲和性与反亲和性使你可以基于已经在节点上运行的 Pod 的标签来约束 Pod 可以调度到的节点,而不是基于节点上的标签。
与节点亲和性一样,当前有两种类型的 Pod 亲和性与反亲和性,即 requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution,分别表示“硬性”与“软性”要求。
Pod 间亲和性通过 PodSpec 中 affinity 字段下的 podAffinity 字段进行指定。 而 Pod 间反亲和性通过 PodSpec 中 affinity 字段下的 podAntiAffinity 字段进行指定。
下面是一个使用pod间亲和性的例子:
1 | apiVersion: v1 |
在这个 Pod 的亲和性配置定义了一条 Pod 亲和性规则和一条 Pod 反亲和性规则。 在此示例中,podAffinity 配置为 requiredDuringSchedulingIgnoredDuringExecution, 然而 podAntiAffinity 配置为 preferredDuringSchedulingIgnoredDuringExecution。
Pod 亲和性规则表示,仅当节点和至少一个已运行且有键为“security”且值为“S1”的标签 的 Pod 处于同一区域时,才可以将该 Pod 调度到节点上。(更确切的说,如果节点 N 具有带有键 topology.kubernetes.io/zone 和某个值 V 的标签, 则 Pod 有资格在节点 N 上运行,以便集群中至少有一个节点具有键 topology.kubernetes.io/zone 和值为 V 的节点正在运行具有键“security”和值 “S1”的标签的 pod。)
Pod 反亲和性规则表示,如果节点处于 Pod 所在的同一可用区且具有键“security”和值“S2”的标签, 则该 pod 不应将其调度到该节点上。 (如果 topologyKey 为 topology.kubernetes.io/zone,则意味着当节点和具有键 “security”和值“S2”的标签的 Pod 处于相同的区域,Pod 不能被调度到该节点上。)
Pod 亲和性与反亲和性的合法操作符有 In,NotIn,Exists,DoesNotExist。
原则上,topologyKey 可以是任何合法的标签键。 然而,出于性能和安全原因,topologyKey 受到一些限制:
- 对于 Pod 亲和性而言,在
requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution中,topologyKey不允许为空。 - 对于 Pod 反亲和性而言,
requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution中,topologyKey都不可以为空。 - 对于
requiredDuringSchedulingIgnoredDuringExecution要求的 Pod 反亲和性, 准入控制器LimitPodHardAntiAffinityTopology被引入以确保topologyKey只能是kubernetes.io/hostname。如果你希望topologyKey也可用于其他定制 拓扑逻辑,你可以更改准入控制器或者禁用之。 - 除上述情况外,
topologyKey可以是任何合法的标签键。
注意:
Pod 间亲和性与反亲和性需要大量的处理,这可能会显著减慢大规模集群中的调度。 不建议在超过数百个节点的集群中使用它们。
Pod 反亲和性需要对节点进行一致的标记,即集群中的每个节点必须具有适当的标签能够匹配
topologyKey。如果某些或所有节点缺少指定的topologyKey标签,可能会导致意外行为。
3 固定节点
最后,来看一种最为简单的节点调度nodeName。nodeName 是节点选择约束的最简单方法,但是由于其自身限制,通常不使用它。 nodeName 是 PodSpec 的一个字段。 如果它不为空,调度器将忽略 Pod,并且给定节点上运行的 kubelet 进程尝试执行该 Pod。 因此,如果 nodeName 在 PodSpec 中指定了,则它优先于上面的节点选择方法。
下面的是使用 nodeName 字段的 Pod 配置文件的例子:
1 | apiVersion: v1 |
上面的 pod 将运行在 kube-01 节点上。
使用 nodeName 来选择节点可能有一些限制:
- 指定的节点可能不存在
- 如果指定的节点没有资源来容纳 Pod,Pod 将会调度失败并且其原因将显示为
OutOfmemory或OutOfcpu - 云环境中的节点名称并非总是可预测或稳定的