Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
In this article, you configure Kueue admission control for Ray workloads on Azure Kubernetes Service (AKS). Kueue gates RayJob submissions: jobs are created with suspend: true and a queue label. Kueue admits them when quota is available by setting suspend: false, which triggers the KubeRay operator to create the Ray cluster.
Two queue configurations are provided. Choose the one that fits your use case:
| Configuration | What it demonstrates |
|---|---|
| Single queue | One ClusterQueue with backpressure - one workload runs, the next waits |
| Team queues | Two ClusterQueues in a shared cohort with per-team quotas, borrowing, and preemption |
Important
Open-source software is mentioned throughout AKS documentation and samples. Software that you deploy is excluded from AKS service-level agreements, limited warranty, and Azure support. As you use open-source technology alongside AKS, consult the support options available from the respective communities and project maintainers to develop a plan.
Microsoft takes responsibility for building the open-source packages that we deploy on AKS. That responsibility includes having complete ownership of the build, scan, sign, validate, and hotfix process, along with control over the binaries in container images. For more information, see Vulnerability management for AKS and AKS support coverage.
Prerequisites
- Infrastructure deployed following Deploy infrastructure for Ray and Kueue on AKS.
kubectlconnected to the cluster.- Kueue controller running (verify with
kubectl -n kueue-system get pods).
Create the namespace and service account
Navigate to the queue configuration module in the cloned repository:
cd <path-to-cloned-repo>/AKS/examples/kueue-and-ray-on-aks/2-kueue-queues
kubectl apply -f manifests/00-namespace.yaml
kubectl apply -f <(terraform -chdir=../1-infrastructure/terraform output -raw ray_workload_sa_yaml)
Verify the service account has workload identity annotations:
kubectl -n ray get serviceaccount ray-workload -o yaml
The output should include azure.workload.identity/client-id and azure.workload.identity/tenant-id annotations that allow Ray pods to access Azure Blob Storage without credentials.
Create ResourceFlavors
ResourceFlavors describe types of nodes available in the cluster. Create two flavors - default (any node) and gpu (NVIDIA accelerator nodes):
kubectl apply -f manifests/10-resource-flavors.yaml
Verify:
kubectl get resourceflavors
Expected output:
NAME AGE
default 1m
gpu 1m
The gpu flavor targets nodes with label accelerator=nvidia, which AKS automatically applies to GPU node pools.
Option A: Configure a single queue
The single-queue configuration creates one ClusterQueue with quotas sized for one workload at a time. When quota is fully consumed, the next submission stays Pending until the first completes.
kubectl apply -f manifests/20-single-queue.yaml
Verify the ClusterQueue and LocalQueue:
kubectl get clusterqueue cluster-queue
kubectl -n ray get localqueue default
Expected output:
NAME COHORT PENDING WORKLOADS
cluster-queue 0
NAME CLUSTERQUEUE PENDING WORKLOADS ADMITTED WORKLOADS
default cluster-queue 0 0
Check the configured quotas:
kubectl get clusterqueue cluster-queue -o jsonpath='{.spec.resourceGroups}' | python3 -m json.tool
Default quotas are sized to admit one workload from this solution at a time: 96 CPU, 768 Gi memory, 8 GPUs.
Option B: Configure team queues with borrowing
The team-queue configuration splits the GPU node across two teams, each with their own ClusterQueue, connected via a shared cohort:
kubectl apply -f manifests/30-team-queues.yaml
Important
Choose Option A or Option B, not both. To switch between them, delete the active configuration first:
kubectl delete -f manifests/20-single-queue.yaml
Verify:
kubectl get clusterqueues
kubectl -n ray get localqueues
Expected output:
NAME COHORT PENDING WORKLOADS
team-a-cq shared-cohort 0
team-b-cq shared-cohort 0
NAME CLUSTERQUEUE PENDING WORKLOADS ADMITTED WORKLOADS
team-a team-a-cq 0 0
team-b team-b-cq 0 0
Note
The workload examples default to QUEUE_NAME=default, which matches Option A. If you chose Option B, set export QUEUE_NAME=team-a or export QUEUE_NAME=team-b before running source env.example in each workload directory.
Each team gets 4 GPUs guaranteed with a borrowingLimit of 4, so a team can use up to 8 GPUs total when the other team is idle. Key behaviors:
| Scenario | What happens |
|---|---|
| Team A submits, Team B idle | Team A gets all 8 GPUs (4 own + 4 borrowed) |
| Team B submits while Team A uses 8 | Kueue preempts Team A's borrowed GPUs, Team B gets its guaranteed 4 |
| Both teams busy | Each team uses its guaranteed 4 GPUs |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Workload stays Pending | Quota exhausted | Wait for the running workload to finish, or increase nominalQuota in the ClusterQueue |
LocalQueue not found |
Wrong queue-name label | Verify the kueue.x-k8s.io/queue-name label matches an existing LocalQueue name |
| ResourceFlavor not matching nodes | Missing node label | Check GPU nodes have accelerator=nvidia label with kubectl get nodes --show-labels |
| Kueue controller not running | Helm release issue | Check with kubectl -n kueue-system logs deploy/kueue-controller-manager |
For the full manifest details, see the 2-kueue-queues directory in the repository.
Clean up resources
To remove the Kueue queue configuration (keeps the cluster and operators):
kubectl delete -f manifests/20-single-queue.yaml # or 30-team-queues.yaml
kubectl delete -f manifests/10-resource-flavors.yaml
kubectl delete -f manifests/00-namespace.yaml
Note
Deleting the ray namespace removes all workloads, LocalQueues, and the ServiceAccount in that namespace. ClusterQueues and ResourceFlavors are cluster-scoped and must be deleted separately. To tear down the entire infrastructure, see Deploy infrastructure for Ray and Kueue on AKS.
Next steps
Run the workload examples on your configured queues. Steps 1–2 and steps 3–4 are independent pairs:
- Fine-tune the Aurora weather model
- Serve the fine-tuned Aurora model with Ray Serve (requires step 1)
- Train an LLM with Ray
- Run batch inference with Ray (requires step 3)