使用 Kueue 对 RayJob 进行组调度和优先级调度#

本指南演示了如何在本地 Kind 集群上使用 Kueue 对 RayJob 进行组调度和优先级调度,以及 KubeRay 和 Kueue 的集成。请参考使用 RayJob 和 Kueue 进行优先级调度使用 RayJob 和 Kueue 进行组调度了解实际用例。

Kueue#

Kueue 是一个 Kubernetes 原生的作业队列系统,用于管理配额以及作业如何消耗配额。Kueue 决定何时

  • 让作业等待。

  • 允许作业启动,这会触发 Kubernetes 创建 Pod。

  • 抢占作业,这会触发 Kubernetes 删除活动的 Pod。

Kueue 原生支持某些 KubeRay API。具体来说,你可以使用 Kueue 来管理 RayJob 和 RayCluster 消耗的资源。请参阅 Kueue 文档了解更多信息。

步骤 0:创建一个 Kind 集群#

kind create cluster

步骤 1:安装 KubeRay Operator#

按照部署 KubeRay Operator的说明,从 Helm 仓库安装最新的稳定版 KubeRay Operator。

步骤 2:安装 Kueue#

VERSION=v0.6.0
kubectl apply --server-side -f https://github.com/kubernetes-sigs/kueue/releases/download/$VERSION/manifests.yaml

请参阅Kueue 安装了解安装 Kueue 的更多详细信息。Kueue 和 RayJob 之间存在一些限制。请参阅Kueue 的限制了解更多详细信息。

步骤 3:创建 Kueue 资源#

