污点和容忍
节点亲和性(Node affinity)是 Pod 的一个属性,它会**吸引** Pod 到一组 节点(作为首选项或硬性要求)。**污点(Taints)** 则恰恰相反——它们允许节点排斥一组 Pod。
**容忍度(Tolerations)** 应用于 Pod。容忍度允许调度器调度带有匹配污点的 Pod。容忍度允许调度但不保证调度:调度器还会**评估其他参数**作为其功能的一部分。
污点和容忍度协同工作,以确保 Pod 不被调度到不合适的节点上。一个或多个污点应用于节点;这标志着节点不应该接受任何不容忍这些污点的 Pod。
概念
你可以使用 kubectl taint 向节点添加污点。例如,
kubectl taint nodes node1 key1=value1:NoSchedule
在节点 `node1` 上设置了一个污点。该污点拥有键 `key1`、值 `value1`,以及污点效果 `NoSchedule`。这意味着没有 Pod 能够调度到 `node1` 上,除非它具有匹配的容忍度。
要移除上面命令添加的污点,你可以运行
kubectl taint nodes node1 key1=value1:NoSchedule-
你可以在 PodSpec 中为 Pod 指定容忍度。以下两种容忍度都“匹配”上面 `kubectl taint` 命令创建的污点,因此具有其中任何一种容忍度的 Pod 都能够调度到 `node1` 上
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
tolerations:
- key: "key1"
operator: "Exists"
effect: "NoSchedule"
当选择一个节点来运行特定的 Pod 时,默认的 Kubernetes 调度器会考虑污点和容忍度。但是,如果你手动为 Pod 指定了 `.spec.nodeName`,该操作会绕过调度器;然后 Pod 将绑定到你分配的节点上,即使该节点上存在你选择的 `NoSchedule` 污点。如果发生这种情况,并且节点也设置了 `NoExecute` 污点,kubelet 将会驱逐该 Pod,除非设置了适当的容忍度。
这是一个定义了一些容忍度的 Pod 示例:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
tolerations:
- key: "example-key"
operator: "Exists"
effect: "NoSchedule"
`operator` 的默认值为 `Equal`。
如果键相同且效果相同,则容忍度“匹配”污点,并且
- `operator` 为 `Exists` (在这种情况下不应指定 `value`),或者
- `operator` 为 `Equal` 且值应相等。
注意
有两种特殊情况:
如果 `key` 为空,则 `operator` 必须为 `Exists`,它会匹配所有键和值。请注意,`effect` 仍然需要同时匹配。
空的 `effect` 匹配所有带有键 `key1` 的效果。
上面的例子使用了 `NoSchedule` 的 `effect`。另外,你也可以使用 `PreferNoSchedule` 的 `effect`。
`effect` 字段允许的值为
NoExecute
- 这会影响已在节点上运行的 Pod,具体如下:
- 不容忍该污点的 Pod 会立即被驱逐。
- 在容忍度规约中未指定 `tolerationSeconds` 但容忍该污点的 Pod 会永远绑定在节点上。
- 容忍该污点并指定了 `tolerationSeconds` 的 Pod 会在指定的时间内保持绑定。该时间结束后,节点生命周期控制器会将 Pod 从节点中驱逐。
NoSchedule
- 除非有匹配的容忍度,否则新的 Pod 将不会被调度到被污染的节点上。当前正在节点上运行的 Pod **不会**被驱逐。
PreferNoSchedule
- `PreferNoSchedule` 是 `NoSchedule` 的“偏好”或“软性”版本。控制平面**会尽量**避免将不容忍污点的 Pod 放置到节点上,但这不作保证。
你可以在同一个节点上设置多个污点,在同一个 Pod 上设置多个容忍度。Kubernetes 处理多个污点和容忍度的方式就像一个过滤器:从节点的所有污点开始,然后忽略 Pod 具有匹配容忍度的那些污点;剩余的未被忽略的污点会对 Pod 产生指示的效果。具体来说:
- 如果至少有一个未被忽略的污点具有 `NoSchedule` 效果,那么 Kubernetes 将不会将 Pod 调度到该节点。
- 如果没有未被忽略的污点具有 `NoSchedule` 效果,但至少有一个未被忽略的污点具有 `PreferNoSchedule` 效果,那么 Kubernetes 将**尝试**不将 Pod 调度到该节点上。
- 如果至少有一个未被忽略的污点具有 `NoExecute` 效果,那么 Pod 将从节点中被驱逐(如果它已经在节点上运行),并且不会被调度到该节点上(如果它尚未在节点上运行)。
例如,假设你像这样污染一个节点
kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 key2=value2:NoSchedule
一个 Pod 有两个容忍度
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
在这种情况下,Pod 将无法调度到节点上,因为没有容忍度能匹配第三个污点。但如果它在污点被添加时已经在节点上运行,它将能够继续运行,因为第三个污点是三个污点中唯一一个 Pod 不容忍的污点。
通常,如果给节点添加了 `NoExecute` 效果的污点,那么任何不容忍该污点的 Pod 都将立即被驱逐,而容忍该污点的 Pod 则永远不会被驱逐。然而,具有 `NoExecute` 效果的容忍度可以指定一个可选的 `tolerationSeconds` 字段,该字段规定了在污点添加后 Pod 将绑定到节点上的时间长度。例如,
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
tolerationSeconds: 3600
意味着如果此 Pod 正在运行,并且向节点添加了一个匹配的污点,则该 Pod 将在节点上绑定 3600 秒,然后被驱逐。如果在该时间之前污点被移除,则 Pod 将不会被驱逐。
使用场景示例
污点和容忍度是一种灵活的方式,可以将 Pod **引导离开**节点,或驱逐不应运行的 Pod。一些用例包括:
**专用节点**:如果你想将一组节点专门供特定用户独占使用,你可以向这些节点添加污点(例如,`kubectl taint nodes nodename dedicated=groupName:NoSchedule`),然后向他们的 Pod 添加相应的容忍度(通过编写自定义准入控制器来完成最简单)。具有容忍度的 Pod 将被允许使用被污染的(专用)节点以及集群中的任何其他节点。如果你想将节点专门供他们使用**并**确保他们**只**使用专用节点,那么你应该另外向同一组节点添加一个类似于污点的标签(例如 `dedicated=groupName`),并且准入控制器应该另外添加一个节点亲和性,要求 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]
当某些条件为真时,节点控制器会自动给节点打污点。以下污点是内置的:
- `node.kubernetes.io/not-ready`:节点未就绪。这对应于 NodeCondition `Ready` 的状态为“`False`”。
- `node.kubernetes.io/unreachable`:节点无法从节点控制器访问。这对应于 NodeCondition `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 启动时使用了“外部”云提供商,此污点会设置在节点上以将其标记为不可用。当云控制器管理器中的控制器初始化此节点后,kubelet 会移除此污点。
在某些情况下,当节点无法访问时,API 服务器无法与节点上的 kubelet 通信。在与 API 服务器重新建立通信之前,无法将删除 Pod 的决定传达给 kubelet。在此期间,计划删除的 Pod 可能会继续在分区节点上运行。
在某些情况下,当节点无法访问时,API 服务器无法与节点上的 kubelet 通信。在与 API 服务器重新建立通信之前,无法将删除 Pod 的决定传达给 kubelet。在此期间,计划删除的 Pod 可能会继续在分区节点上运行。
注意
控制平面限制了向节点添加新污点的速率。此速率限制管理着当许多节点同时变得无法访问(例如:发生网络中断)时触发的驱逐数量。你可以为 Pod 指定 `tolerationSeconds`,以定义该 Pod 在发生故障或无响应的节点上停留多长时间。
例如,你可能希望在网络分区的情况下,让具有大量本地状态的应用程序长时间绑定到节点,希望分区能够恢复,从而避免 Pod 被驱逐。你为该 Pod 设置的容忍度可能看起来像:
tolerations:
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 6000
注意
Kubernetes 会自动为 `node.kubernetes.io/not-ready` 和 `node.kubernetes.io/unreachable` 添加 `tolerationSeconds=300` 的容忍度,除非你或控制器明确设置了这些容忍度。
这些自动添加的容忍度意味着 Pod 在检测到这些问题后,将在节点上保持绑定 5 分钟。
DaemonSet Pod 会自动带有以下针对污点(不含 `tolerationSeconds`)的 `NoExecute` 容忍度:
node.kubernetes.io/unreachable
node.kubernetes.io/not-ready
这确保了 DaemonSet Pod 永远不会因这些问题而被驱逐。
注意
以前,节点控制器负责向节点添加污点和驱逐 Pod。但在 1.29 版本之后,基于污点的驱逐实现已从节点控制器中移出,成为一个独立的组件,称为 taint-eviction-controller。用户可以选择通过在 kube-controller-manager 中设置 `--controllers=-taint-eviction-controller` 来禁用基于污点的驱逐。根据条件污点节点
控制平面使用节点 控制器,自动为 节点条件 创建具有 `NoSchedule` 效果的污点。
调度器在做出调度决策时会检查污点,而不是节点条件。这确保了节点条件不会直接影响调度。例如,如果 `DiskPressure` 节点条件处于活动状态,控制平面会添加 `node.kubernetes.io/disk-pressure` 污点,并且不会将新的 Pod 调度到受影响的节点上。如果 `MemoryPressure` 节点条件处于活动状态,控制平面会添加 `node.kubernetes.io/memory-pressure` 污点。
你可以通过添加相应的 Pod 容忍度来忽略新创建的 Pod 的节点条件。控制平面还会为 QoS 类 不是 `BestEffort` 的 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。