本文发布已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。

Kueue 简介

无论是本地还是云端,集群都面临着资源使用、配额和成本管理方面的实际约束。无论自动扩缩功能如何,集群的容量都是有限的。因此,用户希望有一种简单的方法来公平有效地共享资源。

在本文中,我们介绍 Kueue,一个开源作业排队控制器,旨在将批量作业作为一个单元进行管理。Kueue 将 Pod 级别的编排留给 Kubernetes 的现有稳定组件。Kueue 原生支持 Kubernetes Job API,并为集成其他自定义构建的批量作业 API 提供钩子。

为什么选择 Kueue?

作业排队是在本地和云环境中大规模运行批量工作负载的关键功能。作业排队的主要目标是管理对多个租户共享的有限资源池的访问。作业排队决定哪些作业应该等待,哪些可以立即启动,以及它们可以使用哪些资源。

一些最需要的作业排队要求包括

  • 配额和预算,以控制谁可以使用什么以及使用到什么限制。这不仅在具有静态资源的集群(如本地)中需要,而且在云环境中也需要控制支出或稀缺资源的使用。
  • 租户之间公平共享资源。为了最大化可用资源的使用,应允许在非活动租户之间公平共享分配给非活动租户的任何未使用配额。
  • 基于可用性,在不同资源类型之间灵活放置作业。这在云环境中非常重要,因为云环境具有异构资源,例如不同的架构(GPU 或 CPU 模型)和不同的配置模式(竞价实例与按需实例)。
  • 支持按需配置资源的自动扩展环境。

普通的 Kubernetes 不满足上述要求。在正常情况下,一旦创建 Job,job-controller 会立即创建 Pod,而 kube-scheduler 会不断尝试将 Pod 分配给节点。在规模化的情况下,这种情况可能会使控制平面不堪重负。目前也没有好的方法在作业级别控制哪些作业应该首先获得哪些资源,也没有办法表达顺序或公平共享。当前的 ResourceQuota 模型不适合这些需求,因为配额是在资源创建时强制执行的,并且没有请求排队。ResourceQuotas 的目的是提供一个内置的可靠性机制,以及管理员保护集群免受故障所需的策略。

在 Kubernetes 生态系统中,有几种作业调度解决方案。但是,我们发现这些替代方案存在一个或多个以下问题

  • 它们替换了 Kubernetes 的现有稳定组件,如 kube-scheduler 或 job-controller。这不仅从运营角度来看是有问题的,而且 Job API 的重复也会导致生态系统碎片化并降低可移植性。
  • 它们不与自动缩放集成,或者
  • 它们缺乏对资源灵活性的支持。

Kueue 的工作原理

使用 Kueue,我们决定采用一种不同的方法来在 Kubernetes 上进行作业排队,该方法围绕以下几个方面

  • 不重复由已建立的 Kubernetes 组件(用于 Pod 调度、自动缩放和作业生命周期管理)提供的现有功能。
  • 为现有组件添加缺少的功能。例如,我们投资于 Job API,以涵盖更多用例,例如 IndexedJob与 Pod 跟踪相关的长期存在的问题。虽然此路径需要更长的时间才能实现功能,但我们认为它是更可持续的长期解决方案。
  • 确保与计算资源具有弹性和异构性的云环境兼容。

为了使此方法可行,Kueue 需要旋钮来影响这些已建立组件的行为,以便它可以有效地管理何时以及何处启动作业。我们以两个功能的形式将这些旋钮添加到 Job API 中

  • Suspend 字段,它允许 Kueue 向 job-controller 发出信号,指示何时启动或停止 Job。
  • 可变调度指令,允许 Kueue 在启动 Job 之前更新 Job 的 .spec.template.spec.nodeSelector。这样,Kueue 可以在仍然将实际的 Pod 到节点的调度委托给 kube-scheduler 的同时控制 Pod 的放置。

请注意,如果自定义作业 API 提供上述两个功能,则可以由 Kueue 管理。

资源模型

