集群内负载调度方案调研

基于Kubernetes静态资源请求量的调度,会导致机器的实际资源消耗和请求量不一致,进而导致利用率不均衡。利用率不均衡带来的问题主要以下几个方面:

  1. 单机稳定性问题增加:尤其是利用率高于60%,或Load偏高的物理机上业务实例可用性差,时延高;


  2. 业务实例性能分层:相同配置的容器,性能差异比较大;


备注:分层的原因有多种,如CPU型号差异、整机负载差别等,利用率不均衡仅是原因之一。

业界方案调研

2.1 腾讯 - node annotator

指标收集node-annotator:将节点资源使用指标更新到节点annotation上:

  • 从指标数据源拉取节点指标数据
  • 将节点指标数据更新至节点annotation 调度扩展scheduler extender:基于scheduler extender扩展调度算法,被kube scheduler调用执行调度策略:
  • 依据历史周期内节点资源利用率初选,过滤高利用率节点
  • 依据节点资源利用率指标优选节点,低利用率节点优先级越高

可以调整5分钟、1小时的均值和峰值的预选阈值 可以配置5分钟、1小时、1天的均值和峰值的优选权重

example 1

举例来说,当一个新pod被调度时,kube scheduler能够将pod调度到真实利用率低的节点上。

2.2 k8s社区(IBM, Paypal) - load watcher

指标收集load watcher: 将节点资源使用指标缓存到本地:

  • 从监控源拉取节点指标数据
  • 将指标缓存到本地
  • 并服务于调度器插件查询 调度扩展scheduler plugin:基于Scheduler Framework Plugin实现优选阶段的Score插件,依据资源利用率对工作节点打分,选出资源利用率最低的节点。

example2

这套方案期望的调度结果是:在总资源请求量和平均资源使用量之外,这个方案还会考虑历史周期内出现高峰值的情况,并倾向于将新pod调度到节点1上。

2.3 Intel - Schedule on metric

两个核心组件部署到同一个pod内协同工作: 指标收集和策略控制controller: - 缓存指标数据:负责周期性拉取指标数据并本地缓存(mem cache) - 缓存策略数据:监听TASPolicy CRD,依据规格更新本地缓存中的策略信息 - 提供本地缓存访问:向extender提供服务 调度扩展scheduler extender:extender从controller缓存中获取调度策略,执行相应的预选/优选算法。

该方案除了两个核心组件外还要求对pod设置label或nodeAffinity,才能在调度的时候执行相应策略。

大致的workflow如下:

  1. 创建用户策略实例:

    yaml apiVersion: telemetry.intel.com/v1alpha1 kind: TASPolicy metadata: name: free-memory namespace: default spec: strategies: scheduleonmetric: # 依据指标的调度策略 rules: - metricname: memory_free operator: GreaterThan target: 1000

  2. 创建用户pod实例:

    ```yaml apiVersion: v1 kind: Pod metadata: name: pod-a namespace: default labels: app: demo telemetry-policy: free-memory # 通过label指定调度策略 spec: containers:

    • name: nginx image: nginx:latest imagePullPolicy: IfNotPresent ````
  3. 最终extender会将Pod A调度到可用内存量最高的Node A上。在优选阶段,调度器会依据extender返回节点列表选择优先级最高的节点。

    example3

2.4 负载调度扩展方案比较

table of cases

k8s扩展调度方式

由上述方案可以看出,业界针对动态负载调度主要基于两类方式扩展默认调度器:scheduler extender,scheduler framework plugin。两者都属于非侵入式的方案,无需修改scheduler核心代码。

|     方式.    |  scheduler    |   extender    |
| ----------- | ------------- | ------------- |
| v1.17 兼容性 | Stable in v1.17 | Beta in v1.17, Stable in v1.19 |
| 性能 | 走 http/https + 加解 JSON 包,开销较大 | 调用原生函数 |
| 灵活性 | extender 提供的扩展点较少且固定 | 调度扩展点更多,切分更细 | 
| 异常处理 | scheduler 无法将调度异常传递给extender,以及终止调度请求。|  在 plugin 调度失败或者发生错误时都可能发生中断并被放入 scheduler 队列等待重新
调度 |
| 共享缓存 | extender 需要自行与 API Server 进行通信,建立重复缓存 |
共享 scheduler 的节点、pod 快照 |

3.1 Scheduler framework plugin技术调研

Scheduler framework 通过 KubeSchedulerConfiguration 来配置各 plugin 的开启和关闭。 Scheduler framework 将整个调度过程分为调度循环(Scheduling Cycle)和绑定循环 (Binding Cycle)

  • 调度循环:通过预选和优选选择出一个节点以供 pod 运行。串行执行,即一个 pod 一个 pod 的调 度
  • 绑定循环:依据调度循环的结果,将 pod 绑定节点上。并行执行,即并发执行多个 pod 的绑定操 作 如图,framework 在调度过程中暴露多个扩展点来定制化 pod 的调度。一个插件可以注册到一个 或多个扩展点来实现复杂或者有状态的调度任务。
    • Sort: 用来对调度队列中的 pod 进行排序
  • PreFilter: 对 pod 进行预处理或者检查集群或者 pod 必须满足的条件 - Filter: 在功能上等同于“预选”,在默认的预选算法之后执行
  • PostFilter: 在 Filter 之后再执行过滤,在默认的预选算法、Filter plugin、extender 之后执行 - PreScore: 对过滤之后的节点预评分。v1.17 未支持
  • Score: 在功能上等同于“优选”,在原有的优选策略之后执行
  • NormalizeScore: 执行所有插件的 normalize scoring;每个插件对所有节点 score 进行 reduce,最终将分数限制在[MinNodeScore, MaxNodeScore]有效范围
  • Reserve: 将 node 相关资源预留(assume)给 pod,更新 scheduler cache。这步操作发生在执 行绑定请求
  • Permit: 阻止或推迟 pod 的绑定请求
  • PreBind: 执行 bind 操作之前的准备工作
  • Bind: 用于执行 pod 与 node 之间的绑定操作。先执行 Bind plugin,再发送绑定请求 - PostBind: 绑定循环的最后一个步骤,用于在 bind 操作执行成功后清理相关资源

我们可以在上述扩展点上添加自定义的调度行为来匹配需求。

总结

基于性能指标来动态调度实例在业界已经比较成熟,虽然各方案基于现有监控系统而有所不同,但是基于机器负载来修改默认调度器的思路是一致的。从业界上来看,已有的系统大多使用extender来扩张默认调度器。但是从社区的发展和性能考虑,plugin的方式是以后的趋势。提高机器利用率,均衡机器负载是一个长远的工作,除了扩展调度器外,还需要结合二次调度来平衡集群负载,降低高负载的风险。