干了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:只能是 NoSchedulePreferNoScheduleNoExecute 三者之一

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:NoExecute
  • node.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-readyunreachable 的 300s 容忍,意味着节点失联 5 分钟内 Pod 仍保留,5 分钟后才被驱逐并重新调度。

Q2:明明加了污点,Pod 还是调度上来了?

检查以下几点:

  • Pod 里是不是配置了容忍?容忍的 key、value、effect 是否完全匹配?
  • Pod 是否指定了 .spec.nodeName?如果直接指定了节点名,会绕过调度器,污点不生效
  • 节点上的污点是不是 PreferNoSchedule?那只是“尽量”,不是“绝对”

Q3:多 taint 和多 toleration 的匹配规则

调度器处理逻辑很简单:

  1. 列出节点上所有 taint
  2. 用 Pod 的 tolerations 去匹配,能匹配上的忽略
  3. 剩下的 taint 就是有效的:
    1. 如果存在 NoSchedule 且未匹配 → 调度失败
    2. 如果只有 PreferNoSchedule 未匹配 → 尽量不调度
    3. 如果存在 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 搏斗的兄弟们。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