污点和容忍度

节点亲和性是 Pod 的一种属性,它使 Pod 被吸引到一类特定的节点(这可能处于一种偏好,也可能是硬性要求)。

污点(Taint)则相反,它使节点能够排斥一类特定的 Pod。

容忍度(Toleration)是应用于 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-

我们可以在 Pod 规约中为 Pod 设置容忍度,下面两个容忍度均与上面例子中使用 kubectl taint 命令创建的污点先匹配,因此如果一个 Pod 拥有其中任何一个容忍度,都能够被调度到 node1:

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
tolerations:
- key: "key1"
  operator: "Exists"
  effect: "NoSchedule"

默认的 Kubernetes 调度器在选择一个节点来运行特定的 Pod 是会考虑污点和容忍度。然而,如果我们手动为一个 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,则它们的值应该相等。

上述例子中 effect 使用的值为 NoSchedule,你也可以使用另外一个值 PreferNoSchedule。

effect 字段的允许值包括:

  • NoExecute:这会影响在节点上运行的 Pod,具体影响如下--如果 Pod 不能容忍这类污点,会马上被驱逐。如果 Pod 能够容忍这类污点,但是在容忍度定义中没有指定 tolerationSeconds,则 Pod 还会一直在这个节点上运行。如果 Pod 能够容忍这类污点,而且指定了tolerationSeconds,则 Pod 还能在这个节点上继续运行这个指定的时间长度。这段时间过去后,节点生命周期控制器从这个驱逐这些 Pod。
  • NoSchedule:除非具有匹配的容忍度规约,否则新的 Pod 不会被调度到带有污点的节点上。当前正在节点上运行的 Pod 不会被驱逐。
  • PreferNoScheduler:它是"偏好"或"软性"的 NoScheduler。控制平面将尝试避免将不能容忍污点的 Pod 调度到节点上,但不能报证完全避免。

我们可以给一个节点添加多个污点,也可以给一个 Pod 添加多个容忍度设置。Kubernetes 处理多个污点和容忍度的过程就像一个过滤器:从一个节点的所有污点开始遍历,过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了 Pod 是否会被分配到该节点。需要注意以下情况:

  • 如果未被忽略的污点中存在至少一个 effect 值为 NoSchedule 的污点,则 Kubernetes 不会将 Pod 调度到该节点
  • 如果未被忽略的污点中不存在 effect 值为 NoSchedule 的污点,但是存在至少一个 effect 值为 PreferNoSchedule 的污点,则 Kubernetes 会尝试不将 Pod 调度到该节点。
  • 如果未被忽略的污点中存在至少一个 effect 值为 NoExecute 的污点,则 Kubernetes 不会将 Pod 调度到该节点(如果 Pod 还未在节点上运行),并且会将 Pod 从该节点驱逐(如果 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 中配置的在节点出现问题时的驱逐行为。

基于污点的驱逐

但某种条件为真时,节点控制器会自动给节点添加一个污点。当前内置的污点包括:

  • 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 效果的相关污点。此效果被默认添加到 node.kubernetes.io/not-ready 和 node.kubernetes.io/unreachable 污点中。如果异常状态恢复正常,kubelet 或节点控制器能够移除相关的污点。

在某些情况下,当节点不可达时,API 服务器无法与节点上的 kubelet 进行通信。在与 API 服务器通信被重新建立之前,删除 Pod 的决定无法传递到 kubelet。在此期间,那些被标记为要删除的 Pod 可能会继续在网络隔离的节点上运行。

我们可以为 Pod 设置 tolerationSeconds,以指定当节点失效或者不响应时,Pod 维系与该节点间绑定关系的时长。

比如,我们希望在出现网络隔离事件时,对于一个与节点本地状态有着深度绑定的应用而言,仍然停留在当前节点上运行一段较长的时间,以等待网络恢复以避免被驱逐。我们为这种 Pod 所设置的容忍度是这样的:

tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000

DaemonSet 中的 Pod 被创建时,针对以下污点自动添加的 NoExecute 的容忍度将不会指定 tolerationSeconds:

  • node.kubernetes.io/unreable
  • node.kubernetes.io/not-ready

这保证了出现上述问题时 DaemonSet 中的 Pod 永远不会被驱逐

基于节点状态添加污点

控制平面通过节点控制器,会针对特定的节点状况自动添加具有 NoSchedule 效果的污点。

调度器在进行调度决策时,只检查污点而非直接关注节点状态。这一机制确保了节点状况不会直接影响调度流程。例如:当 DiskPressure(磁盘压力)节点状况激活时,控制平面会添加 node.kubernetes.io/disk-pressure 污点,从而阻止新的 Pod 被调度到受影响的节点上。当MemoryPressure(内存压力)节点状况激活时,控制平面会添加 node.kubernetes.io/memory-pressure 污点。

我们可以通过为新建的 Pod 添加相应的容忍度来忽略这些节点的状况。此外,控制平面会自动会 Qos 等级非 BestEffort 的 Pod 添加 node.kubernetes.io/memory-pressure 容忍度。这是因为 Kubernetes 将 Guaranteed 或 Burstable Qos 类别的 Pod(即使未设置内存请求)视为能够应对内存压力,而新的 BestEffort Pod 则不会被调度到受影响的节点上。

DaemonSet 控制器自动为所有守护进程添加如下 NoSchedule 容忍度,以防 DaemonSet 崩溃:

  • node.kubernetes.io/memory-pressure
  • node.kubernetes.io/disk-pressure
  • node.kubernetes.io/pid-pressure
  • node.kubernetes.io/unschedulable
  • node.kubernetes.io/network-unavailable(只适合主机网络)

设备污点与容忍度

在使用动态资源分配管理特殊硬件的集群中, 管理员可以选择为单个设备设置污点, 而不是为整个节点打污点。这样做的好处是,污点可以精确地作用于出现故障或需要维护的硬件。 同时也支持容忍度配置,并且可以在请求设备时指定。 与污点类似,容忍度会应用于共享同一分配设备的所有 Pod。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