Skip to content

Commit

Permalink
更新 k8s 逻辑
Browse files Browse the repository at this point in the history
  • Loading branch information
isno committed Sep 26, 2024
1 parent d172826 commit e5309f6
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 21 deletions.
2 changes: 1 addition & 1 deletion assets/kube-scheduler.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/scheduling-framework-extensions.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
103 changes: 83 additions & 20 deletions container/kube-scheduler.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ Pod 的创建/更新和节点资源无时无刻不在发生变化。如果每次
—— from Omega 论文
:::

## 1. 调度双循环架构

Omega 的论文中提出了一种基于共享状态(图 7-1 中的 Scheduler Cache)的双循环调度机制,用来解决大规模集群的调度效率问题,这种调度机制不仅应用在 Google 的 Omega 系统中,也被 Kubernetes 继承下来。

Kubernetes 默认调度器(kube-scheduler)双循环架构如下所示。
Expand All @@ -24,38 +22,103 @@ Kubernetes 默认调度器(kube-scheduler)双循环架构如下所示。
图 7-37 kube-scheduler 双循环架构设计
:::

- 第一个控制循环称之为 Informer Path,它主要目的是启动一系列 Informer 监听(Watch)Etcd 中 Pod、Node、Service 等与调度相关的 API 对象的变化。譬如一个待调度 Pod 被创建后,调度器就会通过 Pod Informer 的 Handler 将这个待调度 Pod 添加进调度队列。Kubernetes 的调度器还要负责对调度器缓存(即 Scheduler Cache)进行更新,缓存的目的主要是对调度部分进行性能优化,将集群信息 cache 化,以便提升 Predicate 和 Priority 调度算法的执行效率。

第二个控制循环称为 Scheduling Path ,是负责 Pod 调度的主循环。Scheduling Path 主要逻辑是不断地从调度队列里出队一个 Pod。

- 预选(predicates)就是从集群的所有节点中根据调度算法筛选出所有可以运行该 pod 的节点集合
- 优选(priority)则是按照算法对预选出来的节点进行打分,找到分值最高的节点作为调度节点.
从图 7-37 可以看出,Kubernetes 调度的核心就是两个互相独立的控制循环。

第一个控制循环称为 Informer 循环。该循环的主要逻辑是启动一系列 Informer 监听(Watch)API 资源(如要是 Pod 和 Node)状态的变化。当 API 资源变化时,就会触发 Informer 的回调函数进行处理。如一个待调度 Pod 被创建后,Pod Informer 的回调函数就会将其入队到调度队列中(PriorityQueue)。

然后调用 Predicates 算法对所有的 Node 进行“过滤”。“过滤”得到的一组可以运行这个 Pod 的 Node 列表。当然,Predicates 算法需要的 Node 信息,也都是 Scheduler Cache 里直接拿到的,这是调度器保证算法执行效率的主要手段之一。接下来,调度器就会再调用 Priorities 算法为上述列表里的 Node 打分,得分最高的 Node 就会作为这次调度的结果
此外,当上述事件触发时,Informer 还承担对调度器缓存(即 Scheduler Cache)更新的责任。缓存的主要目的是尽可能将 Pod、Node 的信息缓存化,以便提升后续调度逻辑的执行效率

调度算法执行完成后,调度器就需要将 Pod 对象的 nodeName 字段的值,修改为上述 Node 的名字,这个过程在 Kubernetes 里面被称作 Bind。为了不在关键调度路径里远程访问 API Server,Kubernetes 默认调度器在 Bind 阶段只会更新 Scheduler Cache 里的 Pod 和 Node 的信息。这种基于“乐观”假设的 API 对象更新方式,在 Kubernetes 里被称作 Assume。Assume 之后,调度器才会创建一个 Goroutine 异步地向 API Server 发起更新 Pod 的请求,完成真正 Bind 操作
第二个控制循环称为 Scheduling 循环。该循环的核心逻辑是不断地从调度队列里出队一个 Pod。然后触发两个最核心的调度阶段:过滤阶段(也称为 Predicates)和打分阶段。Kubernetes 从 v1.15 版本起,调度器生命周期的各个关键点上,向用户暴露可以扩展和实现自定义调度逻辑的接口。用户可以编写插件注册到 Kubernetes 从而控制调度逻辑

过滤阶段,本质是调用扩展点注册的插件(主要是 preFilter 和 filter,稍后介绍),根据插件预设的过滤策略筛选出符合 Pod 要求的 node 节点集合。Kubernetes 的调度器内置了一批过滤插件,总结它们的过滤策略如下:

## 2. 调度器可扩展设计
- 资源过滤策略:检查节点资源是否满足 Pod 请求(request),在节点之间平衡资源分配。
- 节点过滤策略:与宿主机节点相关的策略。例如检查 Pod 是否能容忍节点的污点;确保 Pod 调度到符合亲和性条件的节点;
- 拓扑和亲和性策略:该策略主要处理 Pod 之间的亲和性规则,还有确保 Pod 在不同节点间均匀分布。

“Pod 是原子的调度单位”这句话的含义是 kube-scheduler 以 Pod 为调度单元进行依次调度,并不考虑 Pod 之间的关联关系
在过滤之后,得出一个节点列表,里面包含了所有可调度节点;通常情况下,这个节点列表包含不止一个节点。如果这个列表是空的,代表这个 Pod 不可调度。过滤阶段结束之后,接着进入打分阶段

