图解K8s源码 - kube-scheduler篇
本篇介绍的是 Kubernetes 系统的核心组件之一——kube-scheduler,它是 k8s 的默认调度器,负责为新创建出来的 pod寻找一个最合适的节点,这里的“最合适”指两种最优解:从集群中的所有节点中找出的全局最优解,和从集群中的部分节点中找出的局部最优解。它们分别可以解决调度器在小型和大型 k8s 集群规模上的性能问题,比如集群中有几百台主机时 kube-scheduler 采用全局最优解,当集群规模大时采用局部最优解。
那“最合适”的含义是指什么呢?它主要是通过两种调度算法的筛选:
- Predicate(预选):过滤掉不满足条件的节点,选出所有可以运行该 pod 的节点。
- Scheduler(优选):给上一步得到的结果中的每个节点打分,选出得分最高的节点为最终调度结果。
那怎么表示调度器对一个 pod 调度成功呢?实际上就是将它的 spec.nodeName 字段填上调度结果的节点名字。
Scheduler 调度机制工作原理
Scheduler 是作为单独程序运行的,它在启动之后会一直监听 API Server,获取 spec.nodeName 字段为空的 pod。调度算法执行完之后,调度器将 pod 对象的 nodeName 字段的值改成对应 node 的名字,表明 pod 应该放在哪个节点上。这个步骤在 k8s 中称为 Bind。
调度的具体过程可以总结为下图:
Scheduler 实际上就是两个独立的控制循环:
第一个循环(Informer Path)主要目的是启动一系列 informer 来监听(Watch) Etcd 中的 pod、Node、Service 等与调度相关的API对象的变化。比如,一个待调度的 pod 被创建出来之后,调度器就会通过 Pod Informer 的 Handler,将这个待调度的 pod 加入调度队列。
此外,调度器 informer 还要对调度器缓存(Schedule Cache) 进行更新。Predicates 调度算法执行时也是从缓存中取数据,从而提高算法执行效率。
第二个循环(Scheduling Path)主要是不断从调度队列中 pop 出 pod,然后调度器启动多个goroutine,以节点为粒度并发执行 Predicates 算法进行过滤并得到一组节点,即可以运行这个 pod 的宿主机列表。再以 MapReduce 方式并发执行 Priorities 算法为列表中的节点打分,分数从0到10,得分最高的节点会作为这次调度的结果,并完成 Bind 的一系列操作,最后更新 Schedule Cache 里的 pod 和 node 信息。
Scheduler 的优先级和抢占机制
以上是调度机制的工作原理,那如果 pod 调度失败怎么办呢?正常情况下,当一个 pod 调度失败后,就会被暂时搁置处于 pending 状态,直到 pod 被更新或者集群状态发生变化,调度器才会对这个 pod 进行重新调度。但在实际业务场景中存在在线和离线业务之分。如果在线业务的 pod 由于资源不足而调度失败时,在线业务就需要抢占离线业务的资源,此时用到的就是 Scheduler 的优先级和抢占机制了。
- 优先级:高优先级的 pod 可能比低优先级的 pod 提前出队,从而尽早完成调度过程。
- 抢占:当一个高优先级的 pod 调度失败时触发调度器的抢占能力,当某个节点上的一个或多个低优先级 pod 被删除之后,待调度的高优先级 pod 就可以被调度到该节点上。
抢占过程简言之就是两步:
- 将抢占者 Pod 的 spec.nominatedNodeName 字段设置为被抢占节点的名字。
- 在下个调度周期决定是否在被抢占节点上运行。
这样设计的一个重要原因是考虑到调度器只会通过向API Server 发起请求,触发 DELETE API 来删除被抢占的 pod,所以在这些 pod “优雅退出”时间内(默认是30s)其他节点也有可能变成可调度的,或者有新的节点被添加到这个集群中。鉴于优雅退出时期集群的可调度性可能会发生变化,所以把抢占者交给下一个调度周期再处理。
在抢占者等待被调度的过程中,如果有优先级更高的 pod 来抢占同一节点,调度器就会清空原抢占者的 spec.nominatedNodeName 字段,从而允许更高优先级的抢占者执行抢占,这也使得原抢占者本身也有机会去重新抢占其他节点。
那么,Scheduler 的抢占机制是如何设计的呢?一个最重要的设计是调度队列中实现了两个不同的队列:
- activeQ:存放下一个调度周期需要调度的对象。在集群中新建的 pod 都会入队到 activeQ,调度时也是从 activeQ 中出队。
- unschedulableQ:存放调度失败的 pod。当 unschedulableQ 里的 pod 更新后,调度器会把这个 pod 移到 activeQ。
抢占基本流程图如下:
- 集群中新建的 pod 先放到 activeQ 中经过正常的调度策略进行调度;
- 调度失败后,将该 pod 放入 unschedulableQ;
- 检查失败原因,已确认抢占能否帮助抢占者找到新节点;
- 确定可以抢占后,从 Schedule Cache 中复制所有节点信息,用副本模拟抢占流程(基于影响集群稳定性最小原则,即被抢占的 pod 越少越好,优先级越低越好);
- 检查选中的“牺牲者”列表,清理这些 pod 的 nominatedNodeName 字段;
- 更新抢占者pod,将 nominatedNodeName 改成第4步选出的 node 名字;
- 由于抢占者 pod 已发生了更新,所以给他“重新做人”的机会,重新放入 activeQ 里;
- 同时,开启一个新的协程,清理牺牲者 node 上对应的牺牲者 pod。
参考:
《深入剖析kubernetes》
https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/pod-priority-preemption/
https://blog.csdn.net/zyxpaomian/article/details/111870568
k8s系列往期文章列表:
图解K8s源码 - kube-apiserver下的RBAC鉴权机制
图解K8s源码 - kube-controller-manager篇
持续更新中……
END
相关文章
- Spring IoC、AOP、Transaction、MVC 归纳小结
- fl studio21水果软件都更新了些啥功能?
- spring boot 的thymeleaf真的是那么随便就可以用的吗?弄不好你就会遇到很头疼的事
- CorelDRAW2023最新版本在哪里下载有哪些新功能
- 生物信息数据分析教程视频——11-筛选相关性基因
- 谷歌浏览器:Unchecked runtime.lastError: The message port closed before a response was received
- ABAP之选择屏幕真假必输的详细使用方式
- SAP 之SD模块常用权限检查
- 阐述DAPP智能合约流动性质押挖矿分红系统开发技术详细及代码分析
- spring boot整合pagehelper分页插件
- DC/DC升压恒压IC 干电池玩具专用芯片
- C 程序来演示 fork() 和 pipe()
- 生物信息数据分析教程视频——12-基因之间的相关性分析及可视化
- SpringBoot:模块探究之spring-boot-autoconfigure
- SpringBoot:模块探究之spring-boot-actuator
- 生物信息数据分析教程视频——13-3种R包(DESeq2、edgeR和limma)进行RNAseq的差异表达分析与比较
- 【ElementUI】Vue+ElementUI多文件上传,一次请求上传多个文件!
- 一款基于代码生成器的Java快速开发平台【jeecg-boot】
- 生物信息数据分析教程视频——14-芯片数据的表达差异分析
- Hive 视图和索引