6

上车了!一文尽览Scheduling Framework 应用实践

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI2NDc5MDYzMA%3D%3D&%3Bmid=2247486609&%3Bidx=1&%3Bsn=afb21c6ef6e29eed8f681063a1941240
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Mrauq26.jpg!mobile

文|沙同学

导读: Kubernetes 是目前最受欢迎的⾃动化容器管理平台,它提供了灵活的声明式容器编排、自动部署、资源调度等功能。 Kube-Scheduler 作为 Kubernetes 的核心组件之一,主要负责整个集群资源的调度功能,根据特定的调度算法和策略,将 Pod 调度到最优的工作节点上面去,从而更加合理、充分地利用集群资源。

但是随着 Kubernetes 部署的任务类型越来越多,原生 Kube-Scheduler 已经不能应对多样 的调度需求:比如机器学习、深度学习训练任务对于协同调度功能的需求;高性能计算作业,基因计算工作流对于一些动态资源 GPU、网络、存储卷的动态资源绑定需求等。因此自定义 Kubernetes 调度器的需求愈发迫切,本文讨论了扩展 Kubernetes 调度程序的各种方法,然后使用目前最佳的扩展方式 Scheduling Framework,演示如何扩展 Scheduler。

01

自定义调度器的方式

BzUnEzR.png!mobile

02

Scheduling Framework 解析

如下图所示,调度框架提供了丰富的扩展点,在这幅图中,Filter 相当于之前 Predicate 预选模块, Score 相当于 Priority 优选模块,每一个扩展点模块都提供了接口,我们可以实现扩展点定义的接口来实现自己的调度逻辑,并将实现的插件注册到扩展点。

Scheduling Framework 在执行调度流程时,当运行到扩展点时,会调用我们注册的插件,通过执行自定义插件的策略,满足调度需求。此外,一个插件可以在多个扩展点注册,用以执行更复杂或有状态的任务。

2iENveE.png!mobile

Scheduling Framework 每次调度一个 Pod ,都分为调度周期和绑定周期两部分来执行,调度周期为 Pod 选择一个节点,绑定周期则调用 Apiserver,用选好的 Node,更新 Pod 的 spec.nodeName 字段。调度周期和绑定周期统称为 “Scheduling Context” (调度上下文)。调度周期是串行运行的,同一时间只能有一个 Pod 被调度,是线程安全的;而绑定周期因为需要访问 Apiserver 的接口,耗时较长,为了提高调度的效率,需要异步执行,即同一时间,可执行多个 bind 操作,是非线程安全的。

如果 Pod 被确定为不可调度或存在内部错误,那么调度周期或绑定周期将被中止。Pod 将返回队列并等待下一次重试。如果一个绑定周期被终止,它将触发 Reserve 插件中的 UnReserve 方法。

Scheduling Cycle 的扩展点

QueueSort

用于给调度队列排序,默认情况下,所有的 Pod 都会被放到一个队列中,此扩展用于对 Pod 的待调度队列进行排序,以决定先调度哪个 Pod,QueueSort 扩展本质上只需要实现一个方法  Less(Pod1, Pod2)  用于比较两个 Pod 谁更优先获得调度,同一时间点只能有一个 QueueSort 插件生效。

PreFilter

用于对 Pod 的信息进行预处理,或者检查一些集群或 Pod 必须满足的前提条件,比如 Pod 是否包含指定的 annotations 或 labels,如果 PreFilter 返回了 error ,则调度过程终止。

Filter

用于排除那些不能运行该 Pod 的节点,对于每一个节点,调度器将按顺序执行 Filter 扩展,如果任何一个 Filter 将节点标记为不可选,则余下的 Filter 扩展将不会被执行。如果对默认调度器提供的预选规则不满意,可以在配置中禁用默认调度器的预选算法,在这个扩展点只执行自己自定义的过滤逻辑。Node 节点执行 Filter 策略是并发执行的,所以在同一调度周期中多次调用过滤器。

PostFilter

实现此扩展的插件是在 Filter 阶段之后被调用,仅当没有为 Pod 找到可行的节点时才调用。如果有任何 PostFilter 插件将节点标记为可调度节点,则后面的 PostFilter 插件就不会被调用了。一个典型的 PostFilter 实现是抢占,它试图通过抢占其他 Pod 来使 Pod 可调度。

