Kubernetes中让节点排斥Pod的污点机制与Pod侧的容忍声明
Taint(污点) 是打在节点上的标记,表示「这个节点不欢迎某些 Pod」;Toleration(容忍) 是写在Pod 上的声明,表示「我能接受这种污点」。两者配合,实现与亲和性方向相反的调度控制:亲和性是 Pod 主动挑选节点,污点是节点拒绝 Pod。
调度器处理污点的逻辑像一道过滤器:
1. 取出节点上的全部污点
2. 划掉 Pod 有对应容忍的那些污点
3. 剩下未被容忍的污点按 effect 生效:
NoSchedule → 不把 Pod 调度到该节点
PreferNoSchedule → 尽量不调度,但资源不够时仍可调度
NoExecute → 不调度;已运行的 Pod 会被驱逐
一个关键前提:污点可以阻止调度,但不能吸引 Pod。想让 Pod 只去某类节点,必须同时用 nodeAffinity 或 nodeSelector 把它们「吸」过去,只靠污点会把 Pod 卡在 Pending。
kubectl taint nodes <node-name> <key>[=<value>]:<effect> [flags]
添加与删除:
# 添加污点
kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 dedicated=gpu:NoSchedule
# 不带 value 的污点
kubectl taint nodes node1 key1:NoSchedule
# 删除污点(在末尾加一个减号)
kubectl taint nodes node1 key1=value1:NoSchedule-
kubectl taint nodes node1 key1:NoSchedule-
# 按标签批量操作
kubectl taint nodes -l disktype=ssd dedicated=gpu:NoSchedule
# 删除所有节点上的控制平面污点(仅单机测试集群这么做)
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
NoSchedule
新 Pod: 不容忍则不调度
已运行 Pod: 不动,继续留在节点上
典型用途: 专用节点、控制平面节点
PreferNoSchedule
新 Pod: 尽量不调度,资源紧张时仍会调度上去
已运行 Pod: 不动
典型用途: 软性的容量预留,不希望但可以接受
NoExecute
新 Pod: 不容忍则不调度
已运行 Pod: 立刻驱逐
容忍但无 tolerationSeconds: 永久留在节点上
容忍且有 tolerationSeconds: 再待 N 秒后由节点生命周期控制器驱逐
典型用途: 节点故障(not-ready / unreachable)、节点下线前的腾空
spec:
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
- key: "key2"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 3600
字段规则:
key 污点的键;留空时必须搭配 operator: Exists,表示匹配所有键
operator Equal(默认)或 Exists
value operator 为 Exists 时不应填写
effect 留空表示匹配所有 effect
tolerationSeconds 仅对 NoExecute 有意义,表示被驱逐前还能停留多久
一个常见的误解是 operator: Exists 表示「容忍任意值」。准确的说法是:key 为空且 operator: Exists 时才匹配所有键和值,此时 effect 依然要单独匹配。
spec:
tolerations:
- operator: Exists
这条容忍的 key、value、effect 全部为空,可以容忍节点上的任何污点,常用于 DaemonSet 或运维专用负载。它的副作用是让节点上的 NoExecute 故障污点也失效,节点失联时 Pod 不会被迁移,使用前要想清楚。
spec:
tolerations:
# 容忍控制平面污点,允许在控制平面节点上运行
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
# 容忍"节点未就绪"污点 60 秒,给短暂抖动留出恢复时间
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
tolerationSeconds: 60
节点控制器与 kubelet 会按节点状况(condition)自动打上污点,直到状况恢复才移除:
node.kubernetes.io/not-ready NoExecute Ready 条件为 False
node.kubernetes.io/unreachable NoExecute Ready 条件为 Unknown(节点失联)
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 节点网络未就绪(主要影响 hostNetwork Pod)
node.kubernetes.io/unschedulable NoSchedule 节点被 cordon
node.cloudprovider.kubernetes.io/uninitialized NoSchedule 云厂商尚未完成节点初始化
此外 kubeadm 会给控制平面节点打上 node-role.kubernetes.io/control-plane:NoSchedule。
很多「我没写容忍,为什么能调度上去」的疑惑都来自这两处自动注入:
DefaultTolerationSeconds 准入控制器(apiserver)
给所有未显式声明的 Pod 注入:
node.kubernetes.io/not-ready NoExecute tolerationSeconds: 300
node.kubernetes.io/unreachable NoExecute tolerationSeconds: 300
可用 apiserver 的 --default-not-ready-toleration-seconds 与
--default-unreachable-toleration-seconds 调整(默认均为 300)
DaemonSet 控制器
给 DaemonSet 创建的 Pod 自动注入:
not-ready / unreachable NoExecute(带 tolerationSeconds: 300)
disk-pressure / memory-pressure / pid-pressure / unschedulable NoSchedule
network-unavailable NoSchedule(hostNetwork Pod)
这也解释了为什么节点宕机后,业务 Pod 要等约 5 分钟才开始漂移 —— 那正是默认的 300 秒容忍窗口。
# 查看节点上的污点
kubectl describe node node1 | grep -A2 Taints
kubectl get node node1 -o jsonpath='{.spec.taints}' | jq .
# 以表格形式列出所有节点的污点
kubectl get nodes -o custom-columns='NAME:.metadata.name,TAINTS:.spec.taints'
# 查看 Pod 上的容忍
kubectl get pod nginx -o jsonpath='{.spec.tolerations}' | jq .
# 驱逐节点上的 Pod 后打上不可调度污点(节点维护标准流程)
kubectl drain node1 --ignore-daemonsets --delete-emptydir-data
kubectl cordon node1
kubectl uncordon node1
# 观察节点污点变化的实时过程
kubectl get nodes -w -o custom-columns='NAME:.metadata.name,TAINTS:.spec.taints'
# Pod 因污点无法调度时,事件里会明确列出是哪个污点
kubectl describe pod nginx | sed -n '/Events/,$p'
# 0/3 nodes are available: 1 node(s) had untolerated taint {dedicated: gpu},
# 2 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }
# 确认节点当前是否真的带这个污点
kubectl get node node1 -o jsonpath='{.spec.taints[?(@.key=="dedicated")]}'
# Pod 被 NoExecute 驱逐时,事件原因是 TaintManagerEviction
kubectl get events -A --field-selector reason=TaintManagerEviction
三种 effect 对「已运行 Pod」的行为完全不同。NoSchedule 只管新调度,不会动已运行的 Pod;PreferNoSchedule 更弱,连新调度都拦不住;只有 NoExecute 会驱逐已运行的 Pod。给在线业务节点加污点时若误用了 NoExecute,会造成正在服务的 Pod 被立即赶走。
tolerationSeconds 只对 NoExecute 有意义。写在 NoSchedule 或 PreferNoSchedule 的容忍里不会报错,但完全不起作用,是典型的「配了但没效果」的坑。
容错了不等于会被调度上去。容忍只解除「污点这一层的拒绝」,节点资源不足、亲和性不匹配、节点被 cordon 等因素依然会阻止调度。容忍是必要条件,不是充分条件。
手工指定 spec.nodeName 会绕过调度器,污点中的 NoSchedule 也就拦不住;但若该节点有 Pod 未容忍的 NoExecute 污点,kubelet 仍会把 Pod 驱逐掉。直接指定 nodeName 时应格外小心。
kubectl drain 依赖污点机制,但两者不是一回事。drain 会给节点打上 node.kubernetes.io/unschedulable 污点再驱逐 Pod,而单纯 cordon 只打污点、不驱逐。要对节点做维护,必须用 drain。
DaemonSet 的 Pod 会自动获得大量容忍,所以它们不会因为节点打上 memory-pressure 等污点而被赶走。这也是容器网络、日志采集这类组件能在故障节点上继续运行的原因,排查「DaemonSet 为什么没被驱逐」时先想到这一点。
默认 300 秒的容忍窗口意味着故障发现延迟。节点宕机后 Pod 要等约 5 分钟才被判定失联并重建,无法满足严格的 RTO 要求;需要更快时可以调小 apiserver 的 --default-not-ready-toleration-seconds 等参数,或在 Pod 上显式声明更短的 tolerationSeconds。
删除污点必须连值一起匹配。kubectl taint nodes node1 key1=value1:NoSchedule- 才能删掉带值的污点;只写 key1:NoSchedule- 在某些情况下匹配不到,移除后建议用 kubectl describe node 再确认一次。
污点不限制已有 Pod 占用的资源,也不改变节点上已运行 Pod 的行为。给节点加 NoSchedule 后,该节点上的旧 Pod 会继续占用资源,新 Pod 进不来,容易形成「资源看着够、调度却不进去」的错觉。
不建议在普通业务节点上滥用 NoExecute。节点压力导致的 NoExecute 由 kubelet 自动管理,人工再加 NoExecute 会让 Pod 在节点间来回迁移,配合 descheduler 或集群自动伸缩时尤其容易产生震荡。
kubectl — Kubernetes集群管理工具node — 污点的作用对象pod — 容忍声明的载体affinity — 与污点方向相反的调度控制机制kube-scheduler — 执行污点与容忍判定的调度器poddisruptionbudget — 节点腾空时保护可用副本数descheduler — 纠正运行后不再符合容忍规则的Pod