Gang 调度与 RayJob 和 Kueue#

本指南演示了如何使用 Kueue 进行 RayJob 资源的 gang 调度,并利用 Kubernetes 上的动态资源预配和排队。为了说明这些概念,本指南使用了 使用 Ray Data 微调 PyTorch Lightning 文本分类器 示例。

Gang 调度#

Kubernetes 中的 gang 调度可确保一组相关的 Pod(例如 Ray 集群中的 Pod)仅在所有必需资源都可用时才启动。当处理 GPU 等昂贵且有限的资源时,满足此要求至关重要。

Kueue#

Kueue 是一个 Kubernetes 原生系统,用于管理配额及其消费方式。Kueue 决定何时

  • 让作业等待。

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

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

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

为何使用 gang 调度#

当处理 GPU 等昂贵且有限的硬件加速器时,gang 调度至关重要。它可以防止 RayJobs 部分预配 Ray 集群并声称但未使用 GPU。Kueue 会暂停 RayJob,直到 Kubernetes 集群和底层云提供商能够保证 RayJob 执行所需的容量。这种方法极大地提高了 GPU 利用率和成本,尤其是在 GPU 可用性有限的情况下。

在 GKE 中创建 Kubernetes 集群#

使用 enable-autoscaling 选项创建 GKE 集群

gcloud container clusters create kuberay-gpu-cluster \
    --num-nodes=1 --min-nodes 0 --max-nodes 1 --enable-autoscaling \
    --zone=us-east4-c --machine-type e2-standard-4

创建启用了 enable-queued-provisioning 选项的 GPU 节点池

gcloud container node-pools create gpu-node-pool \
  --accelerator type=nvidia-l4,count=1,gpu-driver-version=latest \
  --enable-queued-provisioning \
  --reservation-affinity=none  \
  --zone us-east4-c \
  --cluster kuberay-gpu-cluster \
  --num-nodes 0 \
  --min-nodes 0 \
  --max-nodes 10 \
  --enable-autoscaling \
  --machine-type g2-standard-4

此命令创建一个节点池,该节点池最初有零个节点。--enable-queued-provisioning 标志使用 ProvisioningRequest API 在 Kubernetes 节点自动缩放器中启用了“排队预配”。下面有更多详细信息。您需要使用 --reservation-affinity=none 标志,因为 GKE 不支持与 ProvisioningRequest 节点预留。

安装 KubeRay 运算符#

请按照 部署 KubeRay Operator 从 Helm 仓库安装最新的稳定版 KubeRay Operator。如果您正确设置了 GPU 节点池的污点,KubeRay Operator Pod 必须位于 CPU 节点上。

安装 Kueue#

安装最新发布的 Kueue 版本。

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

有关安装 Kueue 的更多详细信息,请参阅 Kueue 安装

配置 Kueue 进行 gang 调度#

接下来,配置 Kueue 进行 gang 调度。Kueue 利用 ProvisioningRequest API 来执行两项关键任务:

  1. 当作业需要更多资源时,动态地向集群添加新节点。

  2. 阻止 admission 需要足够资源的作业。

有关更多详细信息,请参阅 ProvisioningRequest 的工作原理

创建 Kueue 资源#

此清单创建了以下资源:

  • ClusterQueue:定义配额和公平共享规则。

  • LocalQueue:一个属于租户的命名空间队列,引用 ClusterQueue。

  • ResourceFlavor:定义集群中可用的资源,通常来自节点。

  • AdmissionCheck:一种允许组件影响工作负载 admission 时机的机制。

# kueue-resources.yaml
apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: "default-flavor"
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: AdmissionCheck
metadata:
  name: rayjob-gpu
spec:
  controllerName: kueue.x-k8s.io/provisioning-request
  parameters:
    apiGroup: kueue.x-k8s.io
    kind: ProvisioningRequestConfig
    name: rayjob-gpu-config
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ProvisioningRequestConfig
metadata:
  name: rayjob-gpu-config
