RayJob 的 Gang 调度和优先级调度与 Kueue#

本指南演示了在本地 Kind 集群上使用 RayJob 进行 KubeRay 和 Kueue 的 gang 和优先级调度。请参考 使用 RayJob 和 Kueue 进行优先级调度使用 RayJob 和 Kueue 进行 Gang 调度 以了解实际用例。

Kueue#

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

  • 使任务等待。

  • 要允许一个作业启动,这将触发 Kubernetes 创建 Pod。

  • 要抢占一个作业,这将触发 Kubernetes 删除活动 Pod。

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

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

kind create cluster

步骤 1:安装 KubeRay 操作员#

按照 部署 KubeRay 操作员 来从 Helm 仓库安装最新稳定的 KubeRay 操作员。

步骤 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。此策略允许待处理的 RayJob,如果其在 ClusterQueue 的名义配额内无法容纳,则可以抢占 ClusterQueue 中优先级较低的活跃 RayJob 自定义资源。

  • LocalQueue

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

  • 工作负载优先级类

    • 工作负载优先级类 prod-priority 的值高于工作负载优先级类 dev-priority。具有 prod-priority 优先级类的 RayJob 自定义资源优先于具有 dev-priority 优先级类的 RayJob 自定义资源。

创建 Kueue 资源:

kubectl apply -f kueue-resources.yaml

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

Kueue 始终以“gang”模式接纳工作负载。Kueue 以“全有或全无”的方式接纳工作负载,确保 Kubernetes 永远不会部分配置 RayJob 或 RayCluster。使用 gang 调度策略来避免因工作负载调度效率低下而导致的计算资源浪费。

从 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 个头节点 Pod 和 1 个工作节点 Pod,每个 Pod 请求 1 个 CPU 和 2G 内存。

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

  • shutdownAfterJobFinishes 设置为 true 以启用 RayJob 的自动清理。此设置会触发 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 自定义资源保持挂起状态,即使剩余资源足以创建一个 Pod,KubeRay 也不会从挂起的 RayJob 创建 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

Kueue 在第一个 RayJob 自定义资源完成后,允许挂起的 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 优先级类。使用较低优先级类 dev-priority 创建一个 RayJob:

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

在创建高优先级类 prod-priority 的 RayJob 之前,请使用以下内容修改 RayJob 元数据:

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

使用更高优先级类 prod-priority 创建一个 RayJob:

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

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