Kubernetes 污点(Taints)和容忍度(Tolerations)是什么?如何使用它们控制 Pod 调度?
Kubernetes 集群中并非所有节点都一样——有的挂了 GPU,有的专门跑监控组件,有的正在维护。如何让 Pod "知道"哪些节点该避开、哪些节点可以进入?答案就是污点(Taints)和容忍度(Tolerations)。这对机制从节点侧和 Pod 侧分别控制调度行为,是 Kubernetes 调度体系中不可绕过的一环。
污点是什么——节点说"别来"
污点是打在节点上的标记,告诉调度器:"除非 Pod 明确声明能容忍我,否则别往这儿调度。"
一个完整的污点由三部分组成:
- Key:污点的键,必填。比如
dedicated、node-role.kubernetes.io/master - Value:污点的值,选填。比如
gpu、control-plane - Effect:污点生效的方式,必填。决定"不匹配时怎么办"
三种 Effect 的区别
这是面试中最常被追问的细节,三种 Effect 行为差异很大:
NoSchedule——硬性拒绝新 Pod 调度到该节点,但已经在跑的 Pod 不受影响。这是最常见的用法,典型场景是为专用节点(GPU、Ingress)加锁:没写容忍度的 Pod 根本进不来。
PreferNoSchedule——软性偏好,调度器会尽量避开,但如果集群资源紧张,还是可能把 Pod 放过来。适合那种"最好别来,但来了也行"的场景,比如想让某节点尽量只跑日志采集组件,但不强制。
NoExecute——最严厉的一种。不仅阻止新 Pod 调度进来,还会把已经在跑但没有匹配容忍度的 Pod 驱逐走。这是唯一会"赶人"的 Effect,通常用于节点故障或维护场景。Kubernetes 控制面默认用这种 Effect 处理 NotReady 和 Unreachable 节点。
污点的增删查
添加污点用 kubectl taint:
bash# 给 node1 添加 NoSchedule 污点 kubectl taint nodes node1 dedicated=gpu:NoSchedule # 没有值的污点也可以 kubectl taint nodes node1 special:NoSchedule
查看节点的污点:
bash# 查看单个节点 kubectl describe node node1 | grep Taints # 列出所有节点的污点 kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
删除污点时在键后面加减号:
bash# 删除指定污点 kubectl taint nodes node1 dedicated=gpu:NoSchedule- # 删除该键下所有 Effect kubectl taint nodes node1 dedicated-
容忍度是什么——Pod 说"我能进"
容忍度写在 Pod 的 spec.tolerations 里,告诉调度器:"这个污点我能接受,可以调度到对应节点。"
容忍度的字段
一个容忍度包含以下字段:
| 字段 | 说明 | 是否必填 |
|---|---|---|
| key | 要容忍的污点键 | 否,为空时匹配所有键 |
| operator | Equal 或 Exists | 否,默认 Equal |
| value | 污点的值 | Equal 时必填 |
| effect | 污点的 Effect | 否,为空时匹配所有 Effect |
| tolerationSeconds | 容忍多久后被驱逐 | 仅 NoExecute 有效 |
两种 Operator 的匹配逻辑
Equal——精确匹配,key、value、effect 三者都要对上才算匹配成功:
yamltolerations: - key: "dedicated" operator: "Equal" value: "gpu" effect: "NoSchedule"
这条容忍度只能匹配 dedicated=gpu:NoSchedule 这一个污点,少一个字段都不行。
Exists——只检查 key 是否存在,不关心 value 是什么:
yamltolerations: - key: "dedicated" operator: "Exists" effect: "NoSchedule"
这条能匹配 dedicated=gpu:NoSchedule、dedicated=cpu:NoSchedule 等所有 key 为 dedicated 且 effect 为 NoSchedule 的污点。
两个极端写法也值得记住:operator: "Exists" 且不写 key,匹配一切污点;只写 operator: "Exists" 连 effect 也不写,匹配所有污点的所有 Effect。
tolerationSeconds 的作用
这个字段只对 NoExecute 生效。假设节点出了问题被自动打上污点,Pod 不会立刻被驱逐,而是等 tolerationSeconds 秒后再驱逐。这给应用留了缓冲时间做优雅退出:
yamltolerations: - key: "node.kubernetes.io/not-ready" operator: "Exists" effect: "NoExecute" tolerationSeconds: 300 # 节点 NotReady 后等 5 分钟再驱逐
如果不设置 tolerationSeconds,Pod 会一直容忍该污点,不会被驱逐。
匹配规则:调度器怎么判断
调度器的匹配逻辑可以简化为三步:
- 取出节点上所有污点
- 逐个检查 Pod 的容忍度能否匹配每个污点(忽略能匹配的)
- 剩下未匹配的污点中,如果有 NoSchedule 或 PreferNoSchedule,影响调度决策;如果有 NoExecute,直接驱逐
几个容易混淆的边界情况:
- 容忍度的 key 为空且 operator 为 Exists,匹配所有污点(包括后面新增的)
- 容忍度的 effect 为空,匹配该 key 下的所有 Effect
- 多个污点之间是"与"的关系:Pod 必须容忍节点的所有污点才能被调度,容忍一个不够
控制面自动添加的污点
Kubernetes 控制面会在特定条件下自动给节点打污点,这些污点是内置的,了解它们对排查调度问题至关重要:
| 污点键 | Effect | 触发条件 |
|---|---|---|
node.kubernetes.io/not-ready | NoExecute | 节点 NotReady |
node.kubernetes.io/unreachable | NoExecute | 节点不可达 |
node.kubernetes.io/memory-pressure | NoSchedule | 节点内存压力 |
node.kubernetes.io/disk-pressure | NoSchedule | 节点磁盘压力 |
node.kubernetes.io/pid-pressure | NoSchedule | PID 资源不足 |
node.kubernetes.io/network-unavailable | NoSchedule | 节点网络不可用 |
node.kubernetes.io/unschedulable | NoSchedule | 节点被 cordon |
Kubernetes 还默认为 Pod 添加了对 not-ready 和 unreachable 的容忍度,tolerationSeconds 为 300 秒。这就是为什么节点故障后 Pod 不会立刻被驱逐,而是等 5 分钟。这个默认行为可以通过在 Pod 中显式声明容忍度来覆盖。
实战场景
专用节点隔离
集群里有几台 GPU 机器,只想让需要 GPU 的 Pod 调度上去,普通 Pod 不要占位置。做法是给 GPU 节点打污点,给 GPU Pod 加容忍度:
bash# 节点侧 kubectl taint nodes gpu-node dedicated=gpu:NoSchedule
yaml# Pod 侧 spec: tolerations: - key: "dedicated" operator: "Equal" value: "gpu" effect: "NoSchedule" containers: - name: gpu-app image: nvidia/cuda:11.0.3-base-ubuntu20.04
但注意,只加容忍度只能让 Pod "可以进",不能保证 Pod "一定进"。如果要让 GPU Pod 只调度到 GPU 节点,还需要配合 nodeAffinity 或 nodeSelector 一起使用。污点是"拒绝"机制,不是"吸引"机制。
节点维护与驱逐
需要对节点做内核升级,先标记为不可调度并驱逐工作负载:
bash# cordon 阻止新 Pod 调度 kubectl cordon node1 # drain 驱逐现有 Pod(忽略 DaemonSet) kubectl drain node1 --ignore-daemonsets --delete-emptydir-data
kubectl drain 的本质就是给节点加 node.kubernetes.io/unschedulable:NoSchedule 污点,然后驱逐所有不匹配的 Pod。DaemonSet 的 Pod 默认带有对这些污点的容忍度,所以 drain 不会驱逐它们。
DaemonSet 为什么不怕污点
DaemonSet 控制器会自动为管理的 Pod 添加以下容忍度:
yamltolerations: - key: "node.kubernetes.io/not-ready" operator: "Exists" effect: "NoExecute" - key: "node.kubernetes.io/unreachable" operator: "Exists" effect: "NoExecute" - key: "node.kubernetes.io/disk-pressure" operator: "Exists" effect: "NoSchedule" - key: "node.kubernetes.io/memory-pressure" operator: "Exists" effect: "NoSchedule" # ... 还有更多
这就是为什么日志采集、监控 Agent 这类 DaemonSet 的 Pod 在节点出问题时仍然留在节点上——它们天生容忍这些污点。
污点容忍度与节点亲和性的配合
污点和亲和性解决的是不同方向的问题:
- 污点:从节点出发,"我不想要谁"
- 亲和性:从 Pod 出出发,"我想去哪里"
两者配合才能实现完整的调度控制。一个常见模式是:污点把不该来的 Pod 挡在外面,亲和性把该来的 Pod 拉到正确位置。比如 GPU 场景——污点阻止普通 Pod,亲和性确保 GPU Pod 优先去 GPU 节点。
单独使用污点有一个隐患:Pod 加了容忍度后能进节点,但不一定只进这个节点。它可能被调度到任何有匹配容忍度的节点。所以污点适合"排他",亲和性适合"定向",两者结合才是完整方案。
TaintBasedEviction 机制
当节点进入 NotReady 或 Unreachable 状态时,Kubernetes 不是立刻驱逐 Pod,而是基于 TaintBasedEviction 机制工作:
- 节点控制器检测到节点异常,给节点打上对应污点
- Pod 上的容忍度开始倒计时(tolerationSeconds)
- 倒计时结束,Pod 被标记为驱逐
- 驱逐由 kubelet 执行优雅终止
这个机制比旧版的基于 NodeCondition 的驱逐更灵活,因为你可以为不同的 Pod 设置不同的容忍时间。关键服务给较长缓冲,非关键服务快速驱逐。
排查调度问题的思路
Pod 调度失败时,按这个顺序排查:
bash# 1. 查看 Pod 的调度失败事件 kubectl describe pod <pod-name> | grep -A 20 Events # 2. 检查目标节点的污点 kubectl describe node <node-name> | grep Taints # 3. 对比 Pod 的容忍度是否匹配 kubectl get pod <pod-name> -o jsonpath='{.spec.tolerations}' # 4. 检查调度器日志 kubectl logs -n kube-system -l component=kube-scheduler
常见的调度失败提示比如 node(s) had taints that the pod didn't tolerate,说明 Pod 缺少对应污点的容忍度,要么给 Pod 加容忍度,要么去掉节点上的污点。
污点和容忍度的核心思路就是"节点标记排斥,Pod 声明接受"。三种 Effect 的行为差异、控制面内置污点的触发条件、与亲和性的配合关系、以及 tolerationSeconds 带来的驱逐缓冲——这些是面试和实战中真正需要掌握的要点。理解了这套机制,就能在专用节点隔离、节点维护、故障处理这些场景下做出合理的调度决策。