[k8s理论知识]6.k8s调度器

k8s默认调度器

k8s调度器的主要职责,就是为一个新创建出来的pod寻找一个适合的节点Node。这包括两个步骤,第一,从所有集群的节点中,根据调度算法挑选出所有可以运行该pod的节点,第二,从第一步的结果中,根据调度算法挑选一个最符合条件的pod作为最终结果。

所以在具体的调度流程中,默认调度器会首先调用一组叫作 Predicate 的调度算法,来检查每个 Node。然后,再调用一组叫作 Priority 的调度算法,来给上一步得到的结果里的每个 Node 打分。最终的调度结果,就是得分最高的那个 Node。

cache化

为了高效的完成调度过程,调度器必须要频繁的查询集群的状态信息,如节点的资源使用情况,节标签、污点等等。这些操作每次都从etcd中获取数据,会导致性能瓶颈。为了解决这个问题,Kubernetes 引入了调度器缓存(scheduler cache)。调度器缓存是一个内存中的数据结构,用于存储集群的节点信息和其他相关元数据。通过将这些信息缓存起来,调度器可以更快地访问和处理这些数据,从而提高调度算法的执行效率。

通过将集群信息尽可能的缓存化,可以提高k8s的调度效率,提高性能。通过将节点信息和其他元数据缓存到内存中,调度器可以减少对 etcd 的直接访问次数,从而降低网络延迟和 I/O 开销。

从上图可以看出,第一个控制循环informer Path的主要目的就是启动一系列的informer,用来监听etcd中的pod、node、service等与调度相关的API对象的变化,如果有调度需求,就将这个pod加入调度队列,这个调度队列是一个优先级队列。调度器还要负责对调度器缓存进行更新(update cache)。

第二个控制循环是负责Pod调度的主循环,叫做scheduling path。Scheduling Path 的主要逻辑,就是不断地从调度队列里出队一个 Pod。然后,调用 Predicates 算法进行"过滤"。这一步"过滤"得到的一组 Node,就是所有可以运行这个 Pod 的宿主机列表。当然,Predicates 算法需要的 Node 信息,都是从 Scheduler Cache 里直接拿到的,这是调度器保证算法执行效率的主要手段之一。接下来,调度器就会再调用 Priorities 算法为上述列表里的 Node 打分,分数从 0 到 10。得分最高的 Node,就会作为这次调度的结果。执行完调度算法之后,调度器就需要将 Pod 对象的 nodeName 字段的值,修改为上述 Node 的名字。这个步骤在 Kubernetes 里面被称作 Bind。

乐观绑定

当进行到上面说的修改nodeName的时候,其实pod还没有被部署到这个节点。但是这个时候scheduler就已经把这个调度信息写入到scheduler cache中了。然后再异步的向API server发起更新pod的请求,以真正的完成bind操作。这种设计被称为乐观假设。

这是因为如果选定node名称之后,发起同步的API server通信等待响应,会显著增加调度器的延迟。通过异步绑定,调度器可以在确定合适节点后立即更新调度器缓存,然后继续处理下一个 Pod 的调度,而不需要等待 API Server 的响应。这大大减少了关键路径上的延迟,提高了调度器的吞吐量。

如果调度失败的话,scheduler cache中写入的内容也会在与etcd更新的时候进行修正,并不会使集群的信息不同步。

admit操作

由于乐观绑定的操作,当一个新的 Pod 完成调度需要在某个节点上运行起来之前,该节点上的 kubelet 还会通过一个叫作 Admit 的操作来再次验证该 Pod 是否确实能够运行在该节点上。Admit 操作会重新执行一组最基本的调度算法,如"资源是否可用"、"端口是否冲突"等,作为 kubelet 端的二次确认。

无锁化

调度器会启动多个 Goroutine,以节点为粒度并发执行 Predicates 算法。这样可以并行地检查多个节点是否满足 Pod 的调度要求,从而显著提高这一阶段的执行效率。在并发编程中,锁是一种常用的同步机制,用于确保多个 Goroutine 之间对共享资源的访问是安全的。比如说多个goroutine可能会同时的向队列中加入或移除pod,或是同时操作scheduler cache。然而,锁的使用会带来一定的性能开销,特别是在高并发场景下。

为了增加predicate过程的速度,调度器采用了无锁化设计,在这些并发路径上,调度器避免设置任何全局的竞争资源,从而避免了没有锁带来的状态难以管理的问题。

默认调度器的重构

随着 Kubernetes 项目的逐步稳定,越来越多的用户开始将其应用于规模更大、业务更加复杂的私有集群中。在这些复杂场景下,用户对默认调度器的扩展和重新实现提出了更高的需求。社区对 Kubernetes 项目的主要诉求之一就是增强默认调度器的可扩展性。

下图是默认调度器的可扩展性设计的示意图。可以看到在调度器生命周期的各个关键点上,为用户暴露出了可以扩展和实现的接口。每一个绿色的箭头都是一个可以插入自定义逻辑的接口。比如,上面的 Queue 部分,就意味着你可以在这一部分提供一个自己的调度队列的实现,从而控制每个 Pod 开始被调度(出队)的时机。

在 Kubernetes 的整体架构中,kube-scheduler 的责任虽然重大,但其实它却是在社区里最少受到关注的组件之一。这里的原因也很简单,调度这个事情,在不同的公司和团队里的实际需求一定是大相径庭的,上游社区不可能提供一个大而全的方案出来。所以,将默认调度器进一步做轻做薄,并且插件化,才是 kube-scheduler 正确的演进方向。

相关推荐
小蜜蜂爱编程几秒前
记录一个狗血的docker问题
运维·docker·容器
菜菜-plus3 小时前
微服务技术,SpringCloudAlibaba,Redis,RocketMQ,Docker,分库分表
java·spring boot·redis·spring cloud·docker·微服务·java-rocketmq
瑕、疵3 小时前
使用Docker Compose简化微服务部署
docker·微服务·容器
甲柒5 小时前
12-Docker发布微服务
java·docker·微服务
Arc星语7 小时前
Docker Redis集群3主3从模式
redis·docker
工作不忙10 小时前
不使用docker-compose不使用zookeeper启动ApacheKafka3.8.0单机运行KRAFT模式
ubuntu·docker·zookeeper·kafka·apache
盒马盒马10 小时前
Docker:存储卷
docker·容器
Hadoop_Liang11 小时前
Docker Compose一键部署Spring Boot + Vue项目
vue.js·spring boot·docker
诡异森林。12 小时前
Docker:容器化和虚拟化
java·docker·容器
斯普信专业组17 小时前
K8s企业应用之容器化迁移
云原生·容器·kubernetes