PreScore

在预选后被调用,通常用来在 Score 之前进行一些信息生成或者记录日志和监控信息

Score

实现此扩展的插件为已通过过滤阶段的所有节点进行打分,调度器将针对每一个节点调用 Score 扩展。

NormalizeScore

在 NormalizeScore 阶段,调度器将会把每个 Score 扩展对具体某个节点的评分结果和该扩展的权重合并起来,作为最终评分结果,评分结果是一个范围内的整数。如果 Score 或 NormalizeScore 返回错误,则调度周期将中止。

Reserve

此扩展点为 Pod 预留的在要运行节点上的资源,目的是避免调度器在等待 Pod 与节点绑定的过程中调度新的 Pod 到节点上时,发生实际使用资源超出可用资源的情况。(因为绑定 Pod 到节点上是异步发生的)。这是调度过程的最后一个步骤,Pod 进入 Reserved 状态以后,要么在绑定失败时,触发 Unreserve 扩展,要么在绑定成功时,由 PostBind 扩展结束绑定过程。

Permit

Permit 扩展,发生在 Pod 使用 Reserve 插件预留资源之后, Bind 扩展点 bind 之前,主要有三种策略,批准、拒绝、等待。

1)approve(批准):当所有的 Permit 扩展都批准了 Pod 与节点的绑定,调度器将继续执行绑定过程

2)deny(拒绝):如果任何一个 Permit 扩展 deny 了 Pod 与节点的绑定,Pod 将被放回到待调度队列,此时将触发 Unreserve 扩展

3)wait(等待):如果一个 Permit 扩展返回了 wait,则 Pod 将保持在 Permit 阶段,直到被其他扩展 approve,如果超时事件发生,wait 状态变成 deny,Pod 将被放回到待调度队列,此时将触发 UnReserve 扩展

Binding Cycle 的扩展点

PreBind

扩展用于在 Pod 绑定之前执行某些逻辑。这个插件引入的原因,是有一些资源,是在不在调度 Pod 时,立即确定可用的节点的资源,所以调度程序需要确保,这些资源已经成功绑定到选定的节点后,才能将 Pod 调度到此节点。例如,PreBind 扩展可以将一个基于网络的数据卷挂载到节点上,以Pod 可以使用。如果任何一个 PreBind 扩展返回错误,Pod 将被放回到待调度队列,此时将触发 Unreserve 扩展。

Bind

Bind 扩展会调用 apiserver 提供的接口,将 Pod 绑定到对应的节点上。

PostBind

PostBind 是一个信息扩展点。成功绑定 Pod 后,将调用 PostBind 插件,可用于清理关联的资源。

UnReserve

是一个通知性质的扩展,如果为 Pod 预留了资源,Pod 又在被绑定过程中被拒绝绑定,则 Unreserve 扩展将被调用。Unreserve 扩展应该释放已经为 Pod 预留的节点上的计算资源。在一个插件中,Reserve 扩展和 UnReserve 扩展应该成对出现。

03

使用 Scheduling Framework 自定义 Scheduler

自定义插件需要两个步骤:

1)实现插件的接口

2)注册插件并配置插件

3.1 实现插件的接口

这里我们实现 QueueSort 扩展点,先看看 QueueSort 扩展点定义的接口:

// QueueSortPlugin is an interface that must be implemented by "QueueSort" plugins.
// These plugins are used to sort pods in the scheduling queue. Only one queue sort
// plugin may be enabled at a time.
type QueueSortPlugin interface {
Plugin
// Less are used to sort pods in the scheduling queue.
Less(*QueuedPodInfo, *QueuedPodInfo) bool
}

默认的调度器会优先调度优先级较高的 Pod , 其具体实现的方式是使用 QueueSort 这个插件,默认的实现,是对 Pod 的 Priority 值进行排序,但当优先级相同时,再比较 Pod 的 timestamp ,  timestamp 是 Pod 加入 queue 的时间。我们现在想先根据 Pod 的 Priority 值进行排序,当 Priority 值相同,再根据 Pod 的 QoS 类型进行排序,最后再根据 Pod 的 timestamp 排序。

Qos 类型如下:

1)Guaranteed : resources limits 和 requests 相等

2)Burstable : resources limits 和 requests 不相等

