Kubernetes 调度器为每个待调度的 Pod 执行两段式决策:预选(Predicates,过滤掉不满足硬性条件的节点)与优选(Priorities,给幸存节点打分排序),最终把得分最高者写进 Pod 的 nodeName 字段完成绑定。选址失败则 Pod 停留 Pending 并持续重试。
资源报价单已就位,这一节看调度器怎么花掉它。上一节的实验里 Pod 卡在 Pending,本节把决策过程完整展开:哪些条件会一票否决、打分偏好什么、以及 affinity 与 taint 两类精细控制怎么用。学完这节,Pending 不再是黑箱,而是一条可以逐项核对的证据链。
预选阶段逐项检查硬条件,任何一项不过即淘汰该节点。常见否决项按检查顺序:
优选阶段只对幸存者打分,常见打分项:资源均衡度(倾向选分配后剩余比例更均衡的节点,避免堆叠)、镜像本地缓存(已有所需镜像的节点加分,启动更快)、反亲和命中(副本尽量散开)、拓扑打散。两段式的设计逻辑一句话:预选保证正确性,优选优化质量——先淘汰所有不可行解,再在可行解里挑更优解。
调度器自己不执行任何操作,只往账本写一个字段:
# 看调度器为 Pod 写回的绑定结论 kubectl get pod orders-api-6d9f7c8b5-k8w2t -n production -o jsonpath='{.spec.nodeName}' # node-c # 绑定事件也记录在案 kubectl describe pod orders-api-6d9f7c8b5-k8w2t -n production | grep -A1 Scheduled # Events: # Normal Scheduled 8m default-scheduler # Successfully assigned production/orders-api-... to node-c
nodeSelector 是最朴素的定向手段:给节点打标签,Pod 声明"只去带某标签的节点"。它只有硬性匹配一种语义,而亲和性(affinity)提供软硬两档,还支持按"和其他 Pod 的关系"选址:
spec: template: spec: affinity: podAntiAffinity: # 反亲和:副本之间互相排斥 preferredDuringSchedulingIgnoredDuringExecution: # 软性:尽量满足 - weight: 100 podAffinityTerm: topologyKey: kubernetes.io/hostname # 以节点为拓扑域 labelSelector: matchLabels: app: orders-api # 条件:同应用的其他副本 nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node-type # 硬性:必须落在计算型节点 operator: In values: ["compute"]
这份声明的意图是:orders-api 的 3 个副本尽量分散在不同节点(软性,权重 100),且必须落在计算型节点(硬性)。调度时段(DuringScheduling)生效、运行时段(IgnoredDuringExecution)不强行迁移——软性偏好不满足也照常调度,硬性要求不满足则宁 Pending 不将就。名字很拗口,但语义就这两句。
亲和性是 Pod 挑节点,污点(taint)反其道而行:节点给 Pod 设门槛。控制平面节点就是靠污点保持清静的:
# 看控制平面节点自带的污点 kubectl describe node control-1 | grep Taints # Taints: node-role.kubernetes.io/control-plane:NoSchedule # 语义:不容忍此污点的 Pod 一律不许调度进来 # 常见系统污点(节点异常时自动打上): # node.kubernetes.io/not-ready 节点未就绪 # node.kubernetes.io/disk-pressure 磁盘压力 # node.kubernetes.io/memory-pressure 内存压力
Pod 想进门就得带对应的容忍(tolerations)。给一个可漂在大数据节点的日志伴随 workload 声明容忍:
spec: template: spec: tolerations: - key: "dedicated" operator: "Equal" value: "batch" # 精确匹配 dedicated=batch 的污点 effect: "NoSchedule" # 容忍其"不许调度"效果
污点三要素 key、value、effect 里,effect 决定后果:NoSchedule 拒新来者、NoExecute 连存量都驱逐、PreferNoSchedule 尽量别来。带 NoExecute 容忍还可以加 tolerationSeconds,表示"驱逐前宽限多久"——数据库类 Pod 常借此在网络抖动时争取缓冲。
背景:orders-api 扩到 5 副本后,运维发现副本全部集中在 node-a 与 node-b,node-c 始终空着,怀疑调度器坏了。
操作:按证据链排查——
# 第一步:看 node-c 是否带污点 kubectl describe node node-c | grep -A2 Taints # Taints: dedicated=batch:NoSchedule # 命中:node-c 被打上了批处理专用污点 # 第二步:看 Pod 是否声明了容忍(检查声明文件后确认没有) # 第三步:看副本分布现状 kubectl get pods -n production -l app=orders-api -o wide # 5 个副本全部落在 node-a 与 node-b
结果:调度器没有坏。node-c 某天被平台组打了批处理专用污点,orders-api 不带容忍,预选阶段直接出局,5 个副本只能在剩下两台里挤着。
解读:反常分布几乎总有一个可查的标签或污点。排查顺序固定:先 Taints 再 Affinity 再资源账面,三步之内必有答案。这里的选择是给 node-c 去污点(如果它本该通用),或给 orders-api 补容忍(如果它被允许借用批处理节点)。
变式:若诉求是"控制面留一台给系统组件、其余节点给业务",标准做法就是保留控制面的默认污点、给监控代理等系统级 DaemonSet 声明容忍——第4章的工作负载图谱会再次用到这个组合。
选址完成、nodeName 已写入账本。下一节跟进到节点现场:kubelet 如何认领任务,容器运行时如何把一行镜像声明变成运行中的进程。