用 Kueue 实现 RayJob 优先级调度:KubeRay 队列与配额管理实战
2026/9/19 5:12:41 网站建设 项目流程

用 Kueue 实现 RayJob 优先级调度:KubeRay 队列与配额管理实战

【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray

本指南以 Ray 官方仓库中 rayjob-kueue-priority-scheduling.md 为主线,完整演示如何把一个基于 Ray Data 的 PyTorch Lightning 文本分类器微调任务封装成 RayJob,并借助 Kueue 在 Kubernetes 上实现优先级调度(Priority Scheduling)与配额管理(Quota Management)。读完本文,你将掌握 Kueue 的 ResourceFlavor / ClusterQueue / LocalQueue / WorkloadPriorityClass 四类核心资源的使用方法,理解 Kueue 如何决定"让任务等待、放行任务、抢占任务",并能独立在 GPU 集群上部署排队队列、验证高优先级任务抢占低优先级任务的完整流程。

Kueue 是什么:Kubernetes 原生的任务排队系统

Kueue 是 Kubernetes 原生的任务排队系统,核心职责是管理配额以及任务如何消耗配额。它不直接调度 Pod,而是站在更上层决定三个关键时机:

  • 何时让任务等待(make a job wait):当配额不足时,Kueue 挂起(suspend)任务对应的负载;
  • 何时放行任务启动(admit a job to start):一旦配额可用,Kueue 准入任务,此时 Kubernetes 才开始创建 Pod;
  • 何时抢占任务(preempt a job):当高优先级任务需要配额时,Kueue 触发 Kubernetes 删除低优先级任务的活跃 Pod。

Kueue 对部分 KubeRay API 提供原生支持,具体来说,你可以用 Kueue 管理RayJobRayCluster所消耗的资源(RayService 的资源管理也可通过其底层 RayCluster 间接生效,详见 KubeRay 与 Kueue 集成总览)。本指南聚焦 RayJob 场景,其工作流程为:

  1. 用户创建带kueue.x-k8s.io/queue-name标签的 RayJob;
  2. Kueue 将 RayJob 关联到对应 LocalQueue 排队;
  3. Kueue 依据 ClusterQueue 的配额与优先级决定是否准入;
  4. 准入后 KubeRay 才真正创建 RayCluster 与 submitter Pod;
  5. 任务结束或配额被抢占时,Kueue 控制 RayCluster 的挂起/删除。

补充说明:RayJob 是 KubeRay 提供的一个自定义资源(CRD),它同时管理两部分:一个RayCluster(包含 head Pod 与若干 worker Pod)和一个submitter Kubernetes Job(负责执行ray job submit把 Ray 任务提交到集群)。更多 RayJob 配置字段见 RayJob 快速入门。

Step 0:在 GKE 上创建带 GPU 的 Kubernetes 集群(可选)

如果你已经拥有带 GPU 的 Kubernetes 集群,可以直接跳过本步。否则请参照 为 KubeRay 创建带 GPU 的 GKE 集群 完成集群搭建。

该文档的核心命令如下——先创建一个带自动扩缩的 CPU 节点池集群(e2-standard-4机器类型,4 vCPU / 16 GB RAM):

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

再创建一个 GPU 节点池(本例使用 NVIDIA L4 GPU,g2-standard-4机器类型,每节点 1 GPU / 4 vCPU / 16 GB RAM):

gcloud container node-pools create gpu-node-pool \ --accelerator type=nvidia-l4-vws,count=1 \ --zone us-west1-b \ --cluster kuberay-gpu-cluster \ --num-nodes 1 \ --min-nodes 0 \ --max-nodes 1 \ --enable-autoscaling \ --machine-type g2-standard-4

注意:GKE 会自动为 GPU 节点池配置 taint 与 toleration,确保只有请求 GPU 的 Pod 才会被调度到 GPU 节点上。因此,如果 GPU 节点池设置了正确的 taint,KubeRay operator 的 Pod 会落在 CPU 节点上。

Step 1:安装 KubeRay operator

参照 部署 KubeRay operator,用 Helm 从官方仓库安装最新稳定版 operator(推荐安装到独立的ray-system命名空间,以隔离 operator 的服务账号与业务工作负载):

helm repo add kuberay https://ray-project.github.io/kuberay-helm/ helm repo update kubectl create namespace ray-system helm install kuberay-operator kuberay/kuberay-operator --version 1.7.0 -n ray-system

验证安装:

kubectl get pods -n ray-system # NAME READY STATUS RESTARTS AGE # kuberay-operator-6bc45dd644-gwtqv 1/1 Running 0 24s

也可以使用 Kustomize 方式安装:kubectl create -k "github.com/ray-project/kuberay/ray-operator/config/default?ref=v1.7.0" -n ray-system