spec:
  provisioningClassName: queued-provisioning.gke.io
  managedResources:
  - nvidia.com/gpu
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "cluster-queue"
spec:
  namespaceSelector: {} # match all
  resourceGroups:
  - coveredResources: ["cpu", "memory", "nvidia.com/gpu"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 10000 # infinite quotas
      - name: "memory"
        nominalQuota: 10000Gi # infinite quotas
      - name: "nvidia.com/gpu"
        nominalQuota: 10000 # infinite quotas
  admissionChecks:
  - rayjob-gpu
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  namespace: "default"
  name: "user-queue"
spec:
  clusterQueue: "cluster-queue"

创建 Kueue 资源

kubectl apply -f kueue-resources.yaml

注意

此示例配置 Kueue 来编排 GPU 的 gang 调度。但是,您也可以使用 CPU 和内存等其他资源。

部署 RayJob#

下载 RayJob,它执行 微调 PyTorch Lightning 文本分类器 中记录的所有步骤。源代码 也位于 KubeRay 存储库中。

curl -LO https://raw.githubusercontent.com/ray-project/kuberay/master/ray-operator/config/samples/pytorch-text-classifier/ray-job.pytorch-distributed-training.yaml

在创建 RayJob 之前,修改 RayJob 元数据,添加一个标签,将 RayJob 分配给您之前创建的 LocalQueue。

metadata:
  generateName: pytorch-text-classifier-
  labels:
    kueue.x-k8s.io/queue-name: user-queue

部署 RayJob

$ kubectl create -f ray-job.pytorch-distributed-training.yaml
rayjob.ray.io/dev-pytorch-text-classifier-r6d4p created

RayJob 的 Gang 调度#

当您将需要 GPU 的 RayJob 部署到最初没有 GPU 的集群时,预期的行为如下:

  • Kueue 由于集群中 GPU 资源不足而暂停 RayJob。

  • Kueue 创建一个 ProvisioningRequest,指定 RayJob 的 GPU 要求。

  • Kubernetes 节点自动缩放器会监视 ProvisioningRequests 并根据需要添加具有 GPU 的节点。

  • 一旦所需的 GPU 节点可用,ProvisioningRequest 就会得到满足。

  • Kueue 允许 RayJob,从而允许 Kubernetes 在新预配的节点上调度 Ray 节点,RayJob 执行开始。

如果 GPU 不可用,Kueue 会继续暂停 RayJob。此外,节点自动缩放器会避免预配新节点,直到能够完全满足 RayJob 的 GPU 要求为止。

创建 RayJob 后,请注意,即使 ClusterQueue 具有可用的 GPU 配额,RayJob 状态也会立即显示为 suspended

$ kubectl get rayjob pytorch-text-classifier-rj4sg -o yaml
apiVersion: ray.io/v1
kind: RayJob
metadata:
  name: pytorch-text-classifier-rj4sg
  labels:
    kueue.x-k8s.io/queue-name: user-queue
...
...
...
status:
  jobDeploymentStatus: Suspended  # RayJob suspended
  jobId: pytorch-text-classifier-rj4sg-pj9hx
  jobStatus: PENDING

Kueue 会一直暂停此 RayJob,直到其对应的 ProvisioningRequest 得到满足为止。使用此命令列出 ProvisioningRequest 资源及其状态:

$ kubectl get provisioningrequest
NAME                                                      ACCEPTED   PROVISIONED   FAILED   AGE
rayjob-pytorch-text-classifier-nv77q-e95ec-rayjob-gpu-1   True       False         False    22s

请注意输出中的两列:ACCEPTEDPROVISIONEDACCEPTED=True 表示 Kueue 和 Kubernetes 节点自动缩放器已确认请求。PROVISIONED=True 表示 Kubernetes 节点自动缩放器已完成节点预配。一旦这两个条件都为真,ProvisioningRequest 就得到满足。

$ kubectl get provisioningrequest
NAME                                                      ACCEPTED   PROVISIONED   FAILED   AGE
rayjob-pytorch-text-classifier-nv77q-e95ec-rayjob-gpu-1   True       True          False    57s

由于示例 RayJob 需要 1 个 GPU 进行微调,因此通过在 gpu-node-pool 节点池中添加单个 GPU 节点来满足 ProvisioningRequest。

$ kubectl get nodes
NAME                                                  STATUS   ROLES    AGE   VERSION
gke-kuberay-gpu-cluster-default-pool-8d883840-fd6d    Ready    <none>   14m   v1.29.0-gke.1381000
gke-kuberay-gpu-cluster-gpu-node-pool-b176212e-g3db   Ready    <none>   46s   v1.29.0-gke.1381000  # new node with GPUs

一旦 ProvisioningRequest 得到满足,Kueue 就会允许 RayJob。然后 Kubernetes 调度器会立即将 head 和 worker 节点放置在新预配的资源上。ProvisioningRequest 可确保无缝的 Ray 集群启动,而不会出现任何 Pod 的调度延迟。

$ kubectl get pods
NAME                                                      READY   STATUS    RESTARTS        AGE
pytorch-text-classifier-nv77q-g6z57                       1/1     Running   0               13s
torch-text-classifier-nv77q-raycluster-gstrk-head-phnfl   1/1     Running   0               6m43s