K8s Pod 调度全解:nodeName、nodeSelector、亲和性、污点容忍到 cordon/drain
学习参考:将 Pod 指派给节点
默认情况下,K8s 调度器会自动把 Pod 分散到资源充足的节点上。但生产环境经常有特殊需求:把有大量磁盘 I/O 的 Pod 放到 SSD 节点、把 GPU 任务放到带 GPU 的节点、把同一服务的副本打散到不同节点做高可用。本文把 K8s 控制 Pod 运行位置的5 种手段全 部过一遍,从最简单的 nodeName 到最灵活的亲和性/反亲和性,再到污点容忍和节点维护操作。
一、五种调度约束手段总览
| 手段 | 方向 | 灵活度 | 适用场景 |
|---|---|---|---|
nodeName | Pod → 指定节点 | 最死板,直接写死节点名 | 临时调试,生产不推荐 |
nodeSelector | Pod → 选带标签的节点 | 简单等值匹配 | 最常用的基础选择 |
nodeAffinity | Pod → 选节点标签 | 支持 In/NotIn/Exists/Gt/Lt、硬/软策略 | 需要复杂规则的场景 |
podAffinity/podAntiAffinity | Pod → 跟其他 Pod 同/不同拓扑域 | 基于已运行 Pod 标签 | 打散副本、就近通信 |
Taint + Toleration | 节点 → 选 Pod | 节点主动拒绝,Pod 申请容忍 | 专用节点、GPU 节点、主节点隔离 |
二、nodeName:最直接也最死板
nodeName是 PodSpec 的一个字段,值就是节点名称,调度器直接把 Pod 塞到那个节点上。优先级最高,但通常不推荐使用。
限制:
- 指定节点不存在 → Pod 不会运行,甚至被自动删除
- 指定节点资源不够 → 调度失败,报 OutOfmemory / OutOfcpu
- 云环境节点名称不可预测,写死了迁移困难
apiVersion:apps/v1kind:Deploymentmetadata:labels:app:webappname:webappspec:replicas:2selector:matchLabels:app:webapptemplate:metadata:labels:app:webappspec:nodeName:worker32.laoma.cloud# 直接写死节点containers:-image:hub.laoma.cloud/library/nginxname:nginxroot@master30:~# kubectl apply -f deploy-with-nodeName.yamlroot@master30:~# kubectl get pods -o wide --no-headers | awk '{print $1,$3,$7}'webapp-55c6b896bc-ns9m5 Running worker32.laoma.cloud webapp-55c6b896bc-qr892 Running worker32.laoma.cloud三、nodeSelector:最常用的基础选择
给节点打标签,Pod 选标签。这是节点选择约束的推荐形式。
# 查看节点标签root@master30:~# kubectl get node --show-labels# 给 worker31 打 disktype=ssd 标签root@master30:~# kubectl label node worker31.laoma.cloud disktype=ssdroot@master30:~# kubectl get node -L disktypeNAME STATUS ROLES AGE VERSION DISKTYPE master30.laoma.cloud Ready control-plane 11d v1.30.2 worker31.laoma.cloud Ready<none>11d v1.30.2 ssd worker32.laoma.cloud Ready<none>11d v1.30.2apiVersion:apps/v1kind:Deploymentmetadata:labels:app:webappname:webappspec:replicas:2selector:matchLabels:app:webapptemplate:metadata:labels:app:webappspec:nodeSelector:disktype:ssd# 只调度到带 disktype=ssd 的节点containers:-image:hub.laoma.cloud/library/nginxname:nginxroot@master30:~# kubectl apply -f deploy-with-nodeSelector.yamlroot@master30:~# kubectl get pods -o wide --no-headers | awk '{print $1,$3,$7}'webapp-7d675f9d9c-rz9hx Running worker31.laoma.cloud webapp-7d675f9d9c-zrj97 Running worker31.laoma.cloud注意:删除节点标签不会触发 Pod 重新调度(亲和性只在调度那一刻生效)。只有修改 Deployment 的 nodeSelector 才会触发滚动重建。
四、nodeAffinity:比 nodeSelector 强大得多
节点亲和性让你根据节点标签约束 Pod 调度位置,有两种模式:
requiredDuringSchedulingIgnoredDuringExecution:硬规则,必须满足,不满足就 PendingpreferredDuringSchedulingIgnoredDuringExecution:软规则,优先满足,不满足也能调度
apiVersion:apps/v1kind:Deploymentmetadata:labels:app:webname:webspec:replicas:2selector:matchLabels:app:webtemplate:metadata:labels:app:webspec:affinity:nodeAffinity:# 硬规则:必须调度到 CPU=L1 或 CPU=L2 的节点requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:-matchExpressions:-key:CPUoperator:Invalues:-L1-L2# 软规则:优先选 MEM=L1 或 MEM=L2 的节点preferredDuringSchedulingIgnoredDuringExecution:-weight:1preference:matchExpressions:-key:MEMoperator:Invalues:-L1-L2containers:-image:hub.laoma.cloud/library/nginxname:nginxoperator 支持:In、NotIn、Exists、DoesNotExist、Gt、Lt
权重规则:
weight范围 1-100- 调度器给每个满足硬规则的节点打分,把所有匹配的软规则 weight 累加,总分最高的节点优先
- 如果同时指定了
nodeSelector和nodeAffinity,两者都必须满足
实验验证:
# 节点未打 CPU 标签 → Pod Pendingroot@master30:~# kubectl apply -f deploy-with-nodeAffinity.yamlroot@master30:~# kubectl get podsNAME READY STATUS RESTARTS AGE web-854cd4986-598lb0/1 Pending02s web-854cd4986-mp2660/1 Pending02s# 给两个 worker 都打 CPU 标签 → 均衡调度root@master30:~# kubectl label nodes worker31.laoma.cloud CPU=L1root@master30:~# kubectl label nodes worker32.laoma.cloud CPU=L2root@master30:~# kubectl rollout restart deployment webroot@master30:~# kubectl get pods -o wide --no-headers | awk '{print $1,$3,$7}'web-85c8ffff8-q2j7r Running worker31.laoma.cloud web-85c8ffff8-rkq7s Running worker32.laoma.cloud# 给 worker32 加 MEM=L2 软偏好标签 → 都调度到 worker32root@master30:~# kubectl label nodes worker32.laoma.cloud MEM=L2root@master30:~# kubectl rollout restart deployment webroot@master30:~# kubectl get pods -o wide --no-headers | awk '{print $1,$3,$7}'web-6c45779498-glxbg Running worker32.laoma.cloud web-6c45779498-sn996 Running worker32.laoma.cloud注意:如果你修改或删除了 Pod 所调度到的节点标签,Pod 不会被删除。亲和性选择只在 Pod 调度期间有效。
五、Pod 间亲和性与反亲和性
前面都是"Pod 选节点",这一节是"Pod 选其他 Pod 的邻居"——基于已经在节点上运行的Pod 标签来约束调度位置。
- 亲和性(podAffinity):希望 Pod A 和 Pod B 在同一个拓扑域
- 反亲和性(podAntiAffinity):不希望 Pod A 和 Pod B 在同一个拓扑域
topologyKey表示拓扑域,可以是节点、机架、可用区等(用节点标签的 key 表示)。
⚠️ Pod 间亲和性会消耗大量计算资源,不建议在超过数百个节点的集群中使用。
5.1 示例:亲和性调度(同拓扑域)
把两个 worker 节点都打上topology.kubernetes.io/zone=v,表示它们属于同一个可用区。然后 web2 的 Pod 亲和性要求"跟 web1 在同一个 zone":
---apiVersion:apps/v1kind:Deploymentmetadata:name:web1spec:selector:matchLabels:app:web1replicas:1template:metadata:labels:app:web1spec:nodeSelector:topology.kubernetes.io/zone:vcontainers:-name:web-serverimage:hub.laoma.cloud/library/nginx---apiVersion:apps/v1kind:Deploymentmetadata:name:web2spec:selector:matchLabels:app:web2replicas:1template:metadata:labels:app:web2spec:affinity:podAffinity:requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchExpressions:-key:appoperator:Invalues:-web1topologyKey:topology.kubernetes.io/zonecontainers:-name:web-serverimage:hub.laoma.cloud/library/nginxroot@master30:~# kubectl apply -f deploy-with-podaffinity.yamlroot@master30:~# kubectl get pods -o wide --no-headers | awk '{print $1,$3,$7}'web1-849b694c75-t2spf Running worker31.laoma.cloud web2-84fdf8f999-vn5l9 Running worker32.laoma.cloud# 两个节点都属于 zone=v,所以 web2 可以调度到 worker32# 删除 worker32 的 zone 标签 → web2 只能调度到 worker31(跟 web1 同 zone)root@master30:~# kubectl label nodes worker32.laoma.cloud topology.kubernetes.io/zone-root@master30:~# kubectl rollout restart deployment web2root@master30:~# kubectl get pods -o wide --no-headers | awk '{print $1,$3,$7}'web1-849b694c75-t2spf Running worker31.laoma.cloud web2-7947994f89-pwnkv Running worker31.laoma.cloud
结论:Pod 亲和性必须满足两个条件:
- 节点必须有相应的 topologyKey 标签
- 在有相应标签的节点上,至少运行一个匹配 labelSelector 的 Pod
5.2 示例:反亲和性调度(打散副本)
把同一服务的多个副本分散到不同节点上,提高高可用性。topologyKey: kubernetes.io/hostname表示以单个节点为拓扑域——每个节点最多跑一个副本:
apiVersion:apps/v1kind:Deploymentmetadata:name:storespec:selector:matchLabels:app:storereplicas:3template:metadata:labels:app:storespec:affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchExpressions:-key:appoperator:Invalues:-storetopologyKey:"kubernetes.io/hostname"containers:-name:web-serverimage:hub.laoma.cloud/library/nginxroot@master30:~# kubectl get pods -o wide --no-headers | awk '{print $1,$3,$7}'store-5596cf4c84-9z5ch Pending<none>store-5596cf4c84-dswkq Running worker32.laoma.cloud store-5596cf4c84-ww2l4 Running worker31.laoma.cloud# 3 个副本只有 2 个 worker 节点,第 3 个 Pending5.3 多亲和+反亲和组合
spec:affinity:podAntiAffinity:# 不要跟自己的副本同节点requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchExpressions:-key:appoperator:Invalues:-web-storetopologyKey:"kubernetes.io/hostname"podAffinity:# 必须跟 app=store 的 Pod 同节点requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchExpressions:-key:appoperator:Invalues:-storetopologyKey:"kubernetes.io/hostname"

六、Taint 和 Toleration:节点选 Pod
前面都是"Pod 挑节点",污点是反过来——节点主动拒绝 Pod。
- Taint(污点):Node 上打标记,不匹配的 Pod 不能上来
- Toleration(容忍度):Pod 上声明能容忍哪些污点
nodeAffinity vs Taint 区别:nodeAffinity 是 Pod 选 Node(我想去哪),Taint 是 Node 选 Pod(我不要谁)。就像相亲——女选男是亲和性,男选女是污点。
6.1 设置节点污点
# 给 worker31 加污点:CPU=L1,效果 NoScheduleroot@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule# 移除污点root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU:NoSchedule-6.2 effect 三种效果
| effect | 作用 | 对已运行 Pod |
|---|---|---|
NoSchedule | 新 Pod 不调度上来 | 不影响已运行的 |
PreferNoSchedule | 尽量不调度上来,不强制 | 不影响已运行的 |
NoExecute | 新 Pod 不调度,已运行的也会被驱逐 | 容忍的可留,不容忍的马上驱逐 |
6.3 Pod 容忍度
tolerations:-key:"CPU"operator:"Equal"# Equal(默认)= 键值都匹配;Exists = 只要 key 存在就匹配value:"L1"effect:"NoSchedule"# 给两个 worker 打不同污点root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoScheduleroot@master30:~# kubectl taint nodes worker32.laoma.cloud CPU=L2:NoSchedule
# Pod 容忍 CPU=L1 → 只能调度到 worker31spec:tolerations:-key:"CPU"operator:"Equal"value:"L1"effect:"NoSchedule"root@master30:~# kubectl apply -f deploy-with-tolerations.yamlroot@master30:~# kubectl get pod -o wideNAME READY STATUS RESTARTS AGE IP NODE web-7c99d767-w9rzt1/1 Running05s10.224.162.142 worker31.laoma.cloud web-7c99d767-wwjhk1/1 Running05s10.224.19.13 worker31.laoma.cloud6.4 多污点多容忍度匹配
节点可以有多个污点,Pod 可以有多个容忍度。匹配逻辑像过滤器:从节点所有污点开始,过滤掉 Pod 能容忍的,剩下不能容忍的 effect 决定结果:
- 有未过滤的
NoSchedule→ 不调度 - 只有未过滤的
PreferNoSchedule→ 尽量不调度 - 有未过滤的
NoExecute→ 不调度或驱逐
6.5 内置污点
节点控制器自动给异常节点加污点:
| 污点 | 含义 |
|---|---|
node.kubernetes.io/not-ready | 节点未就绪 |
node.kubernetes.io/unreachable | 节点不可达 |
node.kubernetes.io/memory-pressure | 内存压力 |
node.kubernetes.io/disk-pressure | 磁盘压力 |
node.kubernetes.io/network-unavailable | 网络不可用 |
node.kubernetes.io/unschedulable | 不可调度 |
K8s 默认给所有 Pod 自动加了
not-ready和unreachable的容忍度,tolerationSeconds=300(5 分钟)。也就是说节点挂了,Pod 还会在原地待 5 分钟才被驱逐。
6.6 典型用例
- 专用节点:
dedicated=groupName:NoSchedule,只有带对应容忍度的 Pod 才能上来 - GPU 节点:
special=true:NoSchedule,不需要 GPU 的 Pod 不要来占用资源
七、cordon / uncordon / drain:节点维护
7.1 cordon:标记不可调度
root@master30:~# kubectl cordon worker31.laoma.cloudroot@master30:~# kubectl get nodesNAME STATUS ROLES AGE master30.laoma.cloud Ready control-plane 9d worker31.laoma.cloud Ready,SchedulingDisabled<none>9d worker32.laoma.cloud Ready<none>9d# 新 Pod 不会再调度到 worker31,但已有 Pod 继续运行root@master30:~# kubectl create deployment web --image=nginx --replicas=2root@master30:~# kubectl get pod -o wideNAME READY STATUS NODE web-59b9bb7664-flhvx1/1 Running worker32.laoma.cloud web-59b9bb7664-ss5861/1 Running worker32.laoma.cloud# 恢复可调度root@master30:~# kubectl uncordon worker31.laoma.cloud7.2 drain:安全驱逐节点上所有 Pod
节点维护(内核升级、硬件更换)之前,先用 drain 把 Pod 安全迁走:
# 默认不驱逐 DaemonSet 管理的 Pod(calico-node、kube-proxy 等)root@master30:~# kubectl drain worker31.laoma.clouderror: cannot delete DaemonSet-managed Pods: kube-system/calico-node-jwdzx, kube-system/kube-proxy-98qkl# 加 --ignore-daemonsets 忽略 DaemonSetroot@master30:~# kubectl drain worker31.laoma.cloud --ignore-daemonsetsnode/worker31.laoma.cloud already cordoned WARNING: ignoring DaemonSet-managed Pods: kube-system/calico-node-jwdzx, kube-system/kube-proxy-98qkl evicting pod laoma/web-59b9bb7664-jkb5q evicting pod laoma/web-59b9bb7664-w6mbs pod/web-59b9bb7664-w6mbs evicted node/worker31.laoma.cloud evicted# 维护完后恢复root@master30:~# kubectl uncordon worker31.laoma.clouddrain = cordon + 驱逐 Pod,一步到位。并行 drain 多个节点时会自动遵守 PodDisruptionBudget,不会一次性把所有副本都赶走。
八、总结
| 手段 | 一句话记忆 |
|---|---|
| nodeName | 写死节点名,最简单最死板 |
| nodeSelector | 节点打标签,Pod 选标签,最常用 |
| nodeAffinity | 标签匹配 + 硬/软规则 + 权重,比 nodeSelector 灵活 |
| podAffinity | 我要跟某个 Pod 待在同一个地方 |
| podAntiAffinity | 我不要跟自己的副本待在同一个节点 |
| Taint | 节点说"我不要谁",Pod 说"我能忍" |
| cordon | 停止接新活,老活继续干 |
| drain | 停止接新活 + 老活全部迁走 |
环境清理:
kubectl delete ns scheduler