Step 2:安装 Kueue

使用kubectl apply --server-side安装指定版本(本指南使用 v0.13.4)的 Kueue 清单:

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

Kueue 的官方安装文档对安装正式发布版本有更详细的说明。需要注意 Kueue 与 RayJob 之间存在一些已知限制(例如 Kueue 不处理shutdownAfterJobFinishes为 false 的 RayJob 自定义资源),建议阅读 Kueue 官方文档中运行 RayJob 的限制章节。

Step 3:配置 Kueue 的优先级调度资源

理解本教程之前,需要先弄清 Kueue 的四个核心概念:

概念作用在本例中的角色
ResourceFlavor定义集群中可用的资源种类(通常对应节点特征)default-flavor:同构集群,空 flavor
ClusterQueue定义配额与公平共享规则,任务准入的决策点cluster-queue:2 CPU / 8G 内存 / 1 GPU,并开启LowerPriority抢占
LocalQueue命名空间级队列,归属于某个租户/团队/用户,引用一个 ClusterQueueuser-queue:default 命名空间,绑定cluster-queue
WorkloadPriorityClass声明任务的优先级数值prod-priority(值 1000)与dev-priority(值 100)

创建kueue-resources.yaml,内容如下:

# 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", "nvidia.com/gpu"] flavors: - name: "default-flavor" resources: - name: "cpu" nominalQuota: 2 - name: "memory" nominalQuota: 8G - name: "nvidia.com/gpu" # ClusterQueue only has quota for a single GPU. nominalQuota: 1 --- 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"

这份清单中每个对象的要点如下:

  • ResourceFlavordefault-flavor是一个的 ResourceFlavor,因为集群中的计算资源是同构的(homogeneous)。换言之,用户请求 1 块 GPU 时无需关心它是 NVIDIA A100 还是 T4——所有 GPU 能力一致,不需要用 flavor 区分。
  • ClusterQueue
    • cluster-queue只有一个 flavordefault-flavor,配额为 2 CPU、8G 内存和 1 GPU,恰好等于 1 个 RayJob 自定义资源所请求的资源量。因此在该配额下,同一时刻只能运行 1 个 RayJob
    • 其抢占策略preemption.withinClusterQueue: LowerPriority允许:一个因超出 ClusterQueue nominalQuota 而处于 pending 状态的 RayJob,抢占该 ClusterQueue 内正在运行的低优先级 RayJob。
    • namespaceSelector: {}表示匹配所有命名空间。
  • LocalQueueuser-queuedefault命名空间内的命名空间级对象,归属于某个 ClusterQueue。典型实践是把一个命名空间分配给组织内的一个租户、团队或用户;用户向 LocalQueue 提交任务,而不是直接向 ClusterQueue 提交
  • WorkloadPriorityClassprod-priority的 value(1000)大于dev-priority的 value(100),因此携带prod-priority优先级类的 RayJob 会优先于携带dev-priority的 RayJob。

应用这些 Kueue 资源:

kubectl apply -f kueue-resources.yaml

关于 WorkloadPriorityClass 的补充:它是 Kueue 的优先级声明机制,数值越大优先级越高。与之配合的抢占行为由 ClusterQueue 的preemption.withinClusterQueue策略控制;如果希望跨多个 ClusterQueue 抢占,则需配置preemption.reclaimWithinCohort并将多个队列加入同一个 cohort。本指南仅演示队列内(withinClusterQueue)抢占。

Step 4:部署一个 RayJob 并验证排队机制

本例使用的 RayJob 会执行 用 Ray Data 微调 PyTorch Lightning 文本分类器 教程中的全部步骤,其源码位于 KubeRay 仓库的ray-operator/config/samples/pytorch-text-classifier目录。下载该 RayJob 清单:

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

创建 RayJob 之前,需要修改其metadata,加入 Kueue 的队列标签与优先级标签:

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

各字段含义:

  • kueue.x-k8s.io/queue-name: user-queue:用户向 LocalQueue 提交任务(而非直接向 ClusterQueue 提交),Kueue 据此把该 RayJob 放入user-queue排队;
  • kueue.x-k8s.io/priority-class: dev-priority:为该 RayJob 指定dev-priority这个 WorkloadPriorityClass,声明它是开发任务;
  • generateName: dev-pytorch-text-classifier-:让 Kubernetes 自动生成唯一名称,名称前缀表明这是开发用途的任务。

同时,需要关注该 RayJob 的资源请求。查看 Ray head Pod 的资源配置:

resources: limits: memory: "8G" nvidia.com/gpu: "1" requests: cpu: "2" memory: "8G" nvidia.com/gpu: "1"

该 RayJob 请求 2 CPU、8G 内存和 1 GPU——恰好与 Step 3 中 ClusterQueue 的配额完全匹配。