3)BestEffort : 未设置 resources limits 和 requests

具体是,Guaranteed 优先级 高于 Burstable,Burstable 优先级高于 BestEffort。

插件的实现,其实我们只需要实现 QueueSortPlugin 的 Less 方法:

package qos


import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/v1/pod"
v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos"
framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
)


// Name is the name of the plugin used in the plugin registry and configurations.
const Name = "QOSSort"


// Sort is a plugin that implements QoS class based sorting.
type Sort struct{}


var _ framework.QueueSortPlugin = &Sort{}


// Name returns name of the plugin.
func (pl *Sort) Name() string {
return Name
}


// Less is the function used by the activeQ heap algorithm to sort pods.
// It sorts pods based on their priorities. When the priorities are equal, it uses
// the Pod QoS classes to break the tie.
func (*Sort) Less(pInfo1, pInfo2 *framework.PodInfo) bool {
p1 := pod.GetPodPriority(pInfo1.Pod)
p2 := pod.GetPodPriority(pInfo2.Pod)
return (p1 > p2) || (p1 == p2 && compQOS(pInfo1.Pod, pInfo2.Pod)) || (p1 == p2 && pInfo1.Timestamp.Before(pInfo2.Timestamp))
}


func compQOS(p1, p2 *v1.Pod) bool {
p1QOS, p2QOS := v1qos.GetPodQOS(p1), v1qos.GetPodQOS(p2)
if p1QOS == v1.PodQOSGuaranteed {
return true
}
if p1QOS == v1.PodQOSBurstable {
return p2QOS != v1.PodQOSGuaranteed
}
return p2QOS == v1.PodQOSBestEffort
}


// New initializes a new plugin and returns it.
func New(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error) {
return &Sort{}, nil
}

注意:一个 Plugin 可以实现多个扩展点。即在一个 Plugin 中既可以实现 Filter,又可以实现 Score,也可以再实现 PreBind。

3.2 注册和配置插件

1)注册指向默认调度器中注册插件。

2)配置是通过配置来决定哪些插件需要被初始化。

3.2.1 在 Sheduler 中注册已经实现的 Qos 插件

func main() {
rand.Seed(time.Now().UnixNano())
command := app.NewSchedulerCommand(
app.WithPlugin(qos.Name, qos.New),
)


logs.InitLogs()
defer logs.FlushLogs()


if err := command.Execute(); err != nil {
os.Exit(1)
}
}

3.2.2 通过配置来使用 Qos 插件,并部署自定义调度器

1)配置

通过配置让 Sheduler 知道那些插件需要被初始化,如下面指定了 QueueSort 的插件 Qos,其他的扩展点的插件没有被指定,则都会 Kube-Scheduler 的默认的实现。可以看到,schedulerName 字段代表扩展的调度器名称, plugins 字段中各个扩展点的插件名称,enable 代表该扩展点关于运行你的插件。

apiVersion: v1
kind: ConfigMap
metadata:
name: scheduler-config3
namespace: kube-system
data:
scheduler-config.yaml: |
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
schedulerName: qos-scheduler
leaderElection:
leaderElect: true
lockObjectName: qos-scheduler
lockObjectNamespace: kube-system
plugins:
queueSort:
enabled:
- name: "QOSSort"

2)接着为调度器创建 RBAC

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: qos-cr
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '*'
verbs:
- '*'
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: qos-sa
namespace: kube-system
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: qos-crb
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: qos-cr
subjects:
- kind: ServiceAccount
name: qos-sa
namespace: kube-system

3)配置调度器的 Deployment 

apiVersion: apps/v1
kind: Deployment
metadata:
name: qos-scheduler
namespace: kube-system
labels:
component: qos-scheduler
spec:
replicas: 1
selector:
matchLabels:
component: qos-scheduler
template:
metadata:
labels:
component: qos-scheduler
spec:
imagePullSecrets:
- name: hybrid-regsecret
serviceAccount: qos-sa
priorityClassName: system-cluster-critical
volumes:
- name: scheduler-config3
configMap:
name: scheduler-config3
containers:
- name: qos-scheduler
image: hub.baidubce.com/kun/sxy/qos-scheduler:v1.0.0
imagePullPolicy: Always
args:
- /qos-sample-scheduler
- --config=/scheduler/scheduler-config.yaml
- --v=3
resources:
requests:
cpu: "50m"
volumeMounts:
- name: scheduler-config3
mountPath: /scheduler

