Kubernetes 调度、污点、容忍、亲和性与驱逐

This article is extracted from the chat log with AI. Please identify it with caution.

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

先建立一张心智地图#

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 进入被污点保护的节点可配合 NoExecute 延迟驱逐
Eviction系统或管理员终止 Pod节点维护、资源压力、节点故障会终止或迁移 Pod

一句话记忆:

亲和性是“我想去哪里”;
污点是“谁不该来”;
容忍是“我可以进去”;
驱逐是“已经在这里的 Pod 何时必须离开”。

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

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

调度与运行时的边界#

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

创建未绑定 Node 的 Pod
        |
        v
过滤不满足硬条件的节点
资源请求、nodeSelector、required Node Affinity、
未被容忍的 NoSchedule / NoExecute 污点等
        |
        v
对可用节点打分
preferred Affinity、PreferNoSchedule、资源分布等
        |
        v
绑定 Pod 到某个 Node
        |
        v
运行时持续观察节点状态
节点维护、NoExecute 污点、节点不可达、资源压力等
        |
        v
必要时终止或驱逐 Pod,由控制器创建替代副本

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

污点与容忍:节点隔离的反向筛选#

Taint(污点)定义在 Node 上,格式为:

<key>=<value>:<effect>

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

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

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

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没有匹配容忍则不能调度不驱逐专用节点、GPU 节点、维护前封锁
PreferNoSchedule调度器尽量避开,但不保证不驱逐软隔离、临时倾向
NoExecute没有匹配容忍则不能调度驱逐不匹配 Pod节点异常、强制隔离

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

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

容忍字段与匹配规则#

容忍写在 Pod 的 .spec.tolerations 中:

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

Equal 是精确匹配:

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

Exists 只关心 key 和 effect,不写 value

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

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

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

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

NoExecutetolerationSeconds#

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

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

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

推荐组合:专用节点池#

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

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

先给节点打标:

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

再部署工作负载:

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

这四层的职责不同:

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

DaemonSet 系统组件#

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

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

# 查看 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

亲和性:从“允许”到“选择”#

Node Affinity#

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

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

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

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 要求同一个应用的副本不能落在同一台主机上:

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

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

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

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

计划维护:cordontaintdrain#

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

# 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 只能帮助计划内的迁移控制并发,不能替代资源请求、限制、容量规划和节点监控。

节点状态污点与 NoExecute#

节点不可达或状态异常时,控制平面可能根据 Node Condition 添加污点,例如 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。实际污点及 effect 应以集群中的 Node 对象为准:

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

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

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,优先检查调度事件:

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

常见事件含义:

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

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

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

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 容忍,尤其是空 key 的容忍。
  4. NoExecute 要与业务恢复目标一起评估,不能只因“防抖”就无限容忍。
  5. 维护节点先看 PDB、副本数、备用容量和本地临时数据,再执行 drain
  6. 面对节点压力,优先治理资源请求、限制和节点容量;PDB 不能阻止 kubelet 自救驱逐。
  7. 不要通过 .spec.nodeName 强行绑定节点来绕过调度约束。它会绕过 scheduler 对 NoSchedule 污点的检查,NoExecute 仍可能让 kubelet 驱逐 Pod。

参考资料#

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

相关标签: Kubernetes, DevOps, ByAI