Kueue 定义了新的 API,以解决本文开头提到的要求。三个主要 API 是

  • ResourceFlavor:一个集群范围的 API,用于定义可用于消费的资源类型,如 GPU 模型。其核心是,ResourceFlavor 是一组标签,用于镜像提供这些资源的节点上的标签。
  • ClusterQueue:一个集群范围的 API,用于通过为一种或多种 ResourceFlavor 设置配额来定义资源池。
  • LocalQueue:一个命名空间 API,用于分组和管理单个租户作业。最简单的形式是,LocalQueue 是指向租户(建模为命名空间)可用于启动其作业的 ClusterQueue 的指针。

有关更多详细信息,请查看 API 概念文档。虽然这三个 API 可能看起来令人眼花缭乱,但 Kueue 的大多数操作都以 ClusterQueue 为中心;ResourceFlavor 和 LocalQueue API 主要用于组织封装。

用例示例

想象一下,在云端的 Kubernetes 集群上运行批量工作负载的以下设置

  • 您已经在集群中安装了 cluster-autoscaler,以自动调整集群的大小。
  • 有两种类型的自动缩放节点组,它们的配置策略不同:竞价实例和按需实例。每个组的节点都通过标签 instance-type=spotinstance-type=ondemand 来区分。此外,由于并非所有作业都可以在竞价实例节点上运行,因此节点会标记为 spot=true:NoSchedule
  • 为了在成本和资源可用性之间取得平衡,假设您希望作业使用最多 1000 个按需实例节点的内核,然后使用最多 2000 个竞价实例节点的内核。

作为批量系统的管理员,您可以定义两种 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 将尝试根据顺序将作业放入可用配额中,除非作业与特定类型具有显式亲和性。

对于每个命名空间,您定义一个指向上述 ClusterQueue 的 LocalQueue

apiVersion: kueue.x-k8s.io/v1alpha2
kind: LocalQueue
metadata:
  name: training
  namespace: team-ml
spec:
  clusterQueue: research-pool

管理员创建上述设置一次。批量用户可以通过列出其命名空间中的 LocalQueue 来找到他们可以提交到的队列。该命令类似于以下命令: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 位于 ClusterQueue 的头部,Kueue 会通过检查作业请求的资源是否适合可用配额来评估它是否可以启动。

在上面的示例中,Job 容忍竞价实例资源。如果有之前已接纳的作业消耗了所有现有的按需配额,但没有消耗所有竞价实例的配额,则 Kueue 会使用竞价实例配额接纳该作业。Kueue 通过对 Job 对象发出单个更新来执行此操作

  • .spec.suspend 标志更改为 false
  • 将术语 instance-type: spot 添加到作业的 .spec.template.spec.nodeSelector,以便当作业控制器创建 Pod 时,这些 Pod 只能调度到竞价实例节点上。

最后,如果有具有匹配节点选择器术语的可用空节点,则 kube-scheduler 将直接调度 Pod。如果没有,则 kube-scheduler 最初会将 Pod 标记为不可调度,这将触发 cluster-autoscaler 配置新节点。

未来的工作和参与方式

上面的示例概述了 Kueue 的一些功能,包括对配额、资源灵活性以及与集群自动缩放器的集成的支持。Kueue 还支持公平共享、作业优先级和不同的排队策略。查看 Kueue 文档,以了解有关这些功能以及如何使用 Kueue 的更多信息。

我们计划向 Kueue 添加许多功能,例如分层配额、预算和对动态大小的作业的支持。在更近的将来,我们将专注于添加对作业抢占的支持。

最新的 Kueue 版本可在 Github 上获得;如果您在 Kubernetes 上运行批量工作负载(需要 v1.22 或更高版本),请尝试一下。我们正处于这个项目的早期阶段,我们正在寻求各级(无论是大还是小)的反馈,所以请随时与我们联系。我们也欢迎其他贡献者,无论是修复或报告错误,还是帮助添加新功能或编写文档。您可以通过我们的 存储库邮件列表Slack 与我们联系。

最后但同样重要的是,感谢所有使这个项目成为可能的 我们的贡献者