Step 1: The multi-GPU node landscape: read what your fleet has
The multi-GPU node landscape
This cluster has 5 nodes across 3 GPU types — the same shape you'll see in a real multi-tenant GPU production cluster:
| Node | Pool | GPU product | GPUs | Taints |
|---|---|---|---|---|
mocloud-based | default | Tesla-K80 | 4 | (none) |
kwok-a100-1 | a100-pool | NVIDIA-A100-SXM4-80GB | 2 | kwok.x-k8s.io/node=fake:NoSchedule |
kwok-a100-2 | a100-pool | NVIDIA-A100-SXM4-80GB | 2 | kwok.x-k8s.io/node=fake:NoSchedule |
kwok-l40s-1 | l40s-pool | NVIDIA-L40S | 2 | kwok.x-k8s.io/node=fake:NoSchedule |
kwok-l40s-2 | l40s-pool | NVIDIA-L40S | 2 | kwok.x-k8s.io/node=fake:NoSchedule |
The Tesla-K80 node is the host's real node with execution; the four kwok-* nodes are KWOK virtual nodes (synthetic — pods scheduled there go Running but don't actually execute). For this lab the difference doesn't matter — we're teaching the placement primitives, and kubectl get pod -o wide shows you which node a pod landed on either way.
What gpu-feature-discovery wrote on each node
Every GPU node carries a set of nvidia.com/gpu.* labels written by gpu-feature-discovery (the GPU Operator's labeling daemon). The values come from the underlying hardware (or in our case, the topology ConfigMap):
nvidia.com/gpu.present = true (every GPU node)
nvidia.com/gpu.product = <model name> (e.g., NVIDIA-A100-SXM4-80GB)
nvidia.com/gpu.count = <count of GPUs on the node> (2 or 4 in this lab)
nvidia.com/gpu.memory = <per-GPU memory in MiB> (11441 for K80, 81920 for A100)
nvidia.com/gpu.compute.major / .minor = CUDA Compute Capability
These are the labels your nodeSelector / nodeAffinity will target. Run the step's command (or click Run) — it dumps every nvidia.com label on every node so you can see the full landscape.
What the taints mean
The four KWOK virtual nodes carry the taint:
kwok.x-k8s.io/node=fake:NoSchedule
NoSchedule means: pods that don't tolerate this taint will NOT be scheduled on this node. The taint is automatically applied to KWOK nodes by the KWOK admission webhook to prevent pods from accidentally landing on synthetic nodes. To use these nodes, your pod spec must include a matching toleration. We'll do this in step 4.
The Tesla-K80 node has no taint — pods schedule there freely.
Production parallels
In a real production GPU cluster, the layout looks similar — minus the KWOK fakery. Common patterns:
nvidia.com/gpu=present:NoScheduletaint on every GPU node — repels non-GPU workloads (cron jobs, monitoring sidecars) so they don't waste GPU-node capacity.workload=training:NoScheduletaint on training-reserved nodes — only pods marked as training tolerate; inference / dev land elsewhere.- GPU-product-specific taints like
nvidia.com/gpu.product=NVIDIA-A100:NoSchedulefor "A100s are reserved for the team that authored this taint."
Once you can read kubectl get node -o jsonpath for both labels and taints, the placement decisions become trivial. The next four steps make that concrete.
Why this matters in practice: the four primitives — nodeSelector, nodeAffinity, taints, tolerations — are how every production GPU cluster encodes its workload-to-hardware policy. NCA-AIIO frames this as "given this fleet (5 nodes, 3 GPU types) and this workload (training, inference, dev), author the pod spec that lands it on the right hardware."