4. Scheduler 调度模型:调度队列、调度算法、预选与优选、绑定,CPU 密集型与内存缓存分析
Kubernetes Scheduler 是控制面中负责将 Pod 绑定到合适 Node 的核心组件。其调度过程并非一次性的随机选择,而是一个包含调度队列管理、调度算法循环(预选 & 优选)以及绑定的精密流水线。本章深入剖析该模型的内部机制,并重点分析其 CPU 密集型特征与内存缓存策略,为容量规划提供理论依据。
4.1 调度整体流程与核心组件
调度器的主循环通常被称为 Schedule One,每次为一个待调度的 Pod 执行完整的调度周期。该周期可抽象为以下四个阶段:
- 调度队列(Scheduling Queue):管理待调度的 Pod,支持优先级排序与重试。
- 预选(Predicates / Filtering):过滤掉不满足 Pod 运行要求的 Node。
- 优选(Priorities / Scoring):对通过预选的 Node 进行打分排序。
- 绑定(Binding):将 Pod 与最优 Node 的关联信息写入 API Server。
4.2 调度队列:优先级与公平性
调度队列是调度器的入口,负责接收、排序和缓存待调度的 Pod。Kubernetes 使用 PriorityQueue 实现,内部维护三个子队列:
| 队列名称 | 数据结构 | 作用 |
|---|---|---|
| activeQ | 堆(Heap) | 存放所有可立即调度的 Pod,按优先级排序。 |
| backoffQ | 堆(Heap) | 存放调度失败的 Pod,按重试时间排序,到期后移回 activeQ。 |
| unschedulableQ | Map | 存放因资源不足等不可调度原因失败的 Pod,当集群拓扑变化时重新评估。 |
4.3 预选与优选:调度算法的核心
调度算法循环是 CPU 消耗的主力。预选阶段需要遍历集群中所有 Node(默认开启 NodeResourcesFit、NodeName、PodToleratesNodeTaints 等内置 Predicates),优选阶段则对候选 Node 执行加权打分。
4.3.1 预选(Filtering)
预选阶段的核心是 并行化。调度器会启动多个 Goroutine 同时检查 Node,每个 Goroutine 执行一组 Predicates。常见的预选检查包括:
- NodeResourcesFit:检查 Node 剩余 CPU、内存是否满足 Pod Request。
- NodePorts:检查 Node 端口是否被占用。
- PodToleratesNodeTaints:检查 Pod 是否容忍 Node 的 Taints。
- MatchNodeSelector:检查 Node 标签是否匹配 Pod 的 nodeSelector。
4.3.2 优选(Scoring)
优选阶段对过滤后的 Node 进行打分,分数范围 0-100。调度器最终选择分数最高的 Node。常见的优选策略:
| 优选策略 | 权重 | 计算方式 |
|---|---|---|
| NodeResourcesBalancedAllocation | 1 | CPU 与内存使用率越均衡,分数越高。 |
| NodeResourcesLeastAllocated | 1 | 剩余资源越多,分数越高。 |
| ImageLocality | 1 | Node 上已有 Pod 所需镜像,分数越高。 |
| InterPodAffinity | 2 | 满足 Pod 亲和性/反亲和性规则。 |
4.4 绑定:异步写入与乐观锁
绑定阶段将调度结果写入 API Server。调度器通过 Binder 接口向 API Server 发送 POST /api/v1/namespaces/{ns}/pods/{name}/binding 请求。该操作是异步的,不会阻塞调度主循环。
// 绑定请求的简化示例
{
"apiVersion": "v1",
"kind": "Binding",
"metadata": {
"name": "my-pod",
"namespace": "default"
},
"target": {
"apiVersion": "v1",
"kind": "Node",
"name": "node-01"
}
}
绑定操作本身较轻量,但需要注意 乐观锁冲突:当多个调度器实例(或高并发场景下)同时绑定同一 Pod 时,API Server 会返回冲突错误,调度器需要重新入队并重试。
4.5 CPU 密集型与内存缓存分析
调度器的性能瓶颈主要集中在 CPU 和 内存 两个维度。理解其资源消耗模式是容量规划的前提。
4.5.1 CPU 密集型特征
调度器的 CPU 消耗主要来自:
- 预选阶段的 Node 遍历:每个 Pod 调度都需要遍历所有 Node(或缓存快照),执行 Filter 函数。
- 优选阶段的打分计算:对候选 Node 执行多个 Score 函数,涉及浮点运算与 Map 遍历。
- 队列操作:堆的 Push/Pop 操作在 Pod 数量大时产生 CPU 开销。
- Goroutine 调度:并行 Filter 和 Score 会创建大量 Goroutine,增加 Go 运行时调度压力。
4.5.2 内存缓存策略
为了减少对 API Server 的频繁调用,调度器维护了 Scheduler Cache,用于存储 Node 信息和 Pod 状态。该缓存是调度器性能优化的核心。
| 缓存组件 | 存储内容 | 更新机制 |
|---|---|---|
| NodeInfo | Node 的 CPU、内存、GPU 总量与已分配量 | 通过 Watch 事件增量更新 |
| PodInfo | 已调度 Pod 的 Request 与 Node 关联 | 通过 Watch 事件增量更新 |
| ImageCache | Node 上已存在的容器镜像列表 | 定期同步 + Watch |
--cache-size 参数调大(如 5000),避免缓存频繁驱逐导致性能抖动。同时,监控 scheduler_cache_size 指标,确保缓存命中率 > 99%。
4.6 容量规划关键指标
针对 Scheduler 的容量规划,需要重点监控以下指标:
| 指标名称 | 类型 | 建议阈值 | 说明 |
|---|---|---|---|
scheduler_e2e_scheduling_duration_seconds |
Histogram | P99 < 5s | 端到端调度延迟,超过 5s 说明调度器过载。 |
scheduler_pod_scheduling_duration_seconds |
Histogram | P99 < 2s | 单个 Pod 的调度算法耗时。 |
scheduler_queue_incoming_pods_total |
Counter | 无固定值 | 观察 Pod 入队速率,用于评估集群负载。 |
scheduler_cache_size |
Gauge | 稳定 < 缓存上限 | 缓存条目数,持续增长可能触发 LRU 驱逐。 |
process_cpu_seconds_total |
Counter | 单核利用率 < 80% | 调度器进程 CPU 使用率,超过 80% 需扩容。 |
--kube-api-qps=100 限制其对 API Server 的请求速率。
4.7 本章小结
Scheduler 的调度模型是一个精心设计的流水线:调度队列负责优先级管理,预选与优选负责 Node 筛选与打分,绑定负责结果持久化。其 CPU 密集型特征要求我们在容量规划时重点关注 调度延迟 和 缓存命中率。通过合理配置 PercentageOfNodesToScore、--cache-size 以及 CPU 资源限制,可以有效支撑大规模集群的调度性能。