跳至正文
Kubernetes — Kubernetes 调度:Taint、Toleration、Affinity 与 Eviction

Kubernetes 调度:Taint、Toleration、Affinity 与 Eviction

说明:本文由 Codex 根据作者提供的主题、素材与公开资料辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。

术语修订(2026-09-07,Agent:Codex):保留 Taint、Toleration 与 Affinity 等官方术语,并将调度与运行期关系改为 Mermaid 示意图。此次运行记录:模型 gpt-6-astra,reasoning effort ultra,执行入口 Codex Desktop,提供方 openai,CLI 版本 0.153.4(不代表桌面 App 版本)。

先建立一张心智地图

Kubernetes 调度与节点管理,表面上有很多名词,实际上是在回答四个问题:

  1. Pod 能不能放到这个节点?
  2. Pod 更应该放到哪个节点?
  3. 节点发生维护或异常时,已经运行的 Pod 如何离开
  4. 应用怎样在迁移、故障和资源压力下保持可用?
机制作用方向解决的问题是否会影响已运行 Pod
Label / nodeSelectorPod 选择 Node只运行在带有指定标签的节点通常不会
Node AffinityPod 选择 Node用更灵活的硬、软规则选择节点IgnoredDuringExecution 通常不会
Pod Affinity / Anti-AffinityPod 与 Pod 的关系靠近依赖、避免副本共处仅在调度新 Pod 时生效
TaintNode 排斥 Pod保护专用节点,拒绝不合适的 PodNoExecute 会影响
TolerationPod 容忍 Taint允许 Pod 进入设置了匹配 Taint 的节点可配合 NoExecute 延迟驱逐
Eviction系统或管理员终止 Pod节点维护、资源压力、节点故障会终止 Pod;控制器按需补建

Tolerations permit scheduling onto nodes with matching taints, but they do not guarantee node selection. Taints and Tolerations

可以这样理解各自的作用:

text
Affinity 是“我想去哪里”;
Taint 是“谁不该来”;
Toleration 是“我可以进去”;
驱逐是“已经在这里的 Pod 何时必须离开”。

三个常见误区也可以先排除:

  • Toleration 不等于选择。Pod 容忍 GPU 节点,并不代表它一定会被调度到 GPU 节点。
  • NoSchedule 不等于驱逐。它只阻止新的不匹配 Pod 调度进来。
  • PDB 不是绝对保护。它主要约束自愿性中断,不能阻止节点压力驱逐。

调度与运行时的边界

下面是一个便于理解的逻辑流程,不代表 kube-scheduler 内部插件的精确执行顺序:

调度阶段:

flowchart TB
    A[未绑定的 Pod] --> B[过滤候选节点]
    B --> C{有候选节点?}
    C -->|有| D[打分<br/>绑定]
    C -->|无| E[Pending<br/>等待重试]

运行期:

flowchart TB
    A[运行中的 Pod] --> B{需要终止?}
    B -->|否| C[继续运行]
    B -->|是| D[终止 Pod]
    D --> E[有控制器时<br/>按需补建]

过滤阶段检查资源、nodeSelector、required Node Affinity,以及未被容忍的 NoSchedule / NoExecute Taint;打分阶段考虑 preferred Affinity、PreferNoSchedule 等因素。运行期的节点维护、NoExecute 与资源压力则走各自的处理路径。

因此,调度规则大多决定“新 Pod 去哪里”,而驱逐规则处理“运行中的 Pod 是否还能留在原节点”。这两个阶段不要混为一谈。

Taint 与 Toleration:节点隔离的反向筛选

Taint 定义在 Node 上,格式为:

text
<key>=<value>:<effect>

例如,下面的命令把 gpu-node-01 标记为 GPU 专用节点:

bash
kubectl label node gpu-node-01 workload=gpu
kubectl taint node gpu-node-01 dedicated=gpu:NoSchedule

此后,没有匹配的 Toleration 的 Pod 不能新调度到该节点。查看与移除 Taint:

bash
kubectl describe node gpu-node-01
kubectl get node gpu-node-01 -o jsonpath='{.spec.taints}'
kubectl taint node gpu-node-01 dedicated=gpu:NoSchedule-

三种 effect

effect对新 Pod 的影响对已运行 Pod 的影响适用场景
NoSchedule没有匹配的 Toleration 则不能调度不驱逐专用节点、GPU 节点、维护前封锁
PreferNoSchedule调度器尽量避开,但不保证不驱逐软隔离、临时倾向
NoExecute没有匹配的 Toleration 则不能调度驱逐不匹配 Pod节点异常、强制隔离

