12.1 资源调度的目标与抽象
一句话
调度器只做两件事:把 “资源” 抽象成可分配的单位(CPU/内存/端口/GPU 的向量),再为每个任务找一个放得下且不违背约束的位置;难点不在 “找”,而在 “同时满足利用率、公平、延迟、碎片” 这四个互相冲突的目标。
资源抽象:从机器到 “可分配向量”
| 抽象 | 内容 | 粒度问题 |
|---|---|---|
| 节点(Node) | 一台机器或一个虚拟机 | 可分配总量 = 容量 − 系统预留 |
| 可分配资源 | cpu、memory、pods、ephemeral-storage,扩展资源如 nvidia.com/gpu | CPU 以毫核(1/1000 核)计 |
| 请求(Request) | 调度依据:保证能拿到多少 | 设太小 → 超卖后被 OOM/限流 |
| 上限(Limit) | 运行时限制:最多能用多少 | 设太大 → 节点过载 |
| 约束(Constraint) | 亲和/反亲和、污点、拓扑 | 约束越多,可调度位置越少 |
关键区分:Request 决定 “能不能放下”,Limit 决定 “放进去后会不会炸”。二者混淆是线上事故的常见来源(见 7.9 资源限制与 QoS)。
四个互相冲突的目标
| 目标 | 含义 | 与其他目标的冲突 |
|---|---|---|
| 利用率 | 尽量少留空闲(装箱装箱问题) | 高了碎片化、低了浪费 |
| 公平 | 多租户按配额/权重分配 | 与 “优先给大任务” 冲突 |
| 延迟 | 任务尽快被调度(尤其是短任务) | 与 “全局最优装箱” 冲突 |
| 碎片 | 剩余资源尽量能拼成大块 | 与 “紧凑装箱” 冲突 |
学术上这是 多维装箱(multi-dimensional bin packing) 的变种,NP-hard;工程上用启发式(打分/贪心)+ 约束过滤,而不是求最优。
代码:资源请求与调度结果的关系
resources:
requests: {cpu: "500m", memory: "512Mi"} # 调度器按这个找节点
limits: {cpu: "2", memory: "1Gi"} # 运行时按这个限流/杀进程
# 看节点可分配量与已分配量(K8s 口径)
kubectl describe node <node> | sed -n '/Allocatable/,/Events/p'
kubectl get pod <pod> -o jsonpath='{.spec.containers[*].resources}'
判据:把 requests 设成 “真实 P95 用量”,而不是 “峰值”——设峰值会让集群利用率长期低于 30%;设成平均值则会在高峰被限流。
反模式
| 反模式 | 后果 |
|---|---|
| 不设 requests(只设 limits) | 调度器按 0 处理,节点超卖到崩溃 |
| requests = limits = 峰值 | 利用率低,成本翻倍 |
| 只按 CPU 调度,忽略内存/GPU | 内存 OOM、GPU 抢不到 |
| 约束写成 “必须同节点” | 大任务永远排不上 |
| 用节点标签做 “手工调度” 且无回收 | 标签漂移,节点长期闲置 |
| 不监控 “调度失败原因” | 队列堆积却不知道卡在哪种约束 |
三条铁律
🔑 Request 是 “承诺”,Limit 是 “上限”;二者语义不同,不能混设。
🔑 调度的目标是 “可接受的次优”,不是全局最优。
🔑 约束越多,可调度空间越小;每加一条约束都要问 “真的必要吗”。
自检
Q1 为什么 “不设 requests” 会让节点超卖到崩溃?
▸ 答案:因为调度器把未声明的 requests 当 0 处理:它可以往一个节点塞任意多的 Pod,直到运行时的 limits/物理容量 才真正受限。结果是多个 Pod 同时申请内存时互相挤,触发 OOM Kill。requests 是给调度器的 “占座声明”,没有它就没有 “不会超卖” 的保证。
Q2 利用率长期 30%,但业务没抱怨,要不要 “提高装箱率”?
▸ 答案:先分清是调度问题还是需求问题:如果 requests 设成了峰值(常见),那么 30% 是 “声明虚高”,应先按真实 P95 下调 requests;如果 requests 已合理而利用率仍低,说明负载本身波动大,需要弹性伸缩(见 5.4 弹性伸缩与自动扩缩容)而不是强行装箱。
Q3 多维装箱为什么不能用 “先到先得 + 第一个放得下” 的贪心?
▸ 答案:因为贪心会让大任务被碎片饿死:小任务把每个节点的零散资源占满,大任务(需要连续 8 核)永远找不到位置。工程上的缓解是 “按资源形状打分”(best-fit / least-allocated)与 “预留/优先级队列”,让大任务有优先装入的机会。
Q4 为什么调度延迟对短任务特别重要?
▸ 答案:因为短任务的价值在快速完成,排队时间占比高:一个 100ms 的任务等 5 秒,延迟放大了 50 倍。这也是 “批量调度 + 短任务优先” 存在的原因;但短任务优先又会导致长任务饥饿,需要配额/老化机制平衡。
Q5 节点的 “可分配量” 为什么不等于物理容量?
▸ 答案:因为要扣掉系统预留:kubelet/容器运行时、操作系统、system-reserved/kube-reserved,以及 eviction-hard 的驱逐阈值。把这部分也算进可分配,会让节点在真正紧张时先驱逐业务 Pod——预留是 “用一点容量换稳定” 的保险。
Q6 调度失败(Pending)最常见的原因有哪些?
▸ 答案:四类:资源不足(requests 太大或无节点满足)、约束不满足(亲和/污点/拓扑)、配额限制(namespace 的 ResourceQuota)、镜像/存储未就绪(PVC 绑不上)。排查顺序是 kubectl describe pod 的 Events → 资源与约束 → 配额,而不是先怀疑调度器有 bug。
Q7 为什么 “调度一次就固定” 往往不够?
▸ 答案:因为负载会变:节点故障、Pod 扩缩、热点倾斜都会让 “当初的最优” 变成 “现在的不均”。所以需要再平衡(descheduler)与弹性伸缩配合,但再平衡本身有成本(迁移、重启),必须限频并设置 “只在明显不均衡时触发”。
关联
- 前置:7.5 调度与亲和性
- 并列:12.2 单体调度 (Borg)、12.5 公平调度算法 (MMF / DRF)(待补)
- 下游:12.6 抢占、优先级与排队、5.4 弹性伸缩与自动扩缩容