【K8S 避坑指南】taint 的 3 种效果全解析:节点隔离、维护驱逐与多租户实战
干了10年运维,K8s 从 1.6 玩到现在的 1.32。说实话,关于 taint(污点)这东西,大部分人都知道但用不透——要么只会给 master 打个 NoSchedule,要么节点维护时搞不清该用哪个 effect 导致线上事故。
今天就把它彻底讲透。读完你就能:熟练掌握 taint 增删改查、3 种 effect 的精准选型、NoExecute 驱逐的优雅控场、以及 SRE 天天用到的节点维护流程。
我用的环境是 K8s v1.28,测试在 Ubuntu 22.04(Mac 上 minikube 也差不多)。
一、先搞懂核心概念(别一上来就跑偏)
Taint(污点)是打在节点上的标签,作用是 “不准某些 Pod 上来” 。与之对应的是 Toleration(容忍) ,定义在 Pod 上,作用是 “这个节点上的污点我忍了,让我上去” 。
通俗理解:节点上长了“刺”,默认所有 Pod 都碰不得,除非 Pod 戴上“手套”(配置了对应容忍),才能靠近。
特别注意:Toleration 允许调度但不保证调度,它只是让该节点进入候选范围,调度器还会考虑资源、亲和性等其他因素。
污点的组成格式
一个 taint 就三部分:key=value:effect。格式要求:
key:必须以字母或数字开头,可包含字母、数字、连字符、点和下划线,最多 253 个字符,可选带 DNS 子域前缀(如example.com/xxx)value:可选,规则和 key 类似,最多 63 个字符effect:只能是NoSchedule、PreferNoSchedule或NoExecute三者之一
3 种 effect 的区别(这里最容易混淆)
|
effect |
对新 Pod 的影响 |
对已在运行的 Pod 的影响 |
|
NoSchedule |
不接受调度,除非 Pod 容忍 |
无影响,不清退 |
|
PreferNoSchedule |
尽量不调度(软限制) |
无影响 |
|
NoExecute |
不接受调度 |
不容忍的立刻驱逐 |
说实话,PreferNoSchedule 我用的很少,因为它“尽量但不保证”,在资源紧张时调度器还是可能塞上去,不够可靠。我一般只在“稍微倾斜但不想硬卡死”的场景用。
二、常用操作命令(增删改查一把梭)
2.1 添加污点
# 基础语法
kubectl taint node <node-name> <key>=<value>:<effect>
# 给 node1 打个 NoSchedule
kubectl taint node node1 dedicated=gpu:NoSchedule
# 预期输出:node/node1 tainted
# 不带 value 也行(效果一样)
kubectl taint node node2 disk-pressure:NoExecute
一个小坑:如果不小心打错了 effect,直接重新执行正确命令即可覆盖。但如果你用了 --overwrite=false(默认 false 只拒绝覆盖相同 key+effect),新值会被拒绝。大多数场景不用关心。
2.2 查看污点
# 方式一:describe(适合快速查看)
kubectl describe node node1 | grep -i taints
# 输出:Taints: dedicated=gpu:NoSchedule
# 方式二:get -o json + jq(适合脚本用)
kubectl get nodes -o json | jq -r '.items[] | .metadata.name + "\t" + ([.spec.taints[]? | "\(.key)=\(.value // ""):\(.effect)"] | join(", "))'
# 方式三:直接 get -o yaml 看完整配置
kubectl get node node1 -o yaml | grep -A5 taints
2.3 移除污点(结尾加个减号 -)
# 移除指定的 key+effect
kubectl taint node node1 dedicated=gpu:NoSchedule-
# 移除该 key 下的所有污点(不管 effect)
kubectl taint node node1 dedicated-
# 移除所有污点(不指定 key)
# 这个需要脚本或手动逐个删
移除后 Pod 并不会自动迁回来——K8s 的调度器不会主动 rebalance,除非有新 Pod 创建或现有 Pod 因其他原因被重新调度。
2.4 批量操作(用 -l 按标签选择)
# 给所有带 env=prod 标签的节点打污点
kubectl taint node -l env=prod environment=production:NoSchedule
# 给所有节点打同一个污点
kubectl taint nodes --all example.com/under-maintenance=true:NoSchedule
--all 要小心,会打在集群所有节点上——我当年手滑过一次,差点把所有业务 Pod 赶跑。
三、三个实战场景(SRE 天天用)
场景一:master 节点只运行系统组件(NoSchedule)
新装的 K8s 集群,master 默认就有一个污点,防止普通 Pod 跑上去:
# 查看 master 默认污点
kubectl describe node master | grep Taints
# Taints: node-role.kubernetes.io/master:NoSchedule
如果你想手动给 master 加(升级后的新版 K8s 可能用的 key 不同):
kubectl taint node master node-role.kubernetes.io/master:NoSchedule
系统组件(kube-apiserver、etcd、coredns 等)在部署时已经配置了对应的容忍,所以它们还能跑上去。手动加污点前最好先确认现有 Pod 的容忍情况,否则可能造成不可预料的驱逐。
场景二:GPU 节点专用(只有需要 GPU 的 Pod 才能上)
# 1. 给 GPU 节点打上专用污点
kubectl taint node gpu-node-1 gpu=exclusive:NoSchedule
# 2. 普通 Pod 没有容忍,调度不到这个节点
# 3. 需要 GPU 的 Pod 在 yaml 里配置 toleration
Pod 里的容忍配置:
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-container
image: nvidia/cuda:11.0-base
tolerations:
- key: "gpu"
operator: "Equal"
value: "exclusive"
effect: "NoSchedule"
场景三:节点维护——驱逐 + 隔离 + 恢复(完整流程)
这是 SRE 干活时最常用的流程,也是我最想让你记住的。整体步骤:节点隔离(禁止新 Pod)→ 排空 Pod(优雅迁移)→ 维护 → 恢复调度。
步骤 1:禁止新 Pod 调度到该节点(打 NoSchedule)
kubectl taint node node-to-maintain maintenance=true:NoSchedule
这一步只拦新来的,已经在跑的 Pod 继续干,不影响业务。建议在业务低峰期操作,别在流量高峰往下赶 Pod。
步骤 2:驱逐现有 Pod(这才是搬家动作)
# 优雅驱逐:先 cordon 再加 delete,受 PodDisruptionBudget 限制
kubectl drain node-to-maintain --ignore-daemonsets --delete-emptydir-data
# 参数说明:
# --ignore-daemonsets 忽略 DaemonSet 管理的 Pod(它们会在新节点自动创建)
# --delete-emptydir-data 删除 emptyDir 的 Pod(如果你不在意数据就加,否则别加)
# --grace-period=30 给 Pod 30 秒优雅退出时间
如果某些 Pod 硬是驱不动(比如 PodDisruptionBudget 配置过于保守),可以用 --disable-eviction 绕开 PDB,但生产环境不推荐:
kubectl drain node-to-maintain --ignore-daemonsets --delete-emptydir-data --disable-eviction
我踩过一个大坑:kubectl drain 会先 cordon 再 delete,但如果 PDB 限制了最小可用副本数,死活驱不动。所以建议先在业务层配置好 PDB,用 kubectl drain 而非直接 kubectl delete pod。
步骤 3:维护节点(重启、修硬件、换内核等)
现在已经没有 Pod 在该节点上了,随便折腾。
步骤 4:恢复调度(维护完成后)
# 先解除 cordon(如果 drain 时自动加了的话,检查一下节点状态)
kubectl uncordon node-to-maintain
# 再移除污点
kubectl taint node node-to-maintain maintenance=true:NoSchedule-
如果搞忘了移除污点,就会变成“节点状态 Ready,但一直没 Pod 上来”的诡异情况。
补充:NoExecute 和 tolerationSeconds 的优雅驱逐
NoExecute 更“狠”——不仅禁止新 Pod,还会把已经在跑且不容忍的 Pod 赶走。但你可以在 Pod 上配置 tolerationSeconds,给个“缓冲时间”。
tolerations:
- key: "maintenance"
operator: "Equal"
value: "true"
effect: "NoExecute"
tolerationSeconds: 120 # 120 秒后才会被驱逐
这在临时高负载、短暂网络分区等场景特别好用——给控制器一点反应时间,避免雪崩式驱逐。
举个例子:节点暂时打上 maintenance=true:NoExecute,Pod 有 120 秒缓冲。如果 120 秒内污点被移除,Pod 继续原地跑;120 秒后污点还在,才被驱逐。
不过老实说,节点维护我基本用 drain + NoSchedule 而不是 NoExecute,因为 NoExecute 太刚了。NoExecute 我一般用在“这个节点出问题了赶紧把业务赶走”的紧急场景。
四、常见问题排查
Q1:节点关机后 Pod 不重新调度?
节点关机后系统会自动添加:
node.kubernetes.io/unreachable:NoExecutenode.cloudprovider.kubernetes.io/shutdown:NoSchedule
如果 Pod 配了全容忍(operator=Exists, key 空),就会一直认为节点可用,不会被重新调度。排查方法:
kubectl describe pod <pod-name>
kubectl get pod <pod-name> -o yaml | grep -A10 tolerations
一般建议保持默认的 300 秒容忍——K8s 默认会给 Pod 加上针对 not-ready 和 unreachable 的 300s 容忍,意味着节点失联 5 分钟内 Pod 仍保留,5 分钟后才被驱逐并重新调度。
Q2:明明加了污点,Pod 还是调度上来了?
检查以下几点:
- Pod 里是不是配置了容忍?容忍的 key、value、effect 是否完全匹配?
- Pod 是否指定了
.spec.nodeName?如果直接指定了节点名,会绕过调度器,污点不生效 - 节点上的污点是不是
PreferNoSchedule?那只是“尽量”,不是“绝对”
Q3:多 taint 和多 toleration 的匹配规则
调度器处理逻辑很简单:
- 列出节点上所有 taint
- 用 Pod 的 tolerations 去匹配,能匹配上的忽略
- 剩下的 taint 就是有效的:
- 如果存在
NoSchedule且未匹配 → 调度失败 - 如果只有
PreferNoSchedule未匹配 → 尽量不调度 - 如果存在
NoExecute未匹配(且 Pod 已在运行)→ 驱逐
- 如果存在
Q4:移除污点后 Pod 怎么不回来?
K8s 不会主动 rebalance。你得手动滚动更新或者靠 HPA 新建副本。
五、我的几条“血泪教训”
1、NoExecute 用之前一定确认 Pod 有容忍,否则业务直接全挂。
我吃过一次亏:打个 NoExecute 忘了看 DaemonSet 容忍情况,监控全没了。
2、kubectl drain 和直接删 Pod 不是一回事。
drain 受 PDB 保护,直接 DELETE 不受限。要做节点维护,永远用 drain。
3、默认的 300 秒容忍很多人不知道,排查问题时容易被绕晕。
节点失联 5 分钟内 Pod 不重调度不是 Bug,是 K8s 防止网络抖动导致大面积迁移。知道这个能省半天排查时间。
4、生产环境谨慎使用 --all。
优先用 -l label 按标签操作,出问题影响面可控。
写在最后
污点这东西,说到底是 K8s 调度精细化的基础工具。掌握 3 种 effect、记住 drain 的完整流程、知道默认 300 秒容忍的存在,就能解决日常 90% 的问题。
你还有什么骚操作或踩过的坑?评论区见,咱们一起把 K8s 调度这块玩明白。如果觉得这篇对你有用,欢迎分享给更多正在和 K8s 搏斗的兄弟们。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)