k8s的pod的污点容忍度和亲和性说明[k8s pod tanit-tolerance and affinity]
介绍k8s的pod的污点容忍度和亲和性
污点和容忍度
节点亲和性 是 Pod 的一种属性,它使 Pod 被吸引到一类特定的节点 (这可能出于一种偏好,也可能是硬性要求)。 污点(Taint) 则相反——它使节点能够排斥一类特定的 Pod。
容忍度(Toleration) 是应用于 Pod 上的。容忍度允许调度器调度带有对应污点的节点。 容忍度允许调度但并不保证调度:作为其功能的一部分, 调度器也会评估其他参数。
污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod, 是不会被该节点接受的。
概念
你可以使用命令 kubectl taint 给节点增加一个污点。比如,
|
|
给节点 node1
增加一个污点,它的键名是 key1
,键值是 value1
,效果是 NoSchedule
。 这表示只有拥有和这个污点相匹配的容忍度的 Pod 才能够被分配到 node1
这个节点。
若要移除上述命令所添加的污点,你可以执行:
|
|
你可以在 Pod 规约中为 Pod 设置容忍度。 下面两个容忍度均与上面例子中使用 kubectl taint
命令创建的污点相匹配, 因此如果一个 Pod 拥有其中的任何一个容忍度,都能够被调度到 node1
:
|
|
这里是一个使用了容忍度的 Pod:
|
|
operator
的默认值是 Equal
。
一个容忍度和一个污点相“匹配”是指它们有一样的键名和效果,并且:
- 如果
operator
是Exists
(此时容忍度不能指定value
),或者 - 如果
operator
是Equal
,则它们的value
应该相等
说明:
存在两种特殊情况:
如果一个容忍度的
key
为空且operator
为Exists
, 表示这个容忍度与任意的 key、value 和 effect 都匹配,即这个容忍度能容忍任何污点。如果
effect
为空,则可以与所有键名key1
的效果相匹配。
上述例子中 effect
使用的值为 NoSchedule
,你也可以使用另外一个值 PreferNoSchedule
。 这是“优化”或“软”版本的 NoSchedule
—— 系统会 尽量 避免将 Pod 调度到存在其不能容忍污点的节点上, 但这不是强制的。effect
的值还可以设置为 NoExecute
,下文会详细描述这个值。
你可以给一个节点添加多个污点,也可以给一个 Pod 添加多个容忍度设置。 Kubernetes 处理多个污点和容忍度的过程就像一个过滤器:从一个节点的所有污点开始遍历, 过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了 Pod 是否会被分配到该节点,特别是以下情况:
- 如果未被忽略的污点中存在至少一个 effect 值为
NoSchedule
的污点, 则 Kubernetes 不会将 Pod 调度到该节点。 - 如果未被忽略的污点中不存在 effect 值为
NoSchedule
的污点, 但是存在 effect 值为PreferNoSchedule
的污点, 则 Kubernetes 会 尝试 不将 Pod 调度到该节点。 - 如果未被忽略的污点中存在至少一个 effect 值为
NoExecute
的污点, 则 Kubernetes 不会将 Pod 调度到该节点(如果 Pod 还未在节点上运行), 或者将 Pod 从该节点驱逐(如果 Pod 已经在节点上运行)。
例如,假设你给一个节点添加了如下污点
|
|
假定有一个 Pod,它有两个容忍度:
|
|
在这种情况下,上述 Pod 不会被调度到上述节点,因为其没有容忍度和第三个污点相匹配。 但是如果在给节点添加上述污点之前,该 Pod 已经在上述节点运行, 那么它还可以继续运行在该节点上,因为第三个污点是三个污点中唯一不能被这个 Pod 容忍的。
通常情况下,如果给一个节点添加了一个 effect 值为 NoExecute
的污点, 则任何不能忍受这个污点的 Pod 都会马上被驱逐,任何可以忍受这个污点的 Pod 都不会被驱逐。 但是,如果 Pod 存在一个 effect 值为 NoExecute
的容忍度指定了可选属性 tolerationSeconds
的值,则表示在给节点添加了上述污点之后, Pod 还能继续在节点上运行的时间。例如,
|
|
这表示如果这个 Pod 正在运行,同时一个匹配的污点被添加到其所在的节点, 那么 Pod 还将继续在节点上运行 3600 秒,然后被驱逐。 如果在此之前上述污点被删除了,则 Pod 不会被驱逐。
使用例子
通过污点和容忍度,可以灵活地让 Pod 避开某些节点或者将 Pod 从某些节点驱逐。 下面是几个使用例子:
专用节点:如果你想将某些节点专门分配给特定的一组用户使用,你可以给这些节点添加一个污点(即,
kubectl taint nodes nodename dedicated=groupName:NoSchedule
), 然后给这组用户的 Pod 添加一个相对应的容忍度 (通过编写一个自定义的准入控制器来实现)。 拥有上述容忍度的 Pod 就能够被调度到上述专用节点,同时也能够被调度到集群中的其它节点。 如果你希望这些 Pod 只能被调度到上述专用节点, 那么你还需要给这些专用节点另外添加一个和上述污点类似的 label (例如:dedicated=groupName
), 同时还要在上述准入控制器中给 Pod 增加节点亲和性要求,要求上述 Pod 只能被调度到添加了dedicated=groupName
标签的节点上。配备了特殊硬件的节点:在部分节点配备了特殊硬件(比如 GPU)的集群中, 我们希望不需要这类硬件的 Pod 不要被调度到这些特殊节点,以便为后继需要这类硬件的 Pod 保留资源。 要达到这个目的,可以先给配备了特殊硬件的节点添加污点 (例如
kubectl taint nodes nodename special=true:NoSchedule
或kubectl taint nodes nodename special=true:PreferNoSchedule
), 然后给使用了这类特殊硬件的 Pod 添加一个相匹配的容忍度。 和专用节点的例子类似,添加这个容忍度的最简单的方法是使用自定义 准入控制器。 比如,我们推荐使用扩展资源 来表示特殊硬件,给配置了特殊硬件的节点添加污点时包含扩展资源名称, 然后运行一个 ExtendedResourceToleration 准入控制器。此时,因为节点已经被设置污点了,没有对应容忍度的 Pod 不会被调度到这些节点。 但当你创建一个使用了扩展资源的 Pod 时,ExtendedResourceToleration
准入控制器会自动给 Pod 加上正确的容忍度,这样 Pod 就会被自动调度到这些配置了特殊硬件的节点上。 这种方式能够确保配置了特殊硬件的节点专门用于运行需要这些硬件的 Pod, 并且你无需手动给这些 Pod 添加容忍度。基于污点的驱逐: 这是在每个 Pod 中配置的在节点出现问题时的驱逐行为, 接下来的章节会描述这个特性。
基于污点的驱逐
特性状态: Kubernetes v1.18 [stable]
前文提到过污点的效果值 NoExecute
会影响已经在节点上运行的 Pod,如下
- 如果 Pod 不能忍受这类污点,Pod 会马上被驱逐
- 如果 Pod 能够忍受这类污点,但是在容忍度定义中没有指定
tolerationSeconds
, 则 Pod 还会一直在这个节点上运行。 - 如果 Pod 能够忍受这类污点,而且指定了
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 或节点控制器能够移除相关的污点。
说明:
控制面会限制向节点添加新污点的速率。这一速率限制可以管理多个节点同时不可达时 (例如出现网络中断的情况),可能触发的驱逐的数量。
你可以为 Pod 设置 tolerationSeconds
,以指定当节点失效或者不响应时, Pod 维系与该节点间绑定关系的时长。
比如,你可能希望在出现网络分裂事件时,对于一个与节点本地状态有着深度绑定的应用而言, 仍然停留在当前节点上运行一段较长的时间,以等待网络恢复以避免被驱逐。 你为这种 Pod 所设置的容忍度看起来可能是这样:
|
|
说明:
Kubernetes 会自动给 Pod 添加针对 node.kubernetes.io/not-ready
和 node.kubernetes.io/unreachable
的容忍度,且配置 tolerationSeconds=300
, 除非用户自身或者某控制器显式设置此容忍度。
这些自动添加的容忍度意味着 Pod 可以在检测到对应的问题之一时,在 5 分钟内保持绑定在该节点上。
DaemonSet 中的 Pod 被创建时, 针对以下污点自动添加的 NoExecute
的容忍度将不会指定 tolerationSeconds
:
node.kubernetes.io/unreachable
node.kubernetes.io/not-ready
这保证了出现上述问题时 DaemonSet 中的 Pod 永远不会被驱逐。
基于节点状态添加污点
控制平面使用节点控制器自动创建 与节点状况 对应的、效果为 NoSchedule
的污点。
调度器在进行调度时检查污点,而不是检查节点状况。这确保节点状况不会直接影响调度。 例如,如果 DiskPressure
节点状况处于活跃状态,则控制平面添加 node.kubernetes.io/disk-pressure
污点并且不会调度新的 Pod 到受影响的节点。 如果 MemoryPressure
节点状况处于活跃状态,则控制平面添加 node.kubernetes.io/memory-pressure
污点。
对于新创建的 Pod,可以通过添加相应的 Pod 容忍度来忽略节点状况。 控制平面还在具有除 BestEffort
之外的 QoS 类的 Pod 上添加 node.kubernetes.io/memory-pressure
容忍度。 这是因为 Kubernetes 将 Guaranteed
或 Burstable
QoS 类中的 Pod(甚至没有设置内存请求的 Pod) 视为能够应对内存压力,而新创建的 BestEffort
Pod 不会被调度到受影响的节点上。
DaemonSet 控制器自动为所有守护进程添加如下 NoSchedule
容忍度以防 DaemonSet 崩溃:
node.kubernetes.io/memory-pressure
node.kubernetes.io/disk-pressure
node.kubernetes.io/pid-pressure
(1.14 或更高版本)node.kubernetes.io/unschedulable
(1.10 或更高版本)node.kubernetes.io/network-unavailable
(只适合主机网络配置)
添加上述容忍度确保了向后兼容,你也可以选择自由向 DaemonSet 添加容忍度。
将 Pod 指派给节点
你可以约束一个 Pod 只能在特定的节点上运行。 有几种方法可以实现这点,推荐的方法都是用 标签选择算符来进行选择。 通常这样的约束不是必须的,因为调度器将自动进行合理的放置(比如,将 Pod 分散到节点上, 而不是将 Pod 放置在可用资源不足的节点上等等)。但在某些情况下,你可能需要进一步控制 Pod 被部署到的节点。例如,确保 Pod 最终落在连接了 SSD 的机器上, 或者将来自两个不同的服务且有大量通信的 Pods 被放置在同一个可用区。
你可以使用下列方法中的任何一种来选择 Kubernetes 对特定 Pod 的调度:
- 与节点标签匹配的 nodeSelector
- 亲和性与反亲和性
- nodeName 字段
nodeSelector
nodeSelector
是节点选择约束的最简单推荐形式。你可以将 nodeSelector
字段添加到 Pod 的规约中设置你希望目标节点所具有的节点标签。 Kubernetes 只会将 Pod 调度到拥有你所指定的每个标签的节点上。
亲和性与反亲和性
nodeSelector
提供了一种最简单的方法来将 Pod 约束到具有特定标签的节点上。 亲和性和反亲和性扩展了你可以定义的约束类型。使用亲和性与反亲和性的一些好处有:
- 亲和性、反亲和性语言的表达能力更强。
nodeSelector
只能选择拥有所有指定标签的节点。 亲和性、反亲和性为你提供对选择逻辑的更强控制能力。 - 你可以标明某规则是“软需求”或者“偏好”,这样调度器在无法找到匹配节点时仍然调度该 Pod。
- 你可以使用节点上(或其他拓扑域中)运行的其他 Pod 的标签来实施调度约束, 而不是只能使用节点本身的标签。这个能力让你能够定义规则允许哪些 Pod 可以被放置在一起。
亲和性功能由两种类型的亲和性组成:
- 节点亲和性功能类似于
nodeSelector
字段,但它的表达能力更强,并且允许你指定软规则。 - Pod 间亲和性/反亲和性允许你根据其他 Pod 的标签来约束 Pod。
节点亲和性
节点亲和性概念上类似于 nodeSelector
, 它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点上。 节点亲和性有两种:
requiredDuringSchedulingIgnoredDuringExecution
: 调度器只有在规则被满足的时候才能执行调度。此功能类似于nodeSelector
, 但其语法表达能力更强。preferredDuringSchedulingIgnoredDuringExecution
: 调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该 Pod。
说明:
在上述类型中,IgnoredDuringExecution
意味着如果节点标签在 Kubernetes 调度 Pod 时发生了变更,Pod 仍将继续运行。
你可以使用 Pod 规约中的 .spec.affinity.nodeAffinity
字段来设置节点亲和性。 例如,考虑下面的 Pod 规约:
pods/pod-with-node-affinity.yaml
|
|
说明:
如果你希望 Kubernetes 能够成功地调度此例中的 Pod,你必须拥有打了 kubernetes.io/os=linux
标签的节点。
|
|
在这一示例中,所应用的规则如下:
- 节点必须包含一个键名为
topology.kubernetes.io/zone
的标签, 并且该标签的取值必须为antarctica-east1
或antarctica-west1
。 - 节点最好具有一个键名为
another-node-label-key
且取值为another-node-label-value
的标签。
你可以使用 operator
字段来为 Kubernetes 设置在解释规则时要使用的逻辑操作符。 你可以使用 In
、NotIn
、Exists
、DoesNotExist
、Gt
和 Lt
之一作为操作符。
NotIn
和 DoesNotExist
可用来实现节点反亲和性行为。 你也可以使用节点污点 将 Pod 从特定节点上驱逐。
说明:
如果你同时指定了 nodeSelector
和 nodeAffinity
,两者 必须都要满足, 才能将 Pod 调度到候选节点上。
如果你指定了多个与 nodeAffinity
类型关联的 nodeSelectorTerms
, 只要其中一个 nodeSelectorTerms
满足的话,Pod 就可以被调度到节点上。
如果你指定了多个与同一 nodeSelectorTerms
关联的 matchExpressions
, 则只有当所有 matchExpressions
都满足时 Pod 才可以被调度到节点上。
参阅使用节点亲和性来为 Pod 指派节点, 以了解进一步的信息。
节点亲和性权重
你可以为 preferredDuringSchedulingIgnoredDuringExecution
亲和性类型的每个实例设置 weight
字段,其取值范围是 1 到 100。 当调度器找到能够满足 Pod 的其他调度请求的节点时,调度器会遍历节点满足的所有的偏好性规则, 并将对应表达式的 weight
值加和。
最终的加和值会添加到该节点的其他优先级函数的评分之上。 在调度器为 Pod 作出调度决定时,总分最高的节点的优先级也最高。
例如,考虑下面的 Pod 规约:
pods/pod-with-affinity-anti-affinity.yaml
|
|
如果存在两个候选节点,都满足 requiredDuringSchedulingIgnoredDuringExecution
规则, 其中一个节点具有标签 label-1:key-1
,另一个节点具有标签 label-2:key-2
, 调度器会考察各个节点的 weight
取值,并将该权重值添加到节点的其他得分值之上。
注意:此策略需要考虑调度器的优选策略实现方式。
Pod 间亲和性与反亲和性
Pod 间亲和性与反亲和性使你可以基于已经在节点上运行的 Pod 的标签来约束 Pod 可以调度到的节点,而不是基于节点上的标签。
Pod 间亲和性与反亲和性的规则格式为“如果 X 上已经运行了一个或多个满足规则 Y 的 Pod, 则这个 Pod 应该(或者在反亲和性的情况下不应该)运行在 X 上”。 这里的 X 可以是节点、机架、云提供商可用区或地理区域或类似的拓扑域, Y 则是 Kubernetes 尝试满足的规则。
你通过标签选择算符 的形式来表达规则(Y),并可根据需要指定选关联的名字空间列表。 Pod 在 Kubernetes 中是名字空间作用域的对象,因此 Pod 的标签也隐式地具有名字空间属性。 针对 Pod 标签的所有标签选择算符都要指定名字空间,Kubernetes 会在指定的名字空间内寻找标签。
你会通过 topologyKey
来表达拓扑域(X)的概念,其取值是系统用来标示域的节点标签键。 相关示例可参见常用标签、注解和污点。
说明:
Pod 间亲和性和反亲和性都需要相当的计算量,因此会在大规模集群中显著降低调度速度。 我们不建议在包含数百个节点的集群中使用这类设置。
说明:
Pod 反亲和性需要节点上存在一致性的标签。换言之, 集群中每个节点都必须拥有与
topologyKey
匹配的标签。 如果某些或者所有节点上不存在所指定的topologyKey
标签,调度行为可能与预期的不同。
注意:在实际使用和测试中,特别是大规模集群场景下,这个pod亲和性特性确实会影响调度性能。所以,pod亲和性不做重点来使用
Pod 间亲和性与反亲和性的类型
与节点亲和性类似,Pod 的亲和性与反亲和性也有两种类型:
requiredDuringSchedulingIgnoredDuringExecution
preferredDuringSchedulingIgnoredDuringExecution
例如,你可以使用 requiredDuringSchedulingIgnoredDuringExecution
亲和性来告诉调度器, 将两个服务的 Pod 放到同一个云提供商可用区内,因为它们彼此之间通信非常频繁。 类似地,你可以使用 preferredDuringSchedulingIgnoredDuringExecution
反亲和性来将同一服务的多个 Pod 分布到多个云提供商可用区中。
要使用 Pod 间亲和性,可以使用 Pod 规约中的 .affinity.podAffinity
字段。 对于 Pod 间反亲和性,可以使用 Pod 规约中的 .affinity.podAntiAffinity
字段。
Pod 亲和性示例
考虑下面的 Pod 规约:
pods/pod-with-pod-affinity.yaml
|
|
本示例定义了一条 Pod 亲和性规则和一条 Pod 反亲和性规则。Pod 亲和性规则配置为 requiredDuringSchedulingIgnoredDuringExecution
,而 Pod 反亲和性配置为 preferredDuringSchedulingIgnoredDuringExecution
。
亲和性规则表示,仅当节点和至少一个已运行且有 security=S1
的标签的 Pod 处于同一区域时,才可以将该 Pod 调度到节点上。 更确切的说,调度器必须将 Pod 调度到具有 topology.kubernetes.io/zone=V
标签的节点上,并且集群中至少有一个位于该可用区的节点上运行着带有 security=S1
标签的 Pod。
反亲和性规则表示,如果节点处于 Pod 所在的同一可用区且至少一个 Pod 具有 security=S2
标签,则该 Pod 不应被调度到该节点上。 更确切地说, 如果同一可用区中存在其他运行着带有 security=S2
标签的 Pod 节点, 并且节点具有标签 topology.kubernetes.io/zone=R
,Pod 不能被调度到该节点上。
查阅设计文档 以进一步熟悉 Pod 亲和性与反亲和性的示例。
你可以针对 Pod 间亲和性与反亲和性为其 operator
字段使用 In
、NotIn
、Exists
、 DoesNotExist
等值。
原则上,topologyKey
可以是任何合法的标签键。出于性能和安全原因,topologyKey
有一些限制:
- 对于 Pod 亲和性而言,在
requiredDuringSchedulingIgnoredDuringExecution
和preferredDuringSchedulingIgnoredDuringExecution
中,topologyKey
不允许为空。 - 对于
requiredDuringSchedulingIgnoredDuringExecution
要求的 Pod 反亲和性, 准入控制器LimitPodHardAntiAffinityTopology
要求topologyKey
只能是kubernetes.io/hostname
。如果你希望使用其他定制拓扑逻辑, 你可以更改准入控制器或者禁用之。
除了 labelSelector
和 topologyKey
,你也可以指定 labelSelector
要匹配的命名空间列表,方法是在 labelSelector
和 topologyKey
所在层同一层次上设置 namespaces
。 如果 namespaces
被忽略或者为空,则默认为 Pod 亲和性/反亲和性的定义所在的命名空间。
用节点亲和性把 Pods 分配到节点
给节点添加标签
列出集群中的节点及其标签:
1
kubectl get nodes --show-labels
1 2 3 4 5 6
输出类似于此: NAME STATUS ROLES AGE VERSION LABELS worker0 Ready <none> 1d v1.13.0 ...,kubernetes.io/hostname=worker0 worker1 Ready <none> 1d v1.13.0 ...,kubernetes.io/hostname=worker1 worker2 Ready <none> 1d v1.13.0 ...,kubernetes.io/hostname=worker2
- 选择一个节点,给它添加一个标签:
1
kubectl label nodes <your-node-name> disktype=ssd
其中
<your-node-name>
是你所选节点的名称。验证你所选节点具有
disktype=ssd
标签:1
kubectl get nodes --show-labels
1 2 3 4 5 6
输出类似于此: NAME STATUS ROLES AGE VERSION LABELS worker0 Ready <none> 1d v1.13.0 ...,disktype=ssd,kubernetes.io/hostname=worker0 worker1 Ready <none> 1d v1.13.0 ...,kubernetes.io/hostname=worker1 worker2 Ready <none> 1d v1.13.0 ...,kubernetes.io/hostname=worker2
1
在前面的输出中,可以看到 `worker0` 节点有一个 `disktype=ssd` 标签。
依据强制的节点亲和性调度 Pod
下面清单描述了一个 Pod,它有一个节点亲和性配置 requiredDuringSchedulingIgnoredDuringExecution
,disktype=ssd
。 这意味着 pod 只会被调度到具有 disktype=ssd
标签的节点上。
pods/pod-nginx-required-affinity.yaml
|
|
执行(Apply)此清单来创建一个调度到所选节点上的 Pod:
1
kubectl apply -f https://k8s.io/examples/pods/pod-nginx-required-affinity.yaml
- 验证 pod 已经在所选节点上运行:
1
kubectl get pods --output=wide
1 2 3 4
输出类似于此: NAME READY STATUS RESTARTS AGE IP NODE nginx 1/1 Running 0 13s 10.200.0.4 worker0
使用首选的节点亲和性调度 Pod
本清单描述了一个Pod,它有一个节点亲和性设置 preferredDuringSchedulingIgnoredDuringExecution
,disktype: ssd
。 这意味着 pod 将首选具有 disktype=ssd
标签的节点。
pods/pod-nginx-preferred-affinity.yaml
|
|
执行此清单创建一个会调度到所选节点上的 Pod:
1
kubectl apply -f https://k8s.io/examples/pods/pod-nginx-preferred-affinity.yaml
验证 pod 是否在所选节点上运行:
1
kubectl get pods --output=wide
1 2 3 4
输出类似于此: NAME READY STATUS RESTARTS AGE IP NODE nginx 1/1 Running 0 13s 10.200.0.4 worker0
示例
nodeAffinity测试 affinitypod.yaml
|
|