这篇文章已经一年多了,较旧的文章可能包含过时的内容。请检查从发表以来,页面中的信息是否变得不正确。
作者: Abdullah Gharaibeh(谷歌),Aldo Culquicondor(谷歌)
无论是在本地还是在云端,集群都面临着资源使用、配额和成本管理方面的实际限制。 无论自动扩缩容能力如何,集群的容量都是有限的。 因此,用户需要一种简单的方法来公平有效地共享资源。
在本文中,我们介绍了 Kueue, 这是一个开源作业队列控制器,旨在将批处理作业作为一个单元进行管理。 Kueue 将 Pod 级编排留给 Kubernetes 现有的稳定组件。 Kueue 原生支持 Kubernetes Job API, 并提供用于集成其他定制 API 以进行批处理作业的钩子。
作业队列是在本地和云环境中大规模运行批处理工作负载的关键功能。 作业队列的主要目标是管理对多个租户共享的有限资源池的访问。 作业排队决定了哪些作业应该等待,哪些可以立即启动,以及它们可以使用哪些资源。
一些最需要的作业队列要求包括:
普通的 Kubernetes 不能满足上述要求。 在正常情况下,一旦创建了 Job,Job 控制器会立即创建 Pod,并且 kube-scheduler 会不断尝试将 Pod 分配给节点。 大规模使用时,这种情况可能会使控制平面死机。 目前也没有好的办法在 Job 层面控制哪些 Job 应该先获得哪些资源,也没有办法标明顺序或公平共享。 当前的 ResourceQuota 模型不太适合这些需求,因为配额是在资源创建时强制执行的,并且没有请求排队。 ResourceQuotas 的目的是提供一种内置的可靠性机制,其中包含管理员所需的策略,以防止集群发生故障转移。
在 Kubernetes 生态系统中,Job 调度有多种解决方案。但是,我们发现这些替代方案存在以下一个或多个问题:
借助 Kueue,我们决定采用不同的方法在 Kubernetes 上进行 Job 排队,该方法基于以下方面:
为了使这种方法可行,Kueue 需要旋钮来影响那些已建立组件的行为,以便它可以有效地管理何时何地启动一个 Job。 我们以两个特性的方式将这些旋钮添加到 Job API:
.spec.template.spec.nodeSelector。
这样,Kueue 可以控制 Pod 放置,同时仍将 Pod 到节点的实际调度委托给 kube-scheduler。请注意,任何自定义的 Job API 都可以由 Kueue 管理,只要该 API 提供上述两种能力。
Kueue 定义了新的 API 来解决本文开头提到的需求。三个主要的 API 是:
有关更多详细信息,请查看 API 概念文档。 虽然这三个 API 看起来无法抗拒,但 Kueue 的大部分操作都以 ClusterQueue 为中心; ResourceFlavor 和 LocalQueue API 主要是组织包装器。
想象一下在云上的 Kubernetes 集群上运行批处理工作负载的以下设置:
instance-type=spot 或者 instance-type=ondemand。
此外,并非所有作业都可以容忍在即用节点上运行,节点可以用 spot=true:NoSchedule 污染。作为批处理系统的管理员,你定义了两个 ResourceFlavor,它们代表两种类型的节点:
---
apiVersion: kueue.x-k8s.io/v1alpha2
kind: ResourceFlavor
metadata:
name: ondemand
labels:
instance-type: ondemand
---
apiVersion: kueue.x-k8s.io/v1alpha2
kind: ResourceFlavor
metadata:
name: spot
labels:
instance-type: spot
taints:
- effect: NoSchedule
key: spot
value: "true"
然后通过创建 ClusterQueue 来定义配额,如下所示:
apiVersion: kueue.x-k8s.io/v1alpha2
kind: ClusterQueue
metadata:
name: research-pool
spec:
namespaceSelector: {}
resources:
- name: "cpu"
flavors:
- name: ondemand
quota:
min: 1000
- name: spot
quota:
min: 2000
注意 ClusterQueue 资源中的模板顺序很重要:Kueue 将尝试根据该顺序为 Job 分配可用配额,除非这些 Job 与特定模板有明确的关联。
对于每个命名空间,定义一个指向上述 ClusterQueue 的 LocalQueue:
apiVersion: kueue.x-k8s.io/v1alpha2
kind: LocalQueue
metadata:
name: training
namespace: team-ml
spec:
clusterQueue: research-pool
管理员创建一次上述配置。批处理用户可以通过在他们的命名空间中列出 LocalQueues 来找到他们被允许提交的队列。
该命令类似于:kubectl get -n my-namespace localqueues
要提交作业,需要创建一个 Job 并设置 kueue.x-k8s.io/queue-name 注解,如下所示:
apiVersion: batch/v1
kind: Job
metadata:
generateName: sample-job-
annotations:
kueue.x-k8s.io/queue-name: training
spec:
parallelism: 3
completions: 3
template:
spec:
tolerations:
- key: spot
operator: "Exists"
effect: "NoSchedule"
containers:
- name: example-batch-workload
image: registry.example/batch/calculate-pi:3.14
args: ["30s"]
resources:
requests:
cpu: 1
restartPolicy: Never
Kueue 在创建 Job 后立即进行干预以暂停 Job。 一旦 Job 位于 ClusterQueue 的头部,Kueue 就会通过检查 Job 请求的资源是否符合可用配额来评估它是否可以启动。
在上面的例子中,Job 容忍了 Spot 资源。如果之前承认的 Job 消耗了所有现有的按需配额, 但不是所有 Spot 配额,则 Kueue 承认使用 Spot 配额的 Job。Kueue 通过向 Job 对象发出单个更新来做到这一点:
.spec.suspend 标志位为 falseinstance-type: spot 添加到 Job 的 .spec.template.spec.nodeSelector 中,
以便在 Job 控制器创建 Pod 时,这些 Pod 只能调度到 Spot 节点上。最后,如果有可用的空节点与节点选择器条件匹配,那么 kube-scheduler 将直接调度 Pod。 如果不是,那么 kube-scheduler 将 pod 初始化标记为不可调度,这将触发 cluster-autoscaler 配置新节点。
上面的示例提供了 Kueue 的一些功能简介,包括支持配额、资源灵活性以及与集群自动缩放器的集成。 Kueue 还支持公平共享、Job 优先级和不同的排队策略。 查看 Kueue 文档以了解这些特性以及如何使用 Kueue 的更多信息。
我们计划将许多特性添加到 Kueue 中,例如分层配额、预算和对动态大小 Job 的支持。 在不久的将来,我们将专注于增加对 Job 抢占的支持。
最新的 Kueue 版本在 Github 上可用; 如果你在 Kubernetes 上运行批处理工作负载(需要 v1.22 或更高版本),可以尝试一下。 这个项目还处于早期阶段,我们正在搜集大大小小各个方面的反馈,请不要犹豫,快来联系我们吧! 无论是修复或报告错误,还是帮助添加新特性或编写文档,我们欢迎一切形式的贡献者。 你可以通过我们的仓库、邮件列表或者 Slack 与我们联系。
最后是很重要的一点,感谢所有促使这个项目成为可能的贡献者们!