使用 RayJob 和 Kueue 进行组调度#
本指南演示了如何使用 Kueue 对 RayJob 资源进行组调度,利用 Kubernetes 上的动态资源供应和排队机制。为了说明这些概念,本指南使用了使用 Ray Data 微调 PyTorch Lightning 文本分类器示例。
组调度#
Kubernetes 中的组调度确保一组相关的 Pod(例如 Ray 集群中的 Pod)仅在所有所需资源都可用时才启动。在使用昂贵、有限的资源(例如 GPU)时,此要求至关重要。
Kueue#
Kueue 是一个 Kubernetes 原生系统,用于管理配额以及作业如何消耗它们。Kueue 决定何时
让作业等待。
允许作业启动,这会触发 Kubernetes 创建 Pod。
抢占作业,这会触发 Kubernetes 删除活动 Pod。
Kueue 对某些 KubeRay API 提供原生支持。具体来说,你可以使用 Kueue 管理 RayJob 和 RayCluster 消耗的资源。请参阅 Kueue 文档以了解更多信息。
为何使用组调度#
在使用昂贵、有限的硬件加速器(例如 GPU)时,组调度至关重要。它防止 RayJob 部分供应 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 operator#
按照部署 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.8.2/manifests.yaml
有关安装 Kueue 的更多详细信息,请参阅Kueue 安装。
配置 Kueue 以进行组调度#
接下来,配置 Kueue 进行组调度。Kueue 利用 ProvisioningRequest API 完成两个关键任务
当作业需要更多资源时,动态向集群添加新节点。
阻止等待充足资源可用的新作业的准入。
有关更多详细信息,请参阅ProvisioningRequest 的工作原理。
创建 Kueue 资源#
此清单创建以下资源
ClusterQueue: 定义配额和公平共享规则
LocalQueue: 一个属于租户的命名空间队列,引用 ClusterQueue
ResourceFlavor: 定义集群中可用的资源,通常来自节点
AdmissionCheck: 一种允许组件影响工作负载准入时机的机制
# 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 的组调度。但是,你也可以使用其他资源,例如 CPU 和内存。
部署 RayJob#
下载执行微调 PyTorch Lightning 文本分类器中所有步骤的 RayJob。其源代码也在 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 进行组调度#
将需要 GPU 的 RayJob 部署到最初缺少 GPU 的集群时,预期行为如下
由于集群中 GPU 资源不足,Kueue 暂停 RayJob。
Kueue 创建 ProvisioningRequest,指定 RayJob 的 GPU 需求。
Kubernetes 节点自动伸缩器监控 ProvisioningRequest 并根据需要添加带有 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
注意输出中的两个列:ACCEPTED
和 PROVISIONED
。 ACCEPTED=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 进行微调,因此 ProvisioningRequest 通过在 gpu-node-pool
节点池中添加单个 GPU 节点得到满足。
$ 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