Chapter 5 · Part II
KubeRay for Distributed AI
KubeRay is the Kubernetes operator for Ray, an open-source framework for distributed Python workloads. It manages the lifecycle of RayClusters — head nodes, worker nodes, and autoscaling — as native Kubernetes resources. On DGX Spark Bundle, KubeRay bridges both nodes into a single Ray cluster so vLLM can split a large model across both GB10 GPUs using tensor parallelism.
Critical — ARM64 Architecture Warning
The standard Ray image (rayproject/ray) is x86-only and will fail on DGX Spark with "exec format error". Always use:
nvcr.io/nvidia/vllm:25.09-py3
This image is ARM64-native with Ray 2.49.2, vLLM 0.10.1.1, and CUDA 13.0 built in.
Install KubeRay Operator
helm repo add kuberay https://ray-project.github.io/kuberay-helm/
helm repo update
helm install kuberay-operator kuberay/kuberay-operator \
--namespace kuberay-system \
--create-namespace \
--set nodeSelector."kubernetes\\.io/hostname"=spark-720e
kubectl get pods -n kuberay-system
# kuberay-operator pod should show Running
NGC Registry Login
# On both Sparks
docker login nvcr.io
# Username: $oauthtoken
# Password: <YOUR_NGC_API_KEY>
HuggingFace Token Secret
kubectl create secret generic hf-token \
--from-literal=token=$HF_TOKEN \
-n core-services
Validate Cross-Node Networking
kubectl run test-spark1 \
--image=busybox \
--overrides='{"spec":{"nodeSelector":{"kubernetes.io/hostname":"spark-720e"}}}' \
--command -- sleep 3600
kubectl run test-spark2 \
--image=busybox \
--overrides='{"spec":{"nodeSelector":{"kubernetes.io/hostname":"spark-7229"}}}' \
--command -- sleep 3600
kubectl get pods -o wide # get SPARK2_POD_IP
kubectl exec test-spark1 -- ping -c 4 <SPARK2_POD_IP>
kubectl delete pod test-spark1 test-spark2
Deploy RayCluster
kubectl apply -f - <<'EOF'
apiVersion: ray.io/v1
kind: RayCluster
metadata:
name: vllm-cluster
namespace: core-services
spec:
rayVersion: '2.49.2'
headGroupSpec:
rayStartParams:
dashboard-host: '0.0.0.0'
num-gpus: '1'
template:
spec:
nodeSelector:
kubernetes.io/hostname: spark-720e
containers:
- name: ray-head
image: nvcr.io/nvidia/vllm:25.09-py3
command: ["/bin/bash", "-c"]
args:
- |
ray start --head \
--dashboard-host=0.0.0.0 \
--num-gpus=1 \
--block &
sleep 30
python3 -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-7B-Instruct \
--tensor-parallel-size 2 \
--distributed-executor-backend ray \
--host 0.0.0.0 \
--port 8000 \
--gpu-memory-utilization 0.85 \
--max-num-seqs 4
env:
- name: HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token
key: token
resources:
limits:
nvidia.com/gpu: "1"
memory: "100Gi"
workerGroupSpecs:
- replicas: 1
groupName: worker-group
rayStartParams:
num-gpus: '1'
template:
spec:
nodeSelector:
kubernetes.io/hostname: spark-7229
containers:
- name: ray-worker
image: nvcr.io/nvidia/vllm:25.09-py3
command: ["/bin/bash", "-c"]
args:
- |
ray start \
--address=vllm-cluster-head-svc.core-services.svc.cluster.local:6379 \
--num-gpus=1 \
--block
resources:
limits:
nvidia.com/gpu: "1"
memory: "100Gi"
EOF
Verify Ray Cluster
HEAD=$(kubectl get pods -n core-services -l ray.io/node-type=head -o name | head -1)
kubectl exec -n core-services $HEAD -- python3 -c \
"import ray; ray.init(); print(ray.cluster_resources())"
# Expected: {'GPU': 2.0, 'CPU': 40.0, 'accelerator_type:GB10': 2.0}