2. 调度器架构与生产工作流

kube-scheduler 是 Kubernetes 控制平面中唯一负责“把 Pod 放到哪个 Node 上”的组件。它本身不创建容器,也不修改节点状态,而是通过监听 API Server 中处于 spec.nodeName 为空的 Pod,经过一轮“调度周期”计算出一个合适的节点,再向 API Server 写入一次 Binding(绑定)结果。理解它的架构,关键不是记住组件名字,而是看清一次调度从“待调度 Pod”到“绑定成功”之间,究竟经过了哪些阶段、哪些扩展点,以及生产环境中吞吐和延迟该从哪里观测。

核心组件与调度周期

kube-scheduler 内部可以拆成四类核心对象:Informer 缓存、调度队列、调度框架(Scheduling Framework)和调度缓存(Scheduler Cache)。Informer 持续从 API Server 同步 Pod、Node、PV、PVC、StorageClass 等对象,写入本地缓存,避免每次调度都直接访问 API Server;调度队列分为 activeQ、backoffQ 和 unschedulableQ,分别承载“可立即调度”“失败后等待重试”“暂时不可调度”的 Pod;调度框架把一次调度拆成若干扩展点,插件在这些扩展点上被调用;调度缓存则保存“已假设(assume)但尚未确认”的绑定结果,保证并发调度时不会把同一个节点的资源重复分配出去。

一次完整的调度周期分为两个阶段:调度阶段(Scheduling Cycle)和绑定阶段(Binding Cycle)。调度阶段是串行的,同一时刻只有一个 Pod 在“选节点”;绑定阶段可以异步并发执行。调度阶段内部又分为 PreFilter、Filter、PostFilter、PreScore、Score、NormalizeScore、Reserve、Permit 等扩展点;绑定阶段包含 PreBind、Bind、PostBind。其中 Filter 负责“排除不满足条件的节点”,Score 负责“给剩余节点打分排序”,Reserve 负责“预占资源”,Permit 负责“等待或拒绝”,Bind 负责“真正写入绑定关系”。

kube-scheduler 调度周期:调度阶段(串行) + 绑定阶段(可并发) API Server Pending Pod Node / PV / PVC 调度队列 activeQ backoffQ / unschedulableQ 调度阶段 Scheduling Cycle(串行) PreFilter 预检查/计算 Filter 排除不满足节点 PostFilter 抢占/兜底处理 PreScore 打分前准备 Score 节点打分排序 NormalizeScore 归一化分数 Reserve 预占资源 Permit 等待/拒绝 Scheduler Cache Assume 状态 绑定阶段 Binding Cycle(可并发) PreBind 卷绑定等准备 Bind 写入 Binding PostBind 清理/通知 选出最优节点 绑定成功 Pod.spec.nodeName 已写入 kubelet 开始接管 生产观测关键指标: scheduler_schedule_attempts_total(成功/失败) · scheduler_scheduling_attempt_duration_seconds(调度延迟分布) · scheduler_pending_pods(各队列积压) · scheduler_e2e_scheduling_duration_seconds(端到端延迟) · scheduler_plugin_execution_duration_seconds(插件耗时)

扩展点与插件机制

调度框架把“调度逻辑”从 kube-scheduler 主流程中解耦出来,插件只需实现自己关心的扩展点即可。生产中最常打交道的扩展点有:Filter(如 NodeResourcesFit、TaintToleration、NodeAffinity、InterPodAffinity)、Score(如 NodeResourcesBalancedAllocation、ImageLocality、PodTopologySpread)、PreFilter(如 InterPodAffinity 预计算亲和性匹配)、Reserve(如 VolumeBinding 预占卷)、Permit(如 Coscheduling 等待同组 Pod)。每个插件可以在多个扩展点注册,例如 VolumeBinding 同时参与 PreFilter、Filter、Reserve、PreBind。

理解扩展点的意义在于:当调度出现异常时,可以按扩展点顺序定位问题。例如 Pod 一直 Pending,先看 Filter 是否把所有节点都过滤掉了;如果 Filter 通过但绑定失败,则看 Reserve 或 PreBind 是否报错;如果调度延迟高,则看 Score 或 PreFilter 中是否有插件执行时间过长。

生产集群中的吞吐与延迟观测

kube-scheduler 暴露了丰富的 Prometheus 指标,生产环境应重点关注四类:吞吐类、延迟类、队列类和插件类。吞吐类指标 scheduler_schedule_attempts_total 按 result(scheduled、error、unschedulable)统计调度尝试次数,可以计算每秒调度成功数;延迟类指标 scheduler_scheduling_attempt_duration_seconds 是直方图,可计算 P50、P95、P99 调度延迟;队列类指标 scheduler_pending_pods 按 queue(activeQ、backoffQ、unschedulableQ)统计积压 Pod 数;插件类指标 scheduler_plugin_execution_duration_seconds 按 plugin 和 extension point 统计插件耗时,是定位“哪个插件拖慢了调度”的关键。

一个可落地的观测方法是:先看 scheduler_pending_pods{queue="activeQ"} 是否持续增长,判断调度吞吐是否跟不上 Pod 创建速度;再看 scheduler_scheduling_attempt_duration_seconds 的 P99 是否超过预期(例如生产集群通常要求 P99 在 100ms 以内,大规模集群可放宽到 500ms);如果延迟高,则用 scheduler_plugin_execution_duration_seconds 按 plugin 排序,找出耗时最高的插件;如果 scheduler_schedule_attempts_total{result="unschedulable"} 比例高,则说明大量 Pod 因资源或亲和性无法调度,需要检查节点资源水位和调度策略。

生产建议:为 kube-scheduler 配置独立的 Prometheus 抓取任务,并对 scheduler_scheduling_attempt_duration_seconds 的 P99 和 scheduler_pending_pods{queue="activeQ"} 设置告警阈值。同时开启 --v=4 或更高日志级别时要注意日志量,建议仅在排障时临时开启,避免日志 I/O 影响调度吞吐。

一次调度的关键判断路径

假设一个生产集群有 200 个节点,某 Deployment 扩容出 50 个 Pod,其中 10 个 Pod 持续 Pending。排障路径如下:首先查看 kubectl describe pod 的 Events,若出现 0/200 nodes are available: 200 Insufficient cpu,说明 Filter 阶段因 CPU 不足过滤了所有节点;若出现 node(s) had taint,说明 TaintToleration 插件过滤;若 Events 显示 failed to bind,则问题在绑定阶段。其次查看 scheduler_pending_pods,若 unschedulableQ 积压,说明这些 Pod 被判定为暂时不可调度,调度器会按 backoff 策略重试。最后查看 scheduler_plugin_execution_duration_seconds,确认是否某个插件(如 InterPodAffinity)在 200 节点规模下耗时过高,导致整体调度延迟上升。

需要强调的是,kube-scheduler 的调度周期是串行的,这意味着单次调度延迟会直接影响整体吞吐。在 200 节点、50 个待调度 Pod 的场景下,如果单次调度 P99 为 200ms,理论上每秒最多调度 5 个 Pod,50 个 Pod 需要约 10 秒完成;如果 P99 上升到 1s,则需要约 50 秒。因此,生产集群中调度延迟的观测和优化,直接决定了扩容、滚动更新和故障恢复的速度。

观测维度 关键指标 典型判断
吞吐 scheduler_schedule_attempts_total result="scheduled" 速率是否匹配 Pod 创建速率
延迟 scheduler_scheduling_attempt_duration_seconds P99 是否超过 100ms(小集群)或 500ms(大集群)
队列积压 scheduler_pending_pods activeQ 持续增长说明调度吞吐不足
插件耗时 scheduler_plugin_execution_duration_seconds 按 plugin 排序定位耗时最高的扩展点
失败原因 scheduler_schedule_attempts_total{result="unschedulable"} 比例高说明资源或亲和性策略过严

总结来说,kube-scheduler 的生产工作流可以概括为:Informer 同步对象 → 调度队列排队 → 调度阶段串行选节点(PreFilter/Filter/PostFilter/PreScore/Score/NormalizeScore/Reserve/Permit)→ 绑定阶段并发写 Binding(PreBind/Bind/PostBind)→ 调度缓存更新。掌握这条链路和对应的观测指标,才能在集群出现 Pending、调度慢、绑定失败时快速定位到具体扩展点和插件。