# kueue-resources.yaml
apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: "default-flavor"
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "cluster-queue"
spec:
  preemption:
    withinClusterQueue: LowerPriority
  namespaceSelector: {} # Match all namespaces.
  resourceGroups:
  - coveredResources: ["cpu", "memory"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 3
      - name: "memory"
        nominalQuota: 6G
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  namespace: "default"
  name: "user-queue"
spec:
  clusterQueue: "cluster-queue"
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: WorkloadPriorityClass
metadata:
  name: prod-priority
value: 1000
description: "Priority class for prod jobs"
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: WorkloadPriorityClass
metadata:
  name: dev-priority
value: 100
description: "Priority class for development jobs"

YAML 清单配置了

  • ResourceFlavor

    • ResourceFlavor default-flavor 是一个空的 ResourceFlavor,因为 Kubernetes 集群中的计算资源是同质的。换句话说,用户可以请求 1 个 CPU,而无需考虑它是 ARM 芯片还是 x86 芯片。

  • ClusterQueue

    • ClusterQueue cluster-queue 只有一个 ResourceFlavor default-flavor,配额为 3 个 CPU 和 6G 内存。

    • ClusterQueue cluster-queue 有一个抢占策略 withinClusterQueue: LowerPriority。该策略允许不符合其 ClusterQueue 名义配额的待处理 RayJob 抢占 ClusterQueue 中优先级较低的活动 RayJob 自定义资源。

  • LocalQueue

    • LocalQueue user-queuedefault 命名空间中的一个命名空间对象,属于一个 ClusterQueue。通常的做法是为一个组织租户、团队或用户分配一个命名空间。用户将作业提交到 LocalQueue,而不是直接提交到 ClusterQueue。

  • WorkloadPriorityClass

    • WorkloadPriorityClass prod-priority 的值高于 WorkloadPriorityClass dev-priority。使用 prod-priority 优先级类的 RayJob 自定义资源优先于使用 dev-priority 优先级类的 RayJob 自定义资源。

创建 Kueue 资源

kubectl apply -f kueue-resources.yaml

步骤 4:使用 Kueue 进行组调度#

Kueue 总是以“组”模式准入工作负载。Kueue 基于“全有或全无”的方式准入工作负载,确保 Kubernetes 永远不会部分配置 RayJob 或 RayCluster。使用组调度策略避免由于低效的工作负载调度造成的计算资源浪费。

从 KubeRay 仓库下载 RayJob YAML 清单。

curl -LO https://raw.githubusercontent.com/ray-project/kuberay/master/ray-operator/config/samples/ray-job.kueue-toy-sample.yaml

在创建 RayJob 之前,修改 RayJob 元数据如下

metadata:
  generateName: rayjob-sample-
  labels:
    kueue.x-k8s.io/queue-name: user-queue
    kueue.x-k8s.io/priority-class: dev-priority

创建两个具有相同优先级 dev-priority 的 RayJob 自定义资源。请注意 RayJob 自定义资源的以下重要事项

  • RayJob 自定义资源包含 1 个 head Pod 和 1 个 worker Pod,每个 Pod 请求 1 个 CPU 和 2G 内存。

  • 该 RayJob 运行一个简单的 Python 脚本,演示一个循环,执行 600 次迭代,打印迭代次数并每次迭代休眠 1 秒。因此,提交的 Kubernetes Job 启动后,RayJob 大约会运行 600 秒。

  • 将 RayJob 的 shutdownAfterJobFinishes 设置为 true,以启用自动清理。此设置会触发 KubeRay 在 RayJob 完成后删除 RayCluster。

    • Kueue 不处理 shutdownAfterJobFinishes 设置为 false 的 RayJob 自定义资源。请参阅Kueue 的限制了解更多详细信息。

kubectl create -f ray-job.kueue-toy-sample.yaml
kubectl create -f ray-job.kueue-toy-sample.yaml

每个 RayJob 自定义资源总共请求 2 个 CPU 和 4G 内存。然而,ClusterQueue 总共只有 3 个 CPU 和 6G 内存。因此,第二个 RayJob 自定义资源保持待处理状态,并且 KubeRay 不会从待处理的 RayJob 创建 Pod,即使剩余资源足以创建一个 Pod。你也可以检查 ClusterQueue 查看可用和已使用的配额

$ kubectl get clusterqueues.kueue.x-k8s.io
NAME            COHORT   PENDING WORKLOADS
cluster-queue            1

$ kubectl get clusterqueues.kueue.x-k8s.io cluster-queue -o yaml
Status:
  Admitted Workloads:  1 # Workloads admitted by queue.
  Conditions:
    Last Transition Time:  2024-02-28T22:41:28Z
    Message:               Can admit new workloads
    Reason:                Ready
    Status:                True
    Type:                  Active
  Flavors Reservation:
    Name:  default-flavor
    Resources:
      Borrowed:  0
      Name:      cpu
      Total:     2
      Borrowed:  0
      Name:      memory
      Total:     4Gi
  Flavors Usage:
    Name:  default-flavor
    Resources:
      Borrowed:         0
      Name:             cpu
      Total:            2
      Borrowed:         0
      Name:             memory
      Total:            4Gi
  Pending Workloads:    1
  Reserving Workloads:  1

当第一个 RayJob 自定义资源完成时,Kueue 会准入待处理的 RayJob 自定义资源。检查 RayJob 自定义资源的状态并在它们完成后删除它们

$ kubectl get rayjobs.ray.io
NAME                  JOB STATUS   DEPLOYMENT STATUS   START TIME             END TIME               AGE
rayjob-sample-ckvq4   SUCCEEDED    Complete            xxxxx                  xxxxx                  xxx
rayjob-sample-p5msp   SUCCEEDED    Complete            xxxxx                  xxxxx                  xxx

$ kubectl delete rayjob rayjob-sample-ckvq4
$ kubectl delete rayjob rayjob-sample-p5msp

步骤 5:使用 Kueue 进行优先级调度#

此步骤首先创建一个优先级较低的 RayJob dev-priority,然后创建一个优先级较高的 RayJob prod-priority。优先级较高的 RayJob prod-priority 优先于优先级较低的 RayJob dev-priority。Kueue 抢占优先级较低的 RayJob,以准入优先级较高的 RayJob。

如果你完成了上一步,则 RayJob YAML 清单 ray-job.kueue-toy-sample.yaml 应该已经设置为 dev-priority 优先级类。创建一个优先级较低的 RayJob dev-priority

kubectl create -f ray-job.kueue-toy-sample.yaml

在创建优先级较高的 RayJob prod-priority 之前,修改 RayJob 元数据如下

metadata:
  generateName: rayjob-sample-
  labels:
    kueue.x-k8s.io/queue-name: user-queue
    kueue.x-k8s.io/priority-class: prod-priority

创建一个优先级较高的 RayJob prod-priority

kubectl create -f ray-job.kueue-toy-sample.yaml

你可以看到 KubeRay operator 删除了属于优先级较低的 RayJob dev-priority 的 Pod,并创建了属于优先级较高的 RayJob prod-priority 的 Pod。