背景知识:RayJob 由 KubeRay operator 管理,operator 会依据spec.rayClusterSpec创建 RayCluster,并由一个 submitter Kubernetes Job 执行ray job submit将任务提交进集群。entrypoint字段就是 submitter 要执行的命令;runtimeEnvYAML可声明任务依赖的 pip 包与环境变量;shutdownAfterJobFinishes决定任务结束后是否回收 RayCluster。本教程示例将shutdownAfterJobFinishes设为 true(Kueue 不管理该字段为 false 的 RayJob,这是 Kueue 的已知限制)。

现在部署 RayJob:

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

验证 RayCluster 与 submitter Kubernetes Job 都在运行:

$ kubectl get pod NAME READY STATUS RESTARTS AGE dev-pytorch-text-classifier-r6d4p-4nczg 1/1 Running 0 4s # Submitter Kubernetes Job torch-text-classifier-r6d4p-raycluster-br45j-head-8bbwt 1/1 Running 0 34s # Ray head Pod

确认任务成功完成后,删除该 RayJob:

$ kubectl get rayjobs.ray.io dev-pytorch-text-classifier-r6d4p -o jsonpath='{.status.jobStatus}' SUCCEEDED $ kubectl get rayjobs.ray.io dev-pytorch-text-classifier-r6d4p -o jsonpath='{.status.jobDeploymentStatus}' Complete $ kubectl delete rayjob dev-pytorch-text-classifier-r6d4p rayjob.ray.io "dev-pytorch-text-classifier-r6d4p" deleted

jobStatus: SUCCEEDED表示 Ray 任务执行成功,jobDeploymentStatus: Complete表示 RayJob 已完成整个部署生命周期。

Step 5:排队多个 RayJob,观察配额耗尽

现在连续创建 3 个 RayJob 自定义资源(使用相同的清单,即相同的dev-priority优先级与user-queue队列),观察 Kueue 与 KubeRay 如何协同实现排队:

$ kubectl create -f ray-job.pytorch-distributed-training.yaml rayjob.ray.io/dev-pytorch-text-classifier-8vg2c created $ kubectl create -f ray-job.pytorch-distributed-training.yaml rayjob.ray.io/dev-pytorch-text-classifier-n5k89 created $ kubectl create -f ray-job.pytorch-distributed-training.yaml rayjob.ray.io/dev-pytorch-text-classifier-ftcs9 created

由于每个 RayJob 都请求 1 块 GPU,而 ClusterQueue 只有 1 块 GPU 的配额,Kueue 会自动挂起新增的 RayJob,直到 GPU 配额可用为止。

查看 ClusterQueue 的配额使用情况:

$ kubectl get clusterqueue NAME COHORT PENDING WORKLOADS cluster-queue 2
$ kubectl get clusterqueue cluster-queue -o yaml apiVersion: kueue.x-k8s.io/v1beta1 kind: ClusterQueue ... ... ... status: admittedWorkloads: 1 # Workloads admitted by queue. flavorsReservation: - name: default-flavor resources: - borrowed: "0" name: cpu total: "8" - borrowed: "0" name: memory total: 19531250Ki - borrowed: "0" name: nvidia.com/gpu total: "2" flavorsUsage: - name: default-flavor resources: - borrowed: "0" name: cpu total: "8" - borrowed: "0" name: memory total: 19531250Ki - borrowed: "0" name: nvidia.com/gpu total: "2" pendingWorkloads: 2 # Queued workloads waiting for quotas. reservingWorkloads: 1 # Running workloads that are using quotas.

输出中的关键状态字段说明:

  • admittedWorkloads: 1:已被队列准入的工作负载数(正在运行的 1 个 RayJob);
  • pendingWorkloads: 2:正在排队等待配额的工作负载数(2 个被挂起的 RayJob);
  • reservingWorkloads: 1:当前占用配额正在运行的工作负载数;
  • flavorsReservation/flavorsUsage:flavor 维度上配额被预留与实际使用的资源明细(cpu / memory / nvidia.com/gpu)。

这印证了 Kueue 与 KubeRay 的协作方式:Kueue 只负责"准入"决策,Pod 的实际创建由 KubeRay operator 完成。被挂起的 RayJob 不会创建任何 Pod,从而避免资源浪费和"部分分配"(partial allocation)问题——这正是 Kueue 所谓 "gang"(整组一起调度)模式的体现:要么整个 RayJob 的 RayCluster 被一次性准入,要么完全不创建 Pod(详见 RayJob 与 Kueue 的 Gang Scheduling 示例)。

Step 6:部署高优先级 RayJob,触发抢占

此时已有多个 RayJob 在排队,但配额只够运行 1 个。接下来创建一个携带更高优先级prod-priority的 RayJob,观察它如何抢占已排队(以及正在运行的)低优先级 RayJob。

修改 RayJob 的metadata

metadata: generateName: prod-pytorch-text-classifier- labels: kueue.x-k8s.io/queue-name: user-queue kueue.x-k8s.io/priority-class: prod-priority
  • kueue.x-k8s.io/queue-name: user-queue:任务仍然提交到同一个 LocalQueue;
  • kueue.x-k8s.io/priority-class: prod-priority:为该 RayJob 指定prod-priorityWorkloadPriorityClass;
  • generateName: prod-pytorch-text-classifier-:名称前缀表明这是生产任务。

创建这个新 RayJob:

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

由于配额不足,且 ClusterQueue 配置了preemption.withinClusterQueue: LowerPriority高优先级任务会抢占低优先级任务——Kueue 先删除低优先级任务已创建的 Pod(将其挂起),再准入高优先级任务:

$ kubectl get pods NAME READY STATUS RESTARTS AGE prod-pytorch-text-classifier-gkp9b-r9k5r 1/1 Running 0 5s torch-text-classifier-gkp9b-raycluster-s2f65-head-hfvht 1/1 Running 0 35s

可以看到,此时运行中的 Pod 全部属于prod-pytorch-text-classifier-gkp9b(高优先级生产任务),原先排队/运行的dev-pytorch-text-classifier-*系列任务的 Pod 已被 Kueue 挂起删除。这完整演示了优先级调度的闭环:配额不足 → 排队等待 → 高优先级到达 → 抢占低优先级 → 高优先级任务先运行

进阶:Kueue 抢占语义与搭配使用的注意事项

结合仓库中 KubeRay 与 Kueue 集成总览 的内容,使用本方案时还需注意以下几点:

  1. 抢占是队列内策略:本指南的withinClusterQueue: LowerPriority只在单个 ClusterQueue 内部生效。多个 ClusterQueue 之间若要协同抢占,需要配置preemption.reclaimWithinCohort并把队列归入同一 cohort。
  2. "全有或全无"的准入语义:Kueue 始终以 gang 模式准入工作负载,即一次性满足整个 RayJob(head + workers)的资源需求后才创建 Pod。这对昂贵且稀缺的 GPU 资源尤其重要——避免 Ray 集群被部分创建后"占着 GPU 不干活",能显著提升 GPU 利用率和成本效率。
  3. 动态扩容场景:如果你的集群没有常驻 GPU 节点,可以结合 GKE 的 ProvisioningRequest API 让 Kueue 在准入前动态补充节点(使用AdmissionCheckProvisioningRequestConfig),相关完整流程见 RayJob 与 Kueue 的 Gang Scheduling 示例,其中演示了kubectl get provisioningrequest查看ACCEPTED/PROVISIONED两列状态来确认节点是否供给完成。
  4. 不要手动改suspend字段:RayJob 的suspend字段是 Kueue 实现调度策略的载体(Kueue 通过改写该字段挂起/恢复 RayJob)。如果使用 Kueue 调度 RayJob,应避免手动更新该字段,以免与 Kueue 的状态机冲突。
  5. 版本注意:本指南使用 Kueue v0.13.4;若使用早于 v0.13 的版本,安装后需要重启一次 Kueue controller 才能保证 RayCluster 管理正常工作。更完整的 Kueue 能力(含 Kueue v0.15.2+ 的 RayJob 弹性扩缩容)可参考 KubeRay 与 Kueue 集成总览。

总结

本指南完整走通了"KubeRay + Kueue"实现 RayJob 优先级调度的闭环:从 GKE 集群准备、KubeRay operator 与 Kueue 安装,到用 ResourceFlavor / ClusterQueue / LocalQueue / WorkloadPriorityClass 四件套配置配额与优先级,再到部署多个 RayJob 观察排队、配额耗尽与高优先级抢占的完整现象。核心要点可以浓缩为三句话:

  • Kueue 管"准入",KubeRay 管"创建":Kueue 决定 RayJob 何时等待、何时放行、何时抢占;只有被准入的 RayJob,KubeRay 才会为其创建 RayCluster 和 submitter Pod。
  • 配额精确匹配即天然限流:把 ClusterQueue 的配额设置成恰好等于 1 个 RayJob 的资源需求,即可实现"同一时刻只运行 N 个任务"的限流效果。
  • 优先级声明 + 抢占策略 = 高优任务插队kueue.x-k8s.io/priority-class标签声明任务优先级,preemption.withinClusterQueue: LowerPriority策略授权高优任务抢占低优任务。

这套方案非常适合多团队共享 GPU 集群、需要按业务重要程度划分任务等级的生产环境,是 Ray 生态在 Kubernetes 上落地"多租户配额治理"的基础能力之一。

【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询