
Kubernetes Pod 调度实战:nodeSelector、亲和性与 taint/toleration 把 Pod 放到指定节点集群里混着 GPU 节点、普通节点、还有几台专供网关的机器。你部署了个训练任务,结果它被调度到了普通节点上,GPU 节点空着;或者你想让某几台机器只跑核心服务,别人的测试 Pod 却一直往上挤。这些都是调度控制没配好。这篇把 Kubernetes 控制 Pod 落到哪个节点的三套机制讲透:nodeSelector、亲和性(affinity)、污点与容忍(taint/toleration),并说清楚它们各自解决什么问题、什么时候该用哪个。先看默认行为:调度器凭什么选节点不加任何约束时,kube-scheduler 会过滤掉资源不够的节点,再按打分选最优的。你无法控制它偏好哪台机器。要干预,就得给节点打标签、给 Pod 加约束。先给节点打标签,这是所有调度控制的基础:# 标记 GPU 节点kubectl label nodes node-gpu-01hardwaregpu# 标记专供网关的节点kubectl label nodes node-gw-01rolegateway# 查看节点标签kubectl get nodes --show-labelsnodeSelector:最简单的硬性指定nodeSelector是最直白的方式:Pod 只会调度到带指定标签的节点上。apiVersion:v1kind:Podmetadata:name:gpu-trainerspec:nodeSelector:hardware:gpu# 只往带 hardwaregpu 标签的节点上放containers:-name:trainerimage:my-trainer:latest它的局限也很明显:只能做等值匹配、多个条件是「与」关系、没有软约束(找不到符合的节点 Pod 就一直 Pending)。稍微复杂点的需求它就不够用了,这时候上亲和性。nodeAffinity:带「或」和「软偏好」的节点选择节点亲和性比nodeSelector强在两点:支持In/NotIn/Exists等操作符(能表达「或」),以及区分硬约束和软偏好。requiredDuringSchedulingIgnoredDuringExecution:硬约束,不满足不调度。preferredDuringSchedulingIgnoredDuringExecution:软偏好,满足最好,不满足也能调度到别处。apiVersion:apps/v1kind:Deploymentmetadata:name:webspec:replicas:3selector:matchLabels:{app:web}template:metadata:labels:{app:web}spec:affinity:nodeAffinity:# 硬约束:必须落在 ssd 或 nvme 磁盘的节点requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:-matchExpressions:-key:disktypeoperator:Invalues:[ssd,nvme]# In 表达「或」# 软偏好:优先华东可用区,不满足也认preferredDuringSchedulingIgnoredDuringExecution:-weight:80preference:matchExpressions:-key:zoneoperator:Invalues:[east-1]那串长得吓人的字段名其实在说一件事:IgnoredDuringExecution表示「只在调度那一刻检查,Pod 跑起来后就算节点标签变了也不驱逐」。目前所有 affinity 都是这个行为,记住这点就不会被字段名劝退。podAffinity / podAntiAffinity:让 Pod 靠近或远离彼此上面控制的是「Pod 和节点」的关系。有时你要控制的是「Pod 和 Pod」的关系:比如把同一服务的多个副本打散到不同节点(避免单节点故障全挂),或者把 Web 和它的缓存放到一起(降低网络延迟)。打散副本用podAntiAffinity:affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchLabels:{app:web}# topologyKey 是打散的粒度:按主机名打散 每个节点最多一个topologyKey:kubernetes.io/hostnametopologyKey是关键:它定义「什么算同一个拓扑域」。用kubernetes.io/hostname表示每台机器是一个域,于是同一个app: web的 Pod 不会有两个落在同一节点。换成topology.kubernetes.io/zone就是按可用区打散。要注意用required(硬打散)时,如果副本数超过节点数,多出来的 Pod 会一直 Pending——因为找不到没有 web Pod 的节点了。副本多、节点少的场景改用preferred软打散更稳妥。taint/toleration:让节点「排斥」Pod,反向控制前面几种是 Pod 主动挑节点。污点(taint)是反过来的:节点主动排斥 Pod,除非 Pod 明确声明能容忍这个污点。典型场景:一台专供网关的机器,不想让普通业务 Pod 挤上来。给它打污点:# 给节点打污点,effectNoSchedule 表示不容忍就别调度过来kubectl taint nodes node-gw-01dedicatedgateway:NoSchedule打完之后,除非 Pod 显式容忍,否则调度器不会把它放到这台机器。只有网关服务加上对应的 toleration:spec:tolerations:-key:dedicatedoperator:Equalvalue:gatewayeffect:NoSchedule# 要和污点的 effect 对上containers:-name:gatewayimage:nginx:latesteffect有三种,行为差别很大:NoSchedule:不容忍的新 Pod 不调度过来,已经在跑的不动。PreferNoSchedule:尽量不调度过来,但没别的节点时还是会来(软版本)。NoExecute:不容忍的连已经在跑的都会被驱逐。K8s 给节点标记node.kubernetes.io/not-ready时用的就是这个,所以节点 NotReady 一段时间后上面的 Pod 会被赶走重调度。关键区分:toleration 只是「允许」,不是「指定」这是最容易搞混的地方。给 Pod 加 toleration,只是让它有资格上带污点的节点,并不保证它一定去那儿。它照样可能被调度到其他没污点的普通节点。如果你既要「别人上不来」又要「网关服务一定上去」,得两个机制配合:节点打污点(挡住别人) 网关 Pod 同时配 toleration(有资格上) nodeAffinity 或 nodeSelector(指定必须上这台)。三者缺一,效果就会打折。spec:# 1. 容忍污点:有资格上网关节点tolerations:-key:dedicatedoperator:Equalvalue:gatewayeffect:NoSchedule# 2. 亲和指定:必须上带 rolegateway 标签的节点nodeSelector:role:gateway小结nodeSelector:硬性等值匹配,最简单,只够简单场景。nodeAffinity:支持「或」和软偏好(required vs preferred),控制 Pod 落到哪类节点。podAffinity / podAntiAffinity:控制 Pod 之间靠近或打散,topologyKey决定打散粒度。taint/toleration:节点反向排斥 Pod,NoExecute会驱逐已运行的 Pod;toleration 只给「资格」不给「指定」。专用节点要「污点挡别人 toleration 给资格 affinity 定去向」三件套配合。一句话记忆:affinity 是 Pod 挑节点,taint 是节点挑 Pod,toleration 只是入场券不是指定席。