kube-scheduler 源码解析
opsdev 王希刚 360云计算
女主宣言
本篇文章带大家了解部署在我们 HULK 容器服务 master 节点上的重要组件之一,kube-scheduler 的运行机制解读和核心代码分析,给想要阅读学习 Kubernetes 源码的同学一个参考。本文最先发布于 opsdev,转载已获取作者授权。
PS:丰富的一线技术、多元化的表现形式,尽在“HULK一线技术杂谈”,点关注哦!
前言
本文所涉及的源码为 Kubernetes 1.9,git commit id 为 925c127ec。本文前半部分讲解 scheduler 的原理,后半部分对 scheduler 源码进行分析。
1
kubernetes scheduler 基本原理
kubernetes scheduler 作为一个单独的进程部署在 master 节点上,它会 watch kube-apiserver 进程去发现 PodSpec.NodeName 为空的 Pod,然后根据指定的算法将 Pod 调度到合适的 Node 上,这一过程也叫绑定(Bind)。scheduler 的输入是需要被调度的 Pod 和 Node 的信息,输出是经过调度算法筛选出条件最优的 Node,并将该 Pod 绑定到这个 Node 上。如下图所示:
scheduler 调度算法分为两个阶段:
预选 (Predicates)
根据Predicates策略去滤掉不符合 Policies 的 Node.
优选 (Priorities)
经过 Predicates 剩下的 Node,需要经过Priorities 策略选出一个最优的 Node,并将 Pod 绑定到该 Node 上。根据下面这张调度图详细描述下:
1. 首先 scheduler 根据 predicates 集合过滤掉不符合的 Node。例如,如果 PodSpec 指定的请求资源 (resource requests),那么 scheduler 会过滤掉没有足够资源的 Node。
2. 其次 scheduler 会根据 priority functions 集合从 predicates 中过滤出来的 Node 中,选出一个最优的 Node。
算法实现:
对每一个 Node, priority functions 会计算出一个 0-10 之间的数字,表示 Pod 放到该 Node 的合适程度,其中 10 表示非常合适,0 表示不合适,priority functions 集合中的每一个函数都有一个权重 (weight),最终的值为 weight 和 priority functions 的乘积,而一个节点的 weight 就是所有 priority functions 结果的加和。例如,有两个 priority functions: priorityFunc1 和 priorityFunc2,对应的 weight 分别为 weight1 和 weight2,那么 NodeA 的最终得分是:
3. 最终,得分最高的 Node 胜出(如果有多个得分相同的 Node,会随机的选取一个 Node 作为最终胜出的 Node)。
2
kubernetes scheduler 源码分析
scheduler 的代码结构
k8s.io/kubernetes/plugin/cmd/scheduler.go 为程序入口文件 (main.go)
k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/server.go 包含 scheduler 的基础配置项
除了入口函数,scheduler 的具体逻辑实现均在 k8s.io/kubernetes/plugin/pkg/scheduler 目录下,这里就不一一介绍了。
scheduler的具体实现
上面的时序图就是整个 scheduler 的具体实现逻辑。下面根据这个时序图来对 scheduler 的源码进行解析。
NewSchedulerCommand 创建一个 scheduler命令行实例,用来对 scheduler 的命令行参数进行解析校验并且包含 scheduler 程序的入口函数 Run 的函数定义。
command.Execute() 会执行命令行实例中的 options.Run 方法。
在 options.Run 中主要进行了如下的操作:
loadConfigFromFile 加载 scheduler 配置文件信息。
NewSchedulerServer 创建 scheduler server 实例,使用 scheduler 配置参数对 scheduler server 进行初始化,如:
createClients 创建一系列 client,如连接 k8s 的 client,进行 scheduler 选主的 client 及 event client.
makeLeaderElectionConfig 生成 Leader Election 配置信息 (scheduler做了 HA,可以同时运行多个实例进程,但只有一个能正常工作,如果主的 scheduler 挂了,会重新进行选举)。
makeHealthzServer 初始化 healthz server,用于健康检查。
makeMetricsServer 初始化 metrics server,用于 prometheus 性能监控。
SchedulerServer.Run 启动 SchedulerServer,用于监控还是否有 Pod 待调度,并且进行相应的调度工作。具体的代码实现:
SchedulerConfig() 创建Scheduler Config,其中关键性函数是 NewConfigFactory 和 CreateFromProvider。
NewConfigFactory 定义了 podQueue 用来存储需要被调度的 Pod,每当新的 Pod 建立后,就会将 Pod 添加到该 queue 中。
CreateFromProvider 根据 algorithm provider 名称创建一个 scheduler 配置信息。其中 GetAlgorithmProvider 则根据 provider 名称去获取指定的 provider。
scheduler 默认使用的 provider 是 DefaultProvider。它主要实现了如下数据结构:
AlgorithmProviderConfig 这个数据结构包含预选和优选相关算法 key 的集合 (一个算法对应一个key,key 是算法的名字,value 是算法的具体实现 funtion),而这些算法注册是在 scheduler/algorithmprovider/defaults/defaults.go 文件的 init() 方法中进行注册 (实现使用的是工厂模式)。 如果想了解预选和优选算法的详细信息,请参看官方文档:
https://github.com/kubernetes/community/blob/master/contributors/devel/scheduler_algorithm.md
接着往下说: 通过 GetAlgorithmProvider 得到了 provider 关联的预选和优选算法集合的 Key。然后通过调用 CreateFromKeys (预选和优选的 Key作为参数) 来获取预选和优选算法的具体实现 (funtion),并对 NewGenericScheduler 实例进行初始化,返回最终的 scheduler 配置信息。
NewFromConfig 由 Scheduler Config 创建一个 schduler。
Start up the healthz server 启动健康检查服务
Start up the metrics server 启动 metrics 服务,供 Prometheus 进行性能监控数据的抓取。
LeaderElection 如果指定选举的方式来启动scheduler,则使用这种方式来执行scheduler。(使用 CallBack 的方式执行 Run 方法。如果主的 scheduler 出现问题,还会指定优雅处理函数对其进行处理)。
下面就到了 scheduler 真正干活的逻辑了,每次调度一个 Pod 都会执行下面的 Scheduler.Run(),具体的代码如下:
WaitForCacheSync() 将最新的数据同步到 SchedulerCache 缓存中。
scheduleOne() 调度 Pod 的整体逻辑。具体的实现可以看下面代码:
NextPod() 从 PodQueue 中获取一个未绑定的Pod。
schedule(pod) 执行对应 Algorithm 的 Schedule,进行预选和优选。接口定义如下:
Schedule 主要包含如下几个重要的方法:
nodeLister.List() 获取可用的 Node 列表。
findNodesThatFit() 进行预选。
PrioritizeNodes() 进行优选。
selectHost() 如果优选出的多个得分相同的 Node,则随机选取一个 Node。
assume() 更新 SchedulerCache 中 Pod 的状态,标志该 Pod 为 scheduled,并更新到 NodeInfo 中。
bind() 调用 kube-apiserver API,将 Pod 绑定到选出的 Node,之后 Kube-apiserver 会将元数据写入 etcd 中。接口定义如下:
这样一个 Pod 绑定到 Node 的流程就完成了。
3
总结
kube-scheduler 作为 Kubernetes master上一个单独的进程提供调度服务,通过 master 指定 kube-api-server 的地址,用来 watch Pod 和 watch Node 并调用 api server bind 接口完成 Node 和 Pod 的 Bind 操作。
kube-scheduler 中维护了一个 FIFO 类型的 PodQueue cache (其实还有一种 PriorityQueue 用于指定 Pod 的优先级,需要指定参数开启,默认是 FIFO 队列),新创建的 Pod 都会被 ConfigFactory watch 到,被添加到该 PodQueue 中,每次调度都从该 PodQueue 中 NextPod() 一个即将调度的 Pod。
获取到待调度的 Pod 后,就执行 AlgorithmProvider 配置 Algorithm 的 Schedule 方法进行调度,整个调度过程分两个关键步骤:Predicates 和 Priorities,最终选出一个最适合该 Pod 的 Node。
更新 SchedulerCache 中 Pod 的状态 (AssumePod),标志该 Pod 为 scheduled,并更新到 NodeInfo 中。
调用 api server 的 Bind 接口,完成 Node 和 Pod 的 Bind 操作,如果 Bind 失败,从 SchedulerCache 中删除上一步中已经 Assumed 的 Pod。