但是很多数据**计算类的离线作业具有组合调度的特点,要求所有的子任务都能够成功创建后,整个作业才能正常运行**,即所谓的 All_or_Nothing
在打分阶段,调度器会为 Pod 从所有可调度节点中选取一个最合适的节点。根据当前启用调度插件的打分策略,调度器会给每一个可调度节点进行打分。得分最高的 Node 就会作为这次调度的结果。如果存在多个得分最高的节点,kube-scheduler 会从中随机选取一个

例如:JobA 需要 4 个 Pod 同时启动,才算正常运行。kube-scheduler 依次调度 3 个 Pod 并创建成功,到第 4 个 Pod 时,集群资源不足,则 JobA 的 3 个 Pod 处于空等的状态。但是它们已经占用了部分资源,如果第 4 个 Pod 不能及时启动的话,整个 JobA 无法成功运行,更糟糕的集群其他的资源刚好被 JobB 的 3 个 Pod 所占用,同时在等待 JobB 的第 4 个 Pod 创建,此时整个集群就出现了死锁
在上述两个阶段结束之后,调度器 kube-scheduler 会将就需要将 Pod 对象的 nodeName 字段的值,修改为选中 Node 的名字,这个过程在 Kubernetes 里面被称作 Bind。为了不在关键调度路径里远程访问 API Server,Kubernetes 默认调度器在 Bind 阶段只会更新 Scheduler Cache 里的 Pod 和 Node 的信息。这种基于“乐观”假设的 API 对象更新方式,在 Kubernetes 里被称作 Assume。Assume 之后,调度器才会创建一个 Goroutine 异步地向 API Server 发起更新 Pod 的请求,kubelet 完成真正调度操作

**解决以上问题的思想是将调度单元从 Pod 修改为 PodGroup,以组的形式进行调度,实现“Gang Scheduling”**

实现 Gang Scheduling 的第一步,便是要干预 Pod 的调度逻辑。

Kubernetes 从 v1.15 版本起,为 kube-scheduler 设计了可插拔的扩展机制 —— Scheduling Framework。
Kubernetes 从 v1.15 版本起,为 kube-scheduler 设计了可插拔的扩展机制 —— Scheduling Framework。它在调度器生命周期的各个关键点上(图中绿色矩形箭头框),向用户暴露可以扩展和实现自定义调度逻辑的接口。

:::center
![](../assets/scheduling-framework-extensions.png)<br/>
![](../assets/scheduling-framework-extensions.svg)<br/>
图 7-38 Pod 的调度上下文以及调度框架公开的扩展点
:::

有了 Scheduling Framework,在保持调度“核心”简单且可维护的同时,用户可以编写自己的调度插件注册到 Scheduling Framework 的扩展点来实现自己想要的调度逻辑。

譬如你可以扩展调度队列的实现,控制每个调度的时机,在 Predicates 阶段选择满足某一组 Pod 资源的节点,实现多个 Pod 被作为一个整体调度。

值得注意的是,Scheduling Framework 属于 Kubernetes 内部扩展机制,需要按照规范编写 Golang 代码。

例如下面一个插件,实现 Filter 和 Score 扩展

```go
package main

import (
"context"
"fmt"
"k8s.io/kubernetes/pkg/scheduler/framework"
)

type MySchedulerPlugin struct{}

// NewMySchedulerPlugin creates a new plugin.
func NewMySchedulerPlugin(_ framework.Handle) (framework.Plugin, error) {
return &MySchedulerPlugin{}, nil
}

// Name returns the name of the plugin.
func (p *MySchedulerPlugin) Name() string {
return "MySchedulerPlugin"
}

// 在这里添加你的过滤逻辑
func (p *MySchedulerPlugin) Filter(ctx context.Context, pod *v1.Pod, nodeName string) *framework.Status {
//
nodeInfo, err := p.Handle().NodeInfo(nodeName)
// 判断自定义的 GPU 资源是否满足
if gpu, exists := nodeInfo.Allocatable[v1.ResourceName("nvidia.com/gpu")]; exists && gpu.Value() > 0 {
return framework.NewStatus(framework.Success, "")
}

return framework.NewStatus(framework.Success, "")
}

// 在这里添加你的打分逻辑
func (p *MySchedulerPlugin) Score(ctx context.Context, pod *v1.Pod, nodes []*v1.Node) (map[string]int64, *framework.Status) {
scores := make(map[string]int64)
for _, node := range nodes {
// 为 Node 打分
scores[node.Name] = 1 // 示例分数
}
return scores, framework.NewStatus(framework.Success, "")
}
//其他必要的插件方法...

func main() {
// 启动插件
}

```
编译完插件之后,将插件部署到 Kubernetes 集群中。然后,在 Kubernetes 的调度器配置中,指定你的插件。

```yaml
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- plugins:
score:
enabled:
- name: MySchedulerPlugin
filter:
enabled:
- name: MySchedulerPlugin
```
有了上述的设计,特别是在处理异构资源(如 GPU、FPGA 等)的时候,你思考:“扩展和自定义 Kubernetes 调度器逻辑是不是就非常容易了?”。

0 comments on commit e5309f6

Please sign in to comment.