kube-scheduler是kubernetes中的调度程序,负责从api server中获得待分发的pod列表,并为他们找到最合适运行的Node。
基于kubernetes 1.27
基本框架
下面是kubernetes官发给出的框架图,先对kubernetes pod调度的大致流程有一个认识
看一下有了初步的印象之后,再简单看看里面的操作流程。
调度逻辑里面一共有4个关键的步骤:
- filter
预选,过滤掉不满足pod运行条件的Node。包括prefilter、filter、postfilter。prefilter
初步过滤,包括一些基础的标准或规则,更加轻量级。filter
过滤条件更加复杂和耗时。postfilter
常用来处理没有找到合适pod的场景,比如preempt抢占。
- prioritize
优选。根据优选函数对每个符合条件的Node进行打分。按照各项的分值*权重合并后,找到分值最高的Node。包括secore、prescore、normalizeScoreprescore
准备数据,为score提供需要的信息。score
根据各个plugins计算每个节点的得分。normalizeScore
在计算最终得分之前将各个plugins的得分规范化处理一次,即map-reduce的reduce部分。
- reserve
检测并预留node上的资源。包括reserve、unreserve、permitreserve
检测和预留资源。unreserve
如果某个plugin的资源不足以运行pod,则逆序unreserve之前已经reserve成功的plugins。permit
准入限制。包括三个状态:approve、deny、wait。如果permit plugins返回wait,会阻塞等待一定时间才进行bind cycle操作,wait超时会变成deny,并重新调度。
- bind
Node绑定操作。prebind
绑定前的准备工作,包括分配网络、磁盘等。bind
正式的绑定操作,默认是通过更新pod的NodeName来完成。postbind
善后处理。主要是清理一些关联资源、更新状态或者发送通知。
根据上面的步骤,kubernetes调度一个节点可以简化成下面流程:
- 获取一个待调度的Pod
- 并发计算每一个node是否满足pod的调度条件(执行predicate函数map),如果满足则加入到备选列表中
- 使用map-reduce聚合计算每一个node的分值(执行priority函数map),选出分最高的一个
- 如果前面没有选出可以调度的node,则进行抢占逻辑(在允许抢占的情况下)
- 将之前调度失败的Node,预驱逐所有优先级比当前pod低的其他pod,看驱逐后是否能调度当前pod,这些node加入备选列表。
- 将调度pod放入备选node中,再按照优先级将预驱逐的pod重新添加回来,直到node不能再选点
- 在预驱逐过程中,计算了驱逐各个pod需要破坏多少pdb限制。最后求出各个节点如果要调度当前pod,一共需要破坏多少pdb。
- 选出破坏pdb限制最少、所有pod优先级最高的node
- 将调度的pod绑定到选中的Node,将需要驱逐的pod重新加到待调度列表中。
- 在选中的Node上预留资源,如果预留失败则pod会重新进入调度队列
- 预留成功后,将对Node再做一次准入校验,确定节点是否可以立刻运行当前的pod
- 将pod与选中的节点进行绑定
源码分析
现在咱们应该已经对scheduler的基础逻辑有了一个大概的认识了,可以深入分析源码的实现啦。
在分析前,我先给一个代码的跳转流程,方便后续阅读时可以快速找到对应代码位置
- 创建一个cobra终端应用(NewSchedulerCommand / cmd/kube-scheduler/app/server.go)
- 注册默认的配置以及调度算法并运行scheduler(runCommand / cmd/kube-scheduler/app/server.go)
- 初始化创建scheduler并运行(Run / cmd/kube-scheduler/app/server.go)
- 起两个goroutine,一个将BackoffQ的pod移到activeQ,一个将UnschedulableQ的pod移到activeQ(Run / pkg/scheduler/scheduler.go)
- 循环执行Pod调度,一个Pod调度完成立刻执行下一个(Run / pkg/scheduler/scheduler.go)
- 将一个Pod调度到Node上(scheduleOne / pkg/scheduler/schedule_one.go)
- 执行pod调度流程(schedulingCycle / pkg/scheduler/schedule_one.go)
- 预选。findNodesThatFitPod
- 优选。prioritizeNodes
- 如果scheduler调度失败,则执行postFilter逻辑。(RunPostFilterPlugins / pkg/scheduler/framework/runtime/framework.go)
- postFilter包括抢占preempt(preempt / pkg/scheduler/framework/preemption/preemption.go)
- 占用并预留节点资源(runReservePluginReserve / pkg/scheduler/framework/runtime/framework.go)
- 将pod绑定到选中的Node上(bind / pkg/scheduler/scheduler.go)
后续待更新