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