第1章:Kubernetes调度器原理
调度器这东西,说白了就是Kubernetes集群里的「包工头」。
你提交一个Pod,它得决定把这活儿派给哪台机器干。我刚开始接触K8s时,总觉得调度就是个简单的资源匹配——哪个节点空闲就扔过去呗。后来线上一次Pod调度失败,排查了半天才发现是调度队列的优先级搞反了。嗯,从那以后,我老老实实把调度器原理啃了一遍。
1.1 默认调度器:kube-scheduler
Kubernetes默认的调度器叫kube-scheduler,它是个独立运行的组件。
我个人习惯把它理解成一个「过滤器+打分器」的组合体。先过滤掉不满足条件的节点,再给剩下的节点打分,最后选个分数最高的。
核心职责:
- 监听新创建的Pod(未被调度到节点的)
- 为Pod选择合适的Node
- 将调度结果写入etcd
你想想看,如果集群里有1000个节点,每个节点都要跑一遍过滤和打分逻辑,性能怎么办?
默认调度器用了缓存机制,把节点信息缓存在本地。我在项目中遇到过,节点状态更新不及时导致调度到已满的节点,后来加了informer的重新同步周期才解决。
1.2 调度队列:Pod的候车室
调度队列,就是Pod等待被调度的「候车室」。
K8s把Pod分成三个队列:
| 队列名称 | 作用 | 优先级 |
|---|---|---|
| activeQ | 活跃队列,存放待调度的Pod | 高 |
| backoffQ | 退避队列,调度失败的Pod先放这里 | 中 |
| unschedulableQ | 不可调度队列,资源不足的Pod放这里 | 低 |
为什么会设计三个队列?
我记得有一次线上Pod调度失败,直接丢回activeQ重试,结果频繁重试把API Server打爆了。后来K8s社区引入了backoff机制,失败后先退避一段时间再重试,这才稳定下来。
避坑指南:
我曾经遇到过Pod一直卡在unschedulableQ里出不来。原因是节点资源释放后,没有触发重新入队。后来加了定时器,每隔一段时间强制把unschedulableQ的Pod挪回activeQ重新调度。
1.3 调度周期:过滤与打分
调度周期分为两个阶段:过滤阶段和打分阶段。
过滤阶段(Predicates):
- 检查节点资源是否满足Pod需求(CPU、内存、GPU等)
- 检查节点端口是否冲突
- 检查节点标签是否匹配Pod的nodeSelector
- 检查Pod的亲和性/反亲和性规则
打分阶段(Priorities):
- 资源利用率均衡(LeastRequestedPriority)
- 节点亲和性匹配(NodeAffinityPriority)
- Pod间亲和性(InterPodAffinityPriority)
我习惯把过滤阶段叫「一票否决制」——只要有一个条件不满足,这个节点就直接出局。打分阶段则是「优中选优」,每个节点拿个分数,最高分中标。
注意:
过滤阶段的顺序很重要。默认调度器会把计算量小的过滤器放在前面,比如检查节点端口冲突比检查Pod亲和性快得多。我曾经优化过调度性能,把过滤器的执行顺序调整了一下,调度吞吐量提升了15%。
1.4 绑定过程:从决策到落地
调度器选好节点后,需要把结果写回集群。这个过程叫绑定。
绑定分三步:
- 预绑定:调度器在本地缓存中标记Pod已被调度
- 绑定:调度器调用API Server,将Pod的NodeName字段设置为选中的节点
- 后绑定:触发一些扩展逻辑,比如存储卷的挂载
你想想看,如果绑定过程中API Server挂了怎么办?
调度器会重试绑定操作。我遇到过绑定超时的情况,调度器重试了3次才成功。如果一直失败,Pod会重新进入调度队列,等待下一次调度机会。
核心流程总结:
Pod创建 → 进入activeQ → 出队 → 过滤节点 → 打分排序 → 绑定节点 → 完成调度
1.5 调度器架构图
下面这张图展示了调度器的核心工作流程,我画了张SVG方便你理解:
这张图把调度器的核心流程串起来了。从Pod创建开始,经过队列排队、过滤、打分,最后绑定到节点。如果绑定失败,Pod会重新进入队列等待下一次调度。
个人经验:
我建议你在排查调度问题时,先看Pod在哪个队列里。如果在unschedulableQ,多半是资源不足;如果在backoffQ,可能是调度失败后的退避;如果在activeQ但一直没调度,那可能是调度器本身出了问题。
调度器原理这部分,说白了就是理解「怎么选、选谁、怎么绑」这三个问题。搞清楚了,后面自定义调度器、配置调度策略就顺理成章了。
想要更多资料,联系可可