Set up ElasticQuotaTree and Kube Queue to automate cross-team RayJob scheduling with quotas, preemption, gang scheduling (co-scheduling), and dynamic priority adjustment so high-priority jobs get resources first.ElasticQuotaTree and Kube Queue to automate cross-team RayJob scheduling with quotas, preemption, and gang scheduling so high-priority jobs get resources first.
Prerequisites
Make sure you have:
-
An ACK Pro or ACK Lingjun cluster at v1.24 or later
-
KubeRay Operator installed
-
Kube Queue (ack-kube-queue) at v1.21.4 or later, configured to support RayJob resources
-
A default node pool with at least three ECS instances at 8 vCPU and 32 GiB or higher
The examples use rayproject/ray:2.36.1. If pulls from Docker Hub fail due to network issues, use one of these alternatives:
-
Subscribe to images outside the Chinese mainland through Container Registry.
-
Create a Global Accelerator instance for cross-region image pulls.
How it works
Resource management follows a three-layer pipeline:
-
Define quotas with
ElasticQuotaTree— a tree where each node sets resource guarantees and caps per team or department. -
Automate queue creation — Kube Queue reads the
ElasticQuotaTreeand creates a Queue for each leaf node. RayJobs in a bound namespace route to the matching queue automatically. -
Control job scheduling — a RayJob with
suspend: trueenters its queue. Kube Queue setssuspendtofalsewhen the job's resource request fits the available quota, letting it proceed to scheduling.
For distributed tasks requiring simultaneous startup, gang scheduling prevents partial allocation and deadlocks.
View job queues
After the ElasticQuotaTree is applied, Kube Queue creates a Queue for each leaf node. For the video team under algorithm, the queue root-algorithm-video is created in the kube-queue namespace.
When a RayJob with suspend: true is submitted in the video namespace, Kube Queue:
-
Creates a
QueueUnitresource -
Places the job in the
root-algorithm-videoqueue -
Calculates total resource requirements: Head Pod requests + (replicas × single Pod request per WorkerGroup)
-
Sets
suspendtofalsewhen quota is available, letting the job proceed to scheduling
-
Check the auto-created queue for the
videoteam.kubectl get queue -n kube-queue root-algorithm-video-k42kq -o yamlThe output is similar to:
apiVersion: scheduling.x-k8s.io/v1alpha1 kind: Queue metadata: annotations: kube-queue/parent-quota-fullname: algorithm kube-queue/quota-fullname: root/algorithm/video generateName: root-algorithm-video- name: root-algorithm-video-k42kq namespace: kube-queue spec: queuePolicy: Round status: queueItemDetails: active: [] # Jobs awaiting scheduling, ordered by priority backoff: [] # Jobs waiting to retry after a failed scheduling attempt -
List all queues to confirm the full hierarchy.
kubectl get queue -n kube-queueThe output is similar to:
NAME AGE root-algorithm-n54fm 51s root-algorithm-text-hgbvz 51s root-algorithm-video-k42kq 51s root-devops-2zccw 51s root-infrastructure-devops-d6zqq 51s root-infrastructure-vbpkt 51s root-k8htb 51s
Create and submit RayJobs
Create the ConfigMap
Create a ConfigMap in the video namespace with the Python code for the RayJob.
apiVersion: v1
kind: ConfigMap
metadata:
name: rayjob-video
namespace: video
data:
sample_code.py: |
import ray
import os
import requests
ray.init()
@ray.remote
class Counter:
def __init__(self):
# Used to verify runtimeEnv
self.name = os.getenv("counter_name")
assert self.name == "test_counter"
self.counter = 0
def inc(self):
self.counter += 1
def get_counter(self):
return "{} got {}".format(self.name, self.counter)
counter = Counter.remote()
for _ in range(2):
ray.get(counter.inc.remote())
print(ray.get(counter.get_counter.remote()))
# Verify that the correct runtime env was used for the job.
assert requests.__version__ == "2.26.0"
Verify queuing behavior
-
Create two RayJobs with
kubectl create -f.kubectl get rayjob -n videoExpected output:
NAME JOB STATUS DEPLOYMENT STATUS START TIME END TIME AGE rayjob-video-g2lvn Initializing 2025-01-10T01:36:24Z 6s rayjob-video-h4x2q Suspended 2025-01-10T01:36:25Z 5srayjob-video-g2lvndequeued and is initializing.rayjob-video-h4x2qremains suspended, waiting for quota. -
Confirm enqueue and dequeue timestamps for the first job.
kubectl -n video get rayjob rayjob-video-g2lvn -o yamlBoth timestamps confirm the job was dequeued:
annotations: kube-queue/job-dequeue-timestamp: 2025-01-10 01:36:24.641181026 +0000 UTC kube-queue/job-enqueue-timestamp: 2025-01-10 01:36:24.298639916 +0000 UTCThe second job shows only an enqueue timestamp — not yet scheduled:
kubectl -n video get rayjob rayjob-video-h4x2q -o yamlannotations: kube-queue/job-enqueue-timestamp: 2025-01-10 01:36:25.505182364 +0000 UTC -
Check which Pods are running.
kubectl -n video get podExpected output — only Pods for the first job are running:
NAME READY STATUS RESTARTS AGE rayjob-video-g2lvn-9gz66 1/1 Running 0 28s rayjob-video-g2lvn-raycluster-v8tfh-head-6trq5 1/1 Running 0 49s rayjob-video-g2lvn-raycluster-v8tfh-small-group-worker-hkt7m 1/1 Running 0 49s rayjob-video-g2lvn-raycluster-v8tfh-small-group-worker-rbzjn 1/1 Running 0 49s -
Check the queue to see the second job waiting.
kubectl -n kube-queue get queue root-algorithm-video-k42kq -o yamlThe second job appears in
backoff, waiting for resources:status: queueItemDetails: active: [] backoff: - name: rayjob-video-h4x2q-ray-qu namespace: video position: 1
Troubleshoot gang scheduling failures
If resources are insufficient, the scheduler logs a GangFailedScheduling warning event for each Pod that could not be placed. Filter these events to identify the cause:
kubectl get events -n algorithm-text \
--field-selector='type=Warning,reason=GangFailedScheduling' \
| grep "cycle 1"
Expected output:
5m48s Warning GangFailedScheduling pod/rayjob-sample-dtmtl-raycluster-r9jc7-small-group-worker-89mlq rayjob-sample-dtmtl-raycluster-r9jc7-small-group-worker-89mlq in gang failed to be scheduled in cycle 1: 0/0 nodes are available: 3 Insufficient cpu.
5m48s Warning GangFailedScheduling pod/rayjob-sample-dtmtl-raycluster-r9jc7-small-group-worker-8fwmr rayjob-sample-dtmtl-raycluster-r9jc7-small-group-worker-8fwmr in gang failed to be scheduled in cycle 1: 0/0 nodes are available: 3 Insufficient cpu.
Each event includes a cycle xx number identifying the scheduling attempt and the failure reason. Use this to determine if the issue is insufficient CPU, memory, GPU, or untolerated taints.