调度、放置与存量重平衡
集群还有 40% CPU 和 35% 内存,新的订单 Pod 却全部 Pending。值班人员先扩节点,新增节点仍不接收 Pod;最终发现 Deployment 同时要求 zone-a、专用污点容忍和一个只能绑定 zone-b 的 PVC,三组硬约束交集为空。资源总量没有撒谎,但它回答的是“所有节点加起来还有多少”,调度器需要回答的是“是否存在一个节点同时满足这个 Pod 的全部条件”。
另一个团队为提高利用率运行 Descheduler,策略成功驱逐了几十个 Pod,调度器却只能把它们放回原来的节点。驱逐动作正常、PDB 没被违反,存量偏斜仍然没有变化,还平白制造了一轮连接抖动。新 Pod 的放置和旧 Pod 的重平衡是两件事:前者由调度器完成,后者必须先证明驱逐后存在更好的可行位置。
调度链先过滤,再比较候选节点
kube-scheduler 监听未绑定 Pod,经过 PreFilter、Filter、PostFilter、PreScore、Score、Reserve、Permit、PreBind、Bind 和 PostBind 等扩展点。Filter 只判断可行性,Score 才在候选节点中表达偏好;任何硬约束让候选集归零,调高评分权重都无效。
Pending Pod
-> PreFilter:准备共享状态
-> Filter:资源、污点、亲和、卷、端口、拓扑
-> Score:在可行节点中排序
-> Reserve / Permit:预留与等待
-> Bind:写入 spec.nodeNameScheduling Framework提供扩展点语义;排障时要把 Event 中的失败插件与 Pod request、标签、污点、PVC 和 PriorityClass 对齐,而不是只看最后一个报错字符串。
放置原语表达硬边界与软偏好
nodeSelector 和 required NodeAffinity 是硬约束,preferred NodeAffinity 是评分偏好;taint 默认拒绝 Pod,toleration 只允许进入,不保证一定选择该节点。PodAffinity/AntiAffinity 依赖已有 Pod 标签,Topology Spread 通过 maxSkew、topologyKey 与 whenUnsatisfiable表达故障域分布。
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: checkout三可用区高可用需要每个 zone 有可用容量。一个 zone 故障后继续坚持 DoNotSchedule 可能阻止恢复;改成 ScheduleAnyway 能降级运行,却降低分布保证。两种选择都要事先写入容量和故障策略,而不是事故中临时试字段。
Scheduler Profile 划分不同放置策略
KubeSchedulerConfiguration 可以定义多个 profile,每个 profile 具有 schedulerName、plugins 和 pluginConfig;Pod 通过 spec.schedulerName 选择。profile 适合在同一进程、共享队列和 HA 模型下提供不同评分策略,例如在线服务倾向分散、批任务倾向装箱。
kubectl get pod -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,SCHED:.spec.schedulerName,NODE:.spec.nodeName'
kubectl get --raw /configz 2>/dev/null | jq '.kubeSchedulerConfiguration.profiles' || true
kubectl get event -A --sort-by=.lastTimestamp | grep FailedScheduling所有 profile 的 QueueSort 配置必须一致。引入独立调度器会增加 ServiceAccount、leader election、日志、升级和故障回退面;未指定或不存在的 schedulerName 会让 Pod 永久 Pending。因此上线前保留默认 profile,并用隔离 workload 验证。Scheduler 配置给出了稳定 API 与字段。
Priority 和抢占分配稀缺容量
PriorityClass 影响调度排队和抢占资格。高优先级 Pod 找不到节点时,调度器可以选择较低优先级受害者;PDB 在抢占中是 best effort,不是绝对保护。preemptionPolicy: Never 只禁止该 Pod 抢占别人,不能防止它被更高优先级 Pod 抢占。
高优先级是集群级稀缺权力,要通过 ResourceQuota 或准入策略限制使用者、命名和数值。反向实验创建一个错误的超高优先级工作负载,观察被驱逐 Pod、业务错误率和恢复时间,再验证准入策略能拒绝未授权 PriorityClass。没有这层保护,任何租户都可能把自己的容量问题转嫁给别人。
异构节点把设备与拓扑带进调度
GPU、FPGA、RDMA 和其他加速器通常通过 Device Plugin 或 DRA 暴露。设备数量只是第一层事实;驱动版本、MIG/共享模式、NUMA、PCIe、网络拓扑、本地盘、镜像和许可证都会决定任务能否运行。节点标签与污点用于缩小候选集,也容易制造碎片。
GPU Pod Pending 时同时检查扩展资源 request、设备插件 DaemonSet、Node allocatable、taint、Affinity、拓扑和节点供给器候选规格。一个 4-GPU 任务不能被四台各剩 1 张卡的节点满足;“集群还有 4 张 GPU”仍不是可调度证据。
Descheduler 只驱逐,不负责重新放置
Descheduler 根据策略识别不再合适的存量 Pod,并调用 eviction API。驱逐后由 Deployment、StatefulSet 等控制器补齐,kube-scheduler 再做放置。策略必须从 dry-run 开始,设置 namespace、label、priority、PDB、local storage、PVC 和系统关键 Pod 保护,并限制每节点、每命名空间和总驱逐量。
kubectl get pod -A -o wide > placement-before.txt
kubectl get pdb -A -o wide
# 运行版本固定的 dry-run 配置并保存日志
kubectl get pod -A -o wide > placement-after.txt
diff -u placement-before.txt placement-after.txt || true成功标准不是“Evicted 30”,而是偏斜下降、目标 Pod 全部 Ready、错误率在预算内且没有新的 Pending。策略回滚要停止 Job/CronJob/Deployment、撤销 Policy、确认没有进行中驱逐,再评估是否恢复原放置。
调度可观测从 Event 延伸到插件状态
kubectl describe pod 和 FailedScheduling 是入口,生产还要采集 scheduler 日志、调度尝试、端到端延迟、插件耗时、Pending 时长、抢占、permit 等待和 API 错误。Event 有保留期和聚合行为,不能作为长期唯一证据。
排障按四类分型:资源与设备不足、硬约束交集为空、优先级与抢占失败、扩展插件或外部依赖故障。每类都要有停止实验、回滚配置和 owner。调度器配置、节点标签和 PriorityClass 属于高影响面变更,应走版本化、差异审查和小流量验证。
安全、成本与治理共同决定放置策略
节点标签可能表达租户、安全域和硬件信息,修改权限必须限制;自定义调度器与 Descheduler 使用专用 ServiceAccount,不能获得无边界的 Pod 删除权。日志中的节点名、租户标签和工作负载拓扑可能是敏感容量信息,导出前要脱敏。
高可用分散会增加冗余,装箱会放大节点故障影响,专用节点减少干扰但产生空闲,Descheduler 降低偏斜却制造中断。架构评审必须同时记录故障域目标、候选集大小、碎片率、驱逐预算、调度 P95 和费用。只有新放置、重平衡、故障降级与退出都能用证据解释,调度策略才算可长期维护。