4)使用 kubectl apply 部署后,可以看到 qos-scheduler 已经启动了

$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
qos-scheduler-79c767954f-225mr 1/1 Running 0 44m

5) 使用自定义调度器调度 Pod

在 Pod 中的 spec.schedulerName 上指定 qos-scheduler,自定义调度器将会为 Pod 执行调度逻辑。

apiVersion: v1
kind: Pod
metadata:
name: test
labels:
app: test
spec:
schedulerName: qos-scheduler
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80

创建后,可以看到 Pod 已经被正常调度,并启动成功。

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
test 1/1 Running 0 15s

04

使用调度框架的其他示例

4.1 Coscheduling(协同调度)

有时候,我们需要使用协同调度,类似于 Kube-batch 的功能(也称为“ Gang 调度”)。Gang 调度允许同时安排一定数量的 Pod 。如果 Gang 的所有成员不能同时调度,他们都不应该调度。Scheduling Framework 中的 Gang 调度可以使用 “Permit” 插件完成。

1)主调度线程逐个处理 Pod 并为它们预留节点,每个 Pod 都会调用准入阶段的 Gang 调度插件。

2) 当它发现 Pod 属于一个 Gang 时,它会检查 Gang 的属性。如果没有足够的成员定期或处于“等待”状态,该插件返回“等待”。

3) 当数字达到期望值时,处于等待状态的所有 Pod 均被批准并发送进行绑定。

4.2 Dynamic Resource Binding(动态资源绑定)

在调度 Pod 时,有一些动态的资源比如 volumn ,还不属于备选节点的资源,调度程序需要确保此类集群级资源绑定到选定的节点,然后才能将pod调度到有此类资源的节点的节点。可以使用 Scheduling Framework 的 PreBind 扩展点实现插件,完成这种动态资源的绑定功能。

05

总结

本文首先分析了几种扩展调度器方式的优缺点,之后介绍了 Scheduling Framework 的实现原理,以及如何基于 Scheduling Framework 去实现一个自定义插件。Scheduling Framework 作为目前扩展 Kubernetes 调度器的最佳方式,采用插件和插件扩展点机制,可以轻松对调度器进行扩展、定制。未来,会用更多的调度需求使用 Scheduling Framework 进行扩展。

06

落地

Kubernetes 技术已然成为了容器编排领域的事实标准。百度从 2010 年开始探索容器和集群管理技术,2016年自主研发的 Matrix 集群管理系统已经管理了数十万台机器和服务。

随着 Kubernetes 技术的成熟,我们看到了开源技术强大的生命力,从 2018 年开始尝试向 Kubernetes 架构演化。试点成功之后,启动了大规模 Kubernetes 架构融合项目。一方面保留百度在 Matrix 上积累的核心技术能力,另一方面让存量业务可以更低成本的迁移到 Kubernetes 之上。

百度于 2020 年获得 InfoQ 十大云原生创新技术方案,对百度云原生来说仅仅是个开始。目前大规模 Kubernetes 融合架构的业务正在百度云原生各产品技术架构中稳定运行并持续增长,百度云原生团队也将会在继续服务好客户的同时,利用Kubernetes技术实践经验不断优化产品,更好地助力各行各业的客户实现基于云原生架构的数字化转型。

百度智能云云原生平台,为客户建设容器化和无服务器化的基础设施,提供企业级的微服务治理能力,同时集成源自百度自身多年实践的DevOps工具链。保障开发者享受到高效、灵活、弹性的开发与运维体验,助力企业更高效率低风险地构建云原生应用,广泛应用于金融、互联网、制造等各行各业的云原生转型阶段。

点击 【阅读原文】 ,查看 百度云原生解决方案 详情。

推荐阅读:

Kubernetes 1.20:最优秀、美妙、酷的版本

最新!最全面!Istio 服务网格部署实践

还在手写 Operator?是时候使用 Kubebuilder 了

重磅!云原生计算交流群成立

扫码添加小助手即可申请加入,一定要备注: 名字-公司/学校-地区 ,根据格式备注,才能通过且邀请进群。

qyEV3ar.jpg!mobile

6fAJreA.png!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK