ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Pod 到底跑在哪个节点?K8s 调度全解:从 nodeSelector 到亲和性、污点容忍

Pod 到底跑在哪个节点?K8s 调度全解:从 nodeSelector 到亲和性、污点容忍

K8s Pod 调度全解:nodeName、nodeSelector、亲和性、污点容忍到 cordon/drain

学习参考:将 Pod 指派给节点

默认情况下,K8s 调度器会自动把 Pod 分散到资源充足的节点上。但生产环境经常有特殊需求:把有大量磁盘 I/O 的 Pod 放到 SSD 节点、把 GPU 任务放到带 GPU 的节点、把同一服务的副本打散到不同节点做高可用。本文把 K8s 控制 Pod 运行位置的5 种手段全 部过一遍,从最简单的 nodeName 到最灵活的亲和性/反亲和性,再到污点容忍和节点维护操作。


一、五种调度约束手段总览

手段方向灵活度适用场景
nodeNamePod → 指定节点最死板,直接写死节点名临时调试,生产不推荐
nodeSelectorPod → 选带标签的节点简单等值匹配最常用的基础选择
nodeAffinityPod → 选节点标签支持 In/NotIn/Exists/Gt/Lt、硬/软策略需要复杂规则的场景
podAffinity/podAntiAffinityPod → 跟其他 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:nginx
root@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.2
apiVersion: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:nginx
root@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:硬规则,必须满足,不满足就 Pending
  • preferredDuringSchedulingIgnoredDuringExecution:软规则,优先满足,不满足也能调度
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:nginx

operator 支持: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/nginx
root@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 亲和性调度结果](https://aka.doubaocdn.com/s/K8RtVWYCtj)

结论:Pod 亲和性必须满足两个条件:

  1. 节点必须有相应的 topologyKey 标签
  2. 在有相应标签的节点上,至少运行一个匹配 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/nginx
root@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 个 Pending

5.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"

![多亲和反亲和调度结果1](https://aka.doubaocdn.com/s/679TQtMVPu)
![多亲和反亲和调度结果2](https://aka.doubaocdn.com/s/pM36BYZ1U4)


六、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

![设置节点污点](https://aka.doubaocdn.com/s/bb0S2BQaS4)

# 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.cloud

6.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.cloud

7.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.cloud

drain = cordon + 驱逐 Pod,一步到位。并行 drain 多个节点时会自动遵守 PodDisruptionBudget,不会一次性把所有副本都赶走。


八、总结

手段一句话记忆
nodeName写死节点名,最简单最死板
nodeSelector节点打标签,Pod 选标签,最常用
nodeAffinity标签匹配 + 硬/软规则 + 权重,比 nodeSelector 灵活
podAffinity我要跟某个 Pod 待在同一个地方
podAntiAffinity我不要跟自己的副本待在同一个节点
Taint节点说"我不要谁",Pod 说"我能忍"
cordon停止接新活,老活继续干
drain停止接新活 + 老活全部迁走

环境清理:kubectl delete ns scheduler

返回列表