Kubernetes 集群里跑了一批工作负载后,会冒出新的问题:Pod 到底会被调度到哪台节点?为什么有的节点吃满、有的闲置?想让"日志收集只跑在特定机器""GPU 任务只去有 GPU 的节点"怎么办?这些都由 K8s 的调度(Scheduling)机制决定:scheduler 负责给每个新 Pod 找"合适的节点",而"合不合适"由一组策略表达——亲和/反亲和(affinity/anti-affinity)、污点与容忍(taint/toleration)、节点选择(nodeSelector)、资源请求与限制(requests/limits)。这篇讲清调度解决什么、几个核心概念怎么用、常见调度场景怎么配,以及常见坑与边界。
一、K8s 调度解决什么:让每个 Pod 落到"合适"的节点,而不是随便一台
K8s 的默认调度是"按资源打分、找最不挤的节点",它能保证基本均衡,但满足不了很多真实需求:日志采集器希望跟应用 Pod 同节点跑(节省跨节点网络);数据库不希望和重 CPU 任务挤一台;某些工作负载只在特定节点(有 GPU/SSD/特定内核)上能跑;节点维护时要先把上面的 Pod 迁走。调度机制就是回答"这个 Pod 应该去哪台节点"的一整套规则与工具,让你能把"集群资源被合理使用 + 特殊需求被满足 + 运维动作可预期"三者兼顾。
它和"编排"(Deployment 管副本数、滚动)与"资源管理"(limits 限制用量)是协同的:调度决定"放哪",资源限制决定"能占多少",编排决定"跑几个、怎么换"。排障时若发现"某 Pod 一直 Pending",通常就是调度层的问题——先查调度策略与节点标签,再查资源与污点。
二、核心概念:nodeSelector / 亲和反亲和 / 污点容忍 一次理清
调度能力分三层,从简单到精细。第一层 nodeSelector:最简单——声明"这个 Pod 只去带某 label 的节点",比如 `disktype: ssd`。第二层 nodeAffinity / podAffinity / podAntiAffinity:更精细——支持按表达式匹配节点标签(preferred 软要求 / required 硬要求)、或让"有某 Pod 的节点/与某 Pod 同节点/不与某 Pod 同节点"来约束调度(如"数据库不要和应用同一节点"用反亲和)。第三层 taint / toleration:节点打污点(如"只有带 toleration 的 Pod 才能来"),用于把节点"预留"给特定工作负载(GPU 节点、管理节点)。这三层可以组合,表达力从"标签匹配"到"关系约束"到"节点主权"。
# nodeSelector:只调度到带 disktype=ssd 的节点
spec:
nodeSelector:
disktype: ssd
# nodeAffinity(硬要求 + 软偏好):优先/必须去带 gpu=tesla 标签的节点
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- { key: gpu, operator: In, values: ["tesla"] }
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 10
preference:
matchExpressions:
- { key: rack, operator: In, values: ["r1"] }
# 节点污点:kubectl taint nodes node-a dedicated=gpu:NoSchedule
# Pod 容忍:
spec:
tolerations:
- { key: dedicated, operator: Equal, value: gpu, effect: NoSchedule }
容易混的是"软/硬":required 是"找不到就不调度(Pending)",preferred 是"尽量但找不到也调度"。另一个常踩的坑是"污点 + 容忍的 effect":NoSchedule 只拦新调度、NoExecute 会把已在运行的也驱逐——后者要谨慎用,别把节点上的 Pod 全踢了。先把这三层各自想清楚,再组合时才知道每条规则在约束什么。
三、常见调度场景:专机专用、数据局部性、隔离与维护
自托管/K8s 里最常遇到的四个场景,正好对应上面四类手段。一是"专机专用"(GPU/日志节点):给节点打标签或污点,把特殊负载"钉"过去,普通负载不许来。二是"数据局部性"(Pod 尽量与它依赖的数据/服务同节点):用 podAffinity 让"读同一份数据/互相高频通信的 Pod"尽量调度到一起,减少跨节点流量。三是"隔离"(互不干扰):用 podAntiAffinity 让关键服务(数据库、监控)彼此不挤同一节点,避免一个节点故障带走多个关键服务。四是"节点维护":给节点打 NoSchedule 污点(或 cordon),让它不再接新 Pod,再排空已有 Pod 后维护,避免维护时被新调度打乱。
# 反亲和:让两个 app=api 副本尽量不落同一节点(提高容错)
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels: { app: api }
topologyKey: kubernetes.io/hostname
# 节点维护流程:cordon(不再调度)→ drain(排空)→ 维护 → uncordon
# kubectl cordon node-b && kubectl drain node-b --ignore-daemonsets
# # 维护完成后:kubectl uncordon node-b
这些场景的坑在于"约束写多了反而没节点可去":亲和/反亲和/污点叠多了,调度器找遍集群都没满足的节点,Pod 就无限 Pending。原则是"先用 required/硬约束表达硬性刚需,其余用 preferred/软偏好表达倾向",别把所有偏好都做成硬性要求,否则集群越配越死。
四、常见坑:Pending、资源与调度的交互、标签/污点改错、拓扑键
调度层排障,四个坑要尤其注意。一是"Pod 一直 Pending":原因通常是"没有节点满足调度约束(nodeSelector/亲和/污点)、或资源不足(requests 加不起来)"——用 `kubectl describe pod` 看 Events 里的调度失败原因,90% 能定位。二是"资源与调度交互":节点资源按 requests 计,limits 只约束运行时不超,别以为"limits 设得大就有资源"——调度看的是 requests。三是"标签/污点改错":改错节点标签或误加 NoExecute 污点,可能导致 Pod 被驱逐、调度混乱——改动前先看清拓扑键(topologyKey,如 kubernetes.io/hostname/zone)与 effect。四是"补丁式排障":频繁手工 `kubectl delete pod` 让它重调度,等于掩盖调度策略问题——把"为什么不合适"查清楚才是根除。
# 调度失败排查三步:
# 1) kubectl get pod -n # 看状态是否 Pending
# 2) kubectl describe pod -n # Events 里找调度失败原因
# 3) kubectl get nodes --show-labels # 核对标签/污点/资源是否满足
# 常见报错:0/N nodes available: ... Unschedulable / Insufficient cpu/memory
把"describe 看 Events + 核对节点标签与资源"当成调度排障的第一反应,而不是瞎删 Pod。调度问题多数有明确原因——没满足约束、资源不够、污点挡住——看 Events 基本一眼到底,剩下的就是调整策略或资源。
五、边界:调度管"放哪",管不了"跑得好不好"与"数据放哪"
调度的边界要认清:它负责"Pod 初始该放哪台节点",但不负责"运行中是否健康、要不要重新调度"(那是控制器与探针的事),也不替代"资源规划"——requests/limits 与节点容量规划仍要你设计,调度只是执行分配。其次,调度策略解决的是"位置约束",解决不了"数据/存储去哪"——有状态应用的数据卷(PV)与节点绑定(local PV)要考虑与调度的配合,避免"Pod 被调度走了数据却留在原地"。最后,调度规则越多越细,排障与维护成本越高,够用即可,别把每个负载都配满一套策略。
把调度放回自托管整体:它是 K8s 把"合适的工作负载放到合适的节点"、并支撑专机专用/隔离/维护等运维动作的能力层。落地要点:nodeSelector 做简单标签匹配、亲和/反亲和做关系约束(硬需求用 required、倾向用 preferred)、污点/容忍做节点主权与预留;对日志局部性、关键服务隔离、节点维护等场景组合使用;防住无限 Pending、资源按 requests 计、标签/污点改错、补丁式排障四坑;认清它管"初始放哪"、不管"跑得好不好"与"数据卷绑定"。当调度策略把"该去哪"与"不该去哪"表达清楚,集群才能既用得好资源、又稳得住关键服务。
结语
Kubernetes 调度与亲和性实战的价值,是把"Pod 落到哪台节点"从默认的"找个不挤的"升级为"按标签、关系、污点与资源约束精准安排"。落地要点:nodeSelector 管简单标签、nodeAffinity/podAffinity/podAntiAffinity 管"与节点/与 Pod 的关系"(硬需求 required、软倾向 preferred)、taint/toleration 管节点预留与主权;对专机专用、数据局部性、关键服务隔离、节点维护四类场景组合运用;防住无限 Pending、调度看 requests 而非 limits、标签污点改错、补丁式排障四坑,并认清它管"初始放置"、不管"运行健康"与"数据卷绑定"。当你把"该去哪、不该去哪"用调度策略表达清楚并养成"看 Events 排障"的习惯,K8s 集群才谈得上既高效又稳。



