一文看懂 kube-scheduler
Kube-scheduler 是Kubernetes集群默认的调度器,并且是控制面中一个核心组件。

scheduler通过 kubernetes 的监测(Watch)机制来发现集群中新创建且尚未被调度到 Node 上的 Pod。 scheduler会将发现的每一个未调度的 Pod 调度到一个合适的 Node 上来运行。 scheduler会依据下文的调度原则来做出调度选择。
实际上调度完成之后,scheduler只需更新 Pod 的 NodeName
字段即可。
工作原理
scheduler工作流程基本上如下:
podQueue podQueue podQueue podQueue

具体scheduler 给一个 pod 做调度选择包含两个步骤:
- 过滤(预选)
- 打分(优选)
过滤阶段会将所有满足 Pod 调度需求的 Node 选出来。 例如,PodFitsResources 过滤函数会检查候选 Node 的可用资源能否满足 Pod 的资源请求。 在过滤之后,得出一个 Node 列表,里面包含了所有可调度节点;通常情况下, 这个 Node 列表包含不止一个 Node。如果这个列表是空的,代表这个 Pod 不可调度。
在打分阶段,scheduler会为 Pod 从所有可调度节点中选取一个最合适的 Node。 根据当前启用的打分规则,scheduler会给每一个可调度节点进行打分。
最后,scheduler 会将 Pod 调度到得分最高的 Node 上。 如果存在多个得分最高的 Node,scheduler 会从中随机选取一个。

如何扩展scheduler?
在真实的生产环境中,Kube-scheduler 可能不能满足我们的需求,我们需要扩展其功能。一般来说有以下4种扩展方式:
- clone 官方 kube-schedule,然后对其进行代码级更改,这种方式有一定的局限性,比如你使用的是托管k8s集群,我们压根无法替换默认的调度器。此外,kubernetes 社区更新迭代比较快,每年会发4个正式版本,我们需要不断地merge上游的代码。
- 实现一个新的调度器,配置 pod.spec.schedulerName 来选择使用哪一个调度器。由于两个调度器并行运行,非常有可能出现资源冲突的问题。
- Scheduler extender。侵入性比较小,可以独立于原生 Scheduler 运行,并且无需修改原生 Scheduler 的代码,只需要在运行原生 Scheduler 的时候加一个配置即可。但是该方式也有一些劣势,比如scheduler extender 和默认调度器之间有一些通信成本,扩展点有限,scheduler extender 和默认调度器无法共享cache。
- Scheduler Framework 。Scheduler Framework 是 Kubernetes Scheduler 的一种可插入架构,可以简化调度器的自定义。 它向现有的调度器增加了一组新的“插件” API。插件被编译到scheduler程序中。 这些 API 允许大多数调度功能以插件的形式实现,同时使调度“核心”保持简单且可维护。
发展至今,Scheduler Framework 是最佳的扩展方案。
Scheduler Framework
Scheduler Framework定义了一些扩展点。调度器插件注册后在一个或多个扩展点处被调用。 这些插件中的一些可以改变调度决策,而另一些仅用于提供信息。
工作流程
每次调度一个 Pod 的尝试都分为两个阶段,即 调度周期 和 绑定周期 。
调度周期为 Pod 选择一个节点,绑定周期将该决策应用于集群。 调度周期和绑定周期一起被称为“调度上下文”。
调度周期是串行运行的,而绑定周期可能是同时运行的。
扩展点

如上图所示,我们简单介绍一下支持的扩展点:
- QueueSort : 对队列中的 Pod 进行排序
- PreFilter : 预处理 Pod 的相关信息,或者检查集群或 Pod 必须满足的某些条件。 如果 PreFilter 插件返回错误,则调度周期将终止。
- Filter : 过滤出不能运行该 Pod 的节点。对于每个节点, 调度器将按照其配置顺序调用这些过滤插件。如果任何过滤插件将节点标记为不可行, 则不会为该节点调用剩下的过滤插件。节点可以被同时进行评估。
- PostFilter : 在筛选阶段后调用,但仅在该 Pod 没有可行的节点时调用。 插件按其配置的顺序调用。如果任何后过滤器插件标记节点为“可调度”, 则其余的插件不会调用。典型的后筛选实现是抢占,试图通过抢占其他 Pod 的资源使该 Pod 可以调度。
- PreScore : 运行评分任务以生成可评分插件的共享状态
- Score : 通过调用每个评分插件对过滤的节点进行排名
- NormalizeScore : 结合分数并计算节点的最终排名
- Reserve : 在绑定周期之前选择保留的节点
- Permit : 批准或拒绝调度周期的结果
- PreBind : 用于执行 Pod 绑定前所需的任何工作。例如,一个预绑定插件可能需要提供网络卷并且在允许 Pod 运行在该节点之前 将其挂载到目标节点上。
- Bind : 用于将 Pod 绑定到节点上。直到所有的 PreBind 插件都完成,Bind 插件才会被调用。
- PostBind : 这是个信息性的扩展点。 绑定后插件在 Pod 成功绑定后被调用。这是绑定周期的结尾,可用于清理相关的资源。
Plugin API
插件 API 分为两个步骤。首先,插件必须完成注册并配置,然后才能使用扩展点接口。 扩展点接口具有以下形式。
type Plugin interface { Name() string } type QueueSortPlugin interface { Plugin Less(*v1.pod, *v1.pod) bool } type PreFilterPlugin interface { Plugin PreFilter(context.Context, *framework.CycleState, *v1.pod) error }
你可以在调度器配置中启用或禁用插件。 如果你在使用 Kubernetes v1.18 或更高版本,大部分调度插件都在使用中且默认启用。
发展趋势
默认的Kube-scheduler有两个特点:
- 以单个Pod为粒度
- 调度的最优性只存在于调度完成的那一刻,后续随着新的节点增加,新的Pod增加或减少,当初的调度决策可能已经不是最优。
对于第一点,这决定了默认的调度器无法满足大数据,AI等领域的要求。因而社区涌现了诸多专门用于任务类的调度器。比如 kube-batch 。
kube-batch是Kubernetes的批处理调度程序,为希望利用Kubernetes运行批处理作业的应用程序提供机制。它建立在使用多个系统大规模运行批处理工作负载的十五年经验的基础上,并结合了来自开源社区的最佳想法和实践。

不过,如果我们的kubernetes集群是一个纯粹的离线任务集群,即专门用于大数据,机器学习,那么kube-batch无疑是一个很好的选择。
但如果我们的集群是一个离线和在线混跑的集群,可能通过 Scheduling Framework 的插件机制将kube-batch支持的一些调度功能融入到原生的 Kube-scheduler 中,更加合理。
对于第二点,为了实现全时间的最优调度,社区开发了 descheduler 项目。Descheduler 可以根据一些规则和配置策略来帮助我们重新平衡集群状态,当前项目实现了五种策略: RemoveDuplicates
、 LowNodeUtilization
、 RemovePodsViolatingInterPodAntiAffinity
、 RemovePodsViolatingNodeAffinity
和 RemovePodsViolatingNodeTaints
,这些策略都是可以启用或者禁用的,作为策略的一部分,也可以配置与策略相关的一些参数,默认情况下,所有策略都是启用的。