同一 Node 可以有多个 Taint。Kubernetes 会先从全部 Taint 中移除被 Pod 容忍的部分,再根据剩余 Taint 处理:

  • 剩余任意一个 NoSchedule,Pod 不能调度到该节点。
  • 没有 NoSchedule,但有 PreferNoSchedule,调度器会尽量避开该节点。
  • 剩余任意一个 NoExecute,新 Pod 不能调度;已有 Pod 会被驱逐。

Toleration 字段与匹配规则

Toleration 写在 Pod 的 .spec.tolerations 中:

yaml
tolerations:
  - key: "dedicated"
    operator: "Equal"
    value: "gpu"
    effect: "NoSchedule"
字段含义
key要匹配的 Taint 键
operatorEqualExists;省略时默认 Equal
valueEqual 使用,必须与 Taint 值一致
effect要匹配的效果;为空时匹配同一个 key 的所有 effect
tolerationSeconds仅用于 NoExecute,声明可继续留在节点上的秒数

Equal 是精确匹配:

yaml
tolerations:
  - key: "dedicated"
    operator: "Equal"
    value: "gpu"
    effect: "NoSchedule"

Exists 只关心 key 和 effect,不写 value

yaml
tolerations:
  - key: "dedicated"
    operator: "Exists"
    effect: "NoSchedule"

如果 key 为空,operator 必须是 Exists,它会匹配所有 key 和 value。这个配置范围很大,例如:

yaml
tolerations:
  - operator: "Exists"
    effect: "NoSchedule"

它会容忍任意 NoSchedule Taint。业务 Pod 一般不应这样写,否则专用节点隔离会失效。

NoExecutetolerationSeconds

NoExecute 是唯一会直接影响已有 Pod 的 effect。下面的配置表示:如果节点出现匹配 Taint,Pod 最多可继续运行 300 秒;Taint 在这段时间内被移除,则不会被驱逐。

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

没有 tolerationSeconds 时,匹配的 Pod 会一直容忍该 NoExecute Taint;没有匹配的 Toleration 时,Pod 会被立即驱逐。因此,对 NoExecute 的容忍要明确业务对短暂故障和快速故障切换的取舍。

推荐组合:专用节点池

生产环境中,单独使用 Toleration 通常不够。以下组合能同时表达“普通 Pod 不能来”和“目标 Pod 必须来”:

  1. Node Label:描述节点能力,例如 workload=gpu
  2. Taint:保护该节点,例如 dedicated=gpu:NoSchedule
  3. Toleration:允许目标 Pod 进入。
  4. Node Affinity:要求目标 Pod 主动选择这一类节点。

先给节点打标:

bash
kubectl label node gpu-node-01 workload=gpu
kubectl taint node gpu-node-01 dedicated=gpu:NoSchedule

再部署工作负载:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-worker
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gpu-worker
  template:
    metadata:
      labels:
        app: gpu-worker
    spec:
      tolerations:
        - key: "dedicated"
          operator: "Equal"
          value: "gpu"
          effect: "NoSchedule"
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: workload
                    operator: In
                    values:
                      - gpu
      containers:
        - name: worker
          image: registry.example.com/training-worker:1.0
          resources:
            requests:
              cpu: "1"
              memory: "2Gi"
              nvidia.com/gpu: 1
            limits:
              cpu: "2"
              memory: "4Gi"
              nvidia.com/gpu: 1

这四层的职责不同:

  • 没有 Toleration 的普通 Pod 会被 Taint 挡在 GPU 节点外。
  • 带 Toleration 但没有 Node Affinity 的 Pod 可以进入 GPU 节点,却不保证优先落在那里。
  • 同时带 Toleration 和 required Node Affinity 的 GPU 任务,才会只在 GPU 节点池中寻找位置。
  • GPU 资源请求仍是调度器判断该节点是否有可用 GPU 的依据。

DaemonSet 系统组件

CNI、kube-proxy、日志采集和节点监控等组件通常必须在每个节点运行。它们一般通过 DaemonSet 部署,并需要容忍控制平面、节点状态或维护相关的 Taint。

DaemonSet 控制器会为部分常见节点状态自动补充 Toleration;不同 Kubernetes 版本、控制器清单和节点角色的最终行为仍应以实际 Pod 规格为准。不要为了“跑遍所有节点”而给业务 Pod 添加宽泛的 Exists Toleration,也不要手工覆盖系统 DaemonSet 的 Toleration 策略却不验证其节点故障行为。

bash
# 查看 DaemonSet 的实际 Pod 分布和容忍配置
kubectl -n kube-system get daemonset
kubectl -n kube-system get pod -o wide
kubectl -n kube-system get pod <pod-name> -o yaml

Affinity:从“允许”到“选择”

Node Affinity

Node Affinity 根据 Node Label 选择节点,比 nodeSelector 更灵活。它主要分为两类:

字段语义没有匹配节点时
requiredDuringSchedulingIgnoredDuringExecution硬约束Pod 保持 Pending
preferredDuringSchedulingIgnoredDuringExecution软偏好仍可调度到其他可用节点

一个 Node Affinity 示例:必须在北京或上海可用区,并尽量选择 SSD 节点。

yaml
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
            - key: topology.kubernetes.io/zone
              operator: In
              values:
                - cn-beijing-a
                - cn-shanghai-a
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        preference:
          matchExpressions:
            - key: disk-type
              operator: In
              values:
                - ssd

逻辑关系值得注意:

  • 多个 nodeSelectorTerms 之间是 OR
  • 同一个 matchExpressions 列表中的条件之间是 AND
  • Node Affinity 可用 InNotInExistsDoesNotExistGtLt 只适用于 Node Affinity,且标签值必须是整数。

IgnoredDuringExecution 表示:Pod 已调度后,即使 Node Label 改变,Kubernetes 默认不会仅因这个原因把 Pod 驱逐。它不是持续迁移机制。

Pod Affinity 与 Pod Anti-Affinity

Pod Affinity 根据其他 Pod 的标签决定放置位置:

  • Pod Affinity:尽量与某类 Pod 靠近,例如应用与本地缓存放在同一可用区。
  • Pod Anti-Affinity:尽量或强制与同类 Pod 分开,避免一个节点故障带走多个副本。

下面的 Deployment 要求同一个应用的副本不能落在同一台主机上:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: api
              topologyKey: kubernetes.io/hostname
      containers:
        - name: api
          image: registry.example.com/api:1.0

该规则意味着 3 个副本至少需要 3 台带有 kubernetes.io/hostname 标签的可用节点;节点数不足时,剩余副本会 Pending。若业务更看重“尽量分散但不阻塞扩容”,应改为 preferredDuringSchedulingIgnoredDuringExecution

topologyKey 定义“分开或靠近”的范围:

topologyKey常见含义
kubernetes.io/hostname不同节点
topology.kubernetes.io/zone不同可用区,或同一可用区
topology.kubernetes.io/region不同区域,通常粒度较大

Pod Affinity 和 Pod Anti-Affinity 需要根据集群中已有 Pod 与 Node Label 计算,在大规模集群上可能增加调度成本。高可用副本分布场景也可以评估 Pod Topology Spread Constraints,它通常更直接地表达“跨节点或跨可用区均匀分散”。

驱逐:哪些情况会让 Pod 离开节点

“驱逐”不是单一机制。不同来源对 PDB、终止宽限期和处理方式的影响不同。

场景发起者PDB 是否生效关键特点
kubectl drain 的 API 驱逐管理员 / API Server通常生效适合计划维护,可能因 PDB 被阻塞
NoExecute Taint节点生命周期控制器或管理员不作为保护条件不匹配 Pod 被驱逐,可用 tolerationSeconds 缓冲
Node-pressure evictionkubelet不生效节点内存、磁盘、inode 或 PID 压力下自救
kubectl delete pod管理员不生效直接删除 Pod,不等同于 API 驱逐
抢占(preemption)scheduler不是日常维护手段为高优先级 Pod 腾出调度空间

计划维护:cordontaintdrain

维护节点时,典型顺序如下:

bash
# 1. 先确认节点上的工作负载与可用性预算
kubectl get pod -A --field-selector spec.nodeName=worker-01
kubectl get pdb -A

# 2. 停止接收新 Pod
kubectl cordon worker-01
kubectl taint node worker-01 maintenance=true:NoSchedule

# 3. 通过 Eviction API 迁移普通 Pod
kubectl drain worker-01 --ignore-daemonsets --delete-emptydir-data

# 4. 维护完成后恢复调度
kubectl uncordon worker-01
kubectl taint node worker-01 maintenance=true:NoSchedule-

这里的职责分别是:

  • cordon 把节点标记为不可调度,阻止新的常规调度。
  • taint ... NoSchedule 是额外的显式隔离,便于表达维护意图或防止特定调度路径误入。
  • drain 通过 Eviction API 逐个处理可驱逐 Pod,默认会遵守 PDB 和 terminationGracePeriodSeconds
  • --ignore-daemonsets 保留 DaemonSet Pod;--delete-emptydir-data 允许删除使用 emptyDir 的 Pod,其临时数据会丢失。

不要在常规维护中习惯性加 --force--disable-eviction。前者可能绕过对裸 Pod 的保护,后者会绕过 PDB;使用前应明确知道会终止哪些工作负载。

节点压力驱逐

当节点内存、磁盘空间、inode 或 PID 资源达到 kubelet 配置的驱逐阈值时,kubelet 会先尝试回收节点级资源,例如清理无用镜像和已终止容器;仍无法恢复时才驱逐 Pod。

节点压力驱逐与 kubectl drain 不同:

  • 不遵守 PodDisruptionBudget。
  • 不遵守 Pod 的 terminationGracePeriodSeconds
  • 硬驱逐阈值触发时,终止宽限期为 0s;软阈值可受 kubelet 的 eviction-max-pod-grace-period 限制。
  • kubelet 会综合 Pod 是否超出资源请求、Pod Priority 和相对资源使用量选择驱逐顺序。

所以,PDB 只能帮助计划内的迁移控制并发,不能替代资源请求、限制、容量规划和节点监控。

节点状态 Taint 与 NoExecute

节点不可达或状态异常时,控制平面可能根据 Node Condition 添加 Taint,例如 node.kubernetes.io/not-readynode.kubernetes.io/unreachablenode.kubernetes.io/memory-pressurenode.kubernetes.io/disk-pressurenode.kubernetes.io/pid-pressurenode.kubernetes.io/network-unavailablenode.kubernetes.io/unschedulable。实际 Taint 及 effect 应以集群中的 Node 对象为准:

bash
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
kubectl describe node worker-01

对于需要容忍短暂网络波动的工作负载,可显式配置两个常见的 NoExecute Toleration:

yaml
tolerations:
  - key: "node.kubernetes.io/not-ready"
    operator: "Exists"
    effect: "NoExecute"
    tolerationSeconds: 300
  - key: "node.kubernetes.io/unreachable"
    operator: "Exists"
    effect: "NoExecute"
    tolerationSeconds: 300

时间不宜机械照搬:设置得短,故障转移更快但可能因短抖动导致频繁迁移;设置得长,抖动更稳定但真正故障时恢复更慢。

排查清单

Pod 一直 Pending,优先检查调度事件:

bash
kubectl describe pod <pod-name> -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp
kubectl get nodes -L workload -L topology.kubernetes.io/zone
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

常见事件含义:

text
0/3 nodes are available: 1 node(s) had untolerated taint {dedicated: gpu}.

表示 Pod 缺少对应 Toleration,或 Toleration 中的 keyvalueeffect 没有完全匹配。

Pod 被驱逐或异常重建,检查:

bash
kubectl describe pod <pod-name> -n <namespace>
kubectl get events -A --sort-by=.lastTimestamp
kubectl describe node <node-name>
kubectl top node <node-name>
kubectl get pdb -A

重点关注:

  • Pod 事件中的 EvictedPreemptedNodeNotReadyTaintManagerEviction 等原因。
  • Node Condition 中的 MemoryPressureDiskPressurePIDPressureReady
  • Pod 的 requests / limits、PriorityClass、QoS 与实际资源消耗。
  • PDB 是否让计划维护中的 drain 长时间等待。

选型速查

目标推荐组合
保护 GPU、SSD、平台组件等专用节点Node Label + NoSchedule Taint + 目标 Pod Toleration + Required Node Affinity
尽量优先使用某类节点Preferred Node Affinity;必要时加 PreferNoSchedule
同一服务副本不落在同一节点Pod Anti-Affinity,或 Pod Topology Spread Constraints
应用与缓存尽量同可用区Preferred Pod Affinity,topologyKey: topology.kubernetes.io/zone
节点例行维护cordon + drain,确认 PDB 和 emptyDir 风险
节点短暂不可达时避免立即迁移NoExecute Toleration + 合理的 tolerationSeconds
避免节点压力下关键服务被优先淘汰合理 requests / limits、PriorityClass、容量余量与监控告警

使用建议

  1. Label 表示节点能力或属性,Taint 表示准入限制;不要只用其中一个来表达完整隔离。
  2. 尽量把“必须”与“最好”分开:必须使用 required...,偏好使用 preferred...PreferNoSchedule
  3. 对业务 Pod 避免宽泛的 Exists Toleration,尤其是空 key 的 Toleration。
  4. NoExecute 要与业务恢复目标一起评估,不能只因“防抖”就无限容忍。
  5. 维护节点先看 PDB、副本数、备用容量和本地临时数据,再执行 drain
  6. 面对节点压力,优先治理资源请求、限制和节点容量;PDB 不能阻止 kubelet 自救驱逐。
  7. 不要通过 .spec.nodeName 强行绑定节点来绕过调度约束。它会绕过 scheduler 对 NoSchedule Taint 的检查,NoExecute 仍可能让 kubelet 驱逐 Pod。

参考资料

本文共 3881 字,创建于 Jul 1, 2026

相关标签:Kubernetes, DevOps, ByAI

博客助手

正在打开博客助手…