污点、容忍度与亲和度
2021 年,某团队把 GPU 节点打上 nvidia.com/gpu=true:NoSchedule 的污点,结果所有训练 Pod 卡在 Pending 一下午——因为没人给 Pod 加容忍度。调度器不是歧视,它只是把节点上的标记当命令执行:污点说『别把 Pod 放这』,容忍度说『这条命令我豁免』,亲和度说『不过我更想去那台』。三样东西加起来,才是 Kubernetes 调度器把 Pod 放到哪台机器的全部理由。
污点:节点身上的便签,调度器路过就绕行
taint 是打在节点上的三件套:key、value、effect。effect 决定力度:NoSchedule 只拦新调度,PreferNoSchedule 尽量不,NoExecute 连已经跑着的都驱逐。kubectl 一行就能打:kubectl taint nodes gpu-node accelerator=gpu:NoSchedule。控制面节点默认带污点(如 node-role.kubernetes.io/control-plane),普通工作负载天然不会跑到 master 上。真实场景:给 GPU、专用 SSD、机密专区打上污点,普通流量默认不进,只有你点了名的负载才进得来。
容忍度:一张写着自己能忍什么的通行证
容忍度是 Pod 身上的声明:我容忍 key=accelerator 值为 gpu、effect 为 NoSchedule 的污点。operator 有 Equal 和 Exists 两种,Exists + 空 key 等于容忍一切。致命坑:容忍度写错 key、写错 effect 或 operator 用错,调度器照样无视——它只看『这个污点有没有对应的容忍』,容忍度不会反过来强制调度器把 Pod 放上去。排错看 kubectl describe pod 的 Events,里面有 0/1 nodes are available 和 1 node(s) had untolerated taint。
亲和度:从『能不能』到『想不想』
污点和容忍度回答『能不能』,亲和度回答『想不想』。nodeAffinity 分两档:requiredDuringScheduling 是硬约束(没有满足的节点就 Pending),preferredDuringScheduling 是软偏好(节点不足时照常调度,只是不优选)。podAffinity 把相关 Pod 拉近同拓扑域,podAntiAffinity 把同类副本拆散到不同节点——多副本高可用全靠它。拓扑的单位是 topologyKey:kubernetes.io/hostname 就是按节点分,topology.kubernetes.io/zone 按可用区。
三件套怎么一起用:一个真实调度决策
假设订单服务要跑在 SSD 节点、和 Redis 同机、三个副本分散三台机器:给 SSD 节点打污点 ssd=true:NoSchedule 隔离普通流量,订单 Pod 加对应容忍度放行;nodeAffinity requiredDuringScheduling 限定节点标签 ssd=true;podAffinity preferred 让它和 Redis Pod 同节点;podAntiAffinity required 让三副本分到不同 hostname。硬约束先过滤出可行集,软偏好再打分,调度器选分最高的——这就是一次完整的调度决策。
实战示例
打上污点,跑一个没写 tolerations 的 Pod:
kubectl taint nodes gpu-node accelerator=gpu:NoSchedule
kubectl run probe --image=nginx
kubectl describe pod probe
# Events:0/1 nodes are available: 1 node(s) had untolerated taint
给它加上容忍度再 Apply,Pod 立刻调度上去:
# pod spec 里
spec:
tolerations:
- key: accelerator
operator: Equal
value: gpu
effect: NoSchedule
一行污点挡路,一行容忍放行——调度的门禁就这么简单。子关键词
Taint 节点污点:key/value/effect 三件套
打节点上的排斥标记,effect 有 NoSchedule/PreferNoSchedule/NoExecute 三档。kubectl taint nodes key=value:NoSchedule,删除用 key-。
Toleration Pod 的豁免通行证
Pod 声明容忍哪些污点,operator Equal 要 key+value 都对上,Exists 只看 key。容忍度是豁免,不是反向强制。
NoExecute 驱逐已有 Pod 的狠 effect
除了拦新调度,还会驱逐已经在跑的、没写对应容忍度的 Pod。加 tolerationSeconds 可以延迟驱逐。
NodeAffinity 节点亲和:硬/软两档
requiredDuringScheduling 是硬约束,preferredDuringScheduling 是 weight 打分。matchExpressions 用 In/NotIn/Exists 匹配节点标签。
PodAffinity / AntiAffinity 同组 Pod 拉近或拆散
podAffinity 把相关 Pod 调度到同一拓扑域,podAntiAffinity 把副本分散。两者都基于 topologyKey 定义『多近算近』。
调度打分链路 硬过滤 + 软打分
调度先按硬约束过滤可行节点,再按 preferred/资源均衡打分取最高。kube-scheduler 的 scoring 是可插拔的。
衍生角度
设计模式:声明式约束 + 打分
污点/容忍是硬约束(能或不能),亲和度 weight 是软偏好打分。调度器在硬约束过滤出的可行集里,按软偏好总分挑最优——和资源调度的 binpacking 同一套心智。
概念拆解:驱逐 vs 调度约束
NoSchedule 只管新调度,NoExecute 会驱逐已在跑的 Pod。作用时点不同,别把 taint 当『重启大法』——NoExecute 是会对存量负载动手的。
风格架构:控制面声明,调度器执行
用户只声明 Pod 的容忍与亲和,调度器在调度周期内匹配节点。声明与执行解耦,是 Kubernetes 一切资源的一致模式。
原理分析:为什么容忍度不能反向强制
容忍度是豁免不是要求:Pod 没写容忍度,调度器连看都不看带污点节点。所以『打了污点还是被打上』的根源,永远在 Pod 的 tolerations 字段。
实际用法与坑
- 打污点前想清楚 effect:NoExecute 会立即驱逐节点上已有的 Pod,不只是拦新的。
- 容忍度 operator 别用 Exists + 空 key:那等于容忍所有污点,NoExecute 的驱逐也拦不住。
- preferred 是『尽量』不是『必须』:节点不够时照常调度,别拿它当高可用的硬保证。
- podAntiAffinity 只保证启动时分散:节点增加后不会自动重排副本。
- master 节点的控制面污点别手贱删:那是控制面的保险丝。
下一步动手做
给一台测试节点打 taint,先跑无 toleration 的 Pod 看 Pending 与 Events;加上 toleration 看放行;再用 nodeAffinity 的 preferred 起两个 Pod,观察调度器选了哪台;最后用 podAntiAffinity 起三个副本,确认它们分散到不同节点。
相关书籍
Kubernetes in Action
读调度与亲和章节:taint/toleration/nodeAffinity 的完整字段与实战组合。