Skip to content

Commit f71bd8b

Browse files
authored
fix(bundler): make the DRA eviction node label opt-in (#2471)
Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
1 parent ca8556c commit f71bd8b

23 files changed

Lines changed: 752 additions & 189 deletions

.github/workflows/gpu-h100-kind-runtime-test.yaml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,6 @@ jobs:
8585
control_plane_resource_patches: 'true'
8686
control_plane_leader_election_tuning: 'true'
8787

88-
- name: Label nodes for the DRA kubelet plugin
89-
shell: bash
90-
env:
91-
DRA_EVICTION_NODE_LABEL: nvidia.com/dra-kubelet-plugin=true
92-
run: >-
93-
kubectl label nodes
94-
--selector='!node-role.kubernetes.io/control-plane'
95-
"${DRA_EVICTION_NODE_LABEL}"
96-
--overwrite
97-
9888
- name: Build AICR CLI
9989
timeout-minutes: 10
10090
uses: ./.github/actions/aicr-build

api/aicr/v1/server.baseline.yaml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,8 @@ components:
10381038
components are also rejected (driver-ownership paths, GPU
10391039
allocation-policy keys, the DRA eviction paths
10401040
kubeletPlugin.nodeSelector and driver.manager.env when both contract
1041-
components are enabled, and — where the corresponding NVSentinel gate
1041+
components are enabled and dra-eviction-node-label is supplied, and —
1042+
where the corresponding NVSentinel gate
10421043
applies on the recipe's platform and configuration — the NVSentinel
10431044
remedy/consumer/runtime-class paths).
10441045
schema:
@@ -1096,12 +1097,15 @@ components:
10961097
in: query
10971098
required: false
10981099
description: >
1099-
Single key=value node label coordinating NVIDIA DRA kubelet-plugin
1100-
eviction with GPU Operator driver upgrades. Applied only when both
1101-
components are enabled.
1100+
Opt in to NVIDIA DRA kubelet-plugin eviction coordination with GPU
1101+
Operator driver upgrades, as a single key=value node label. Omit the
1102+
parameter to inject nothing: the kubelet plugin then needs no extra
1103+
node label. When set, and both components are enabled, the plugin runs
1104+
only on GPU nodes carrying this label. Unlabeled nodes are silently
1105+
excluded; if no GPU node carries it the kubelet-plugin DaemonSet
1106+
reaches DESIRED=0.
11021107
schema:
11031108
type: string
1104-
default: nvidia.com/dra-kubelet-plugin=true
11051109
example: "example.com/dra-ready=enabled"
11061110
BundleDeployer:
11071111
name: deployer

api/aicr/v1/server.yaml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,8 @@ components:
10251025
components are also rejected (driver-ownership paths, GPU
10261026
allocation-policy keys, the DRA eviction paths
10271027
kubeletPlugin.nodeSelector and driver.manager.env when both contract
1028-
components are enabled, and — where the corresponding NVSentinel gate
1028+
components are enabled and dra-eviction-node-label is supplied, and —
1029+
where the corresponding NVSentinel gate
10291030
applies on the recipe's platform and configuration — the NVSentinel
10301031
remedy/consumer/runtime-class paths).
10311032
schema:
@@ -1083,12 +1084,15 @@ components:
10831084
in: query
10841085
required: false
10851086
description: >
1086-
Single key=value node label coordinating NVIDIA DRA kubelet-plugin
1087-
eviction with GPU Operator driver upgrades. Applied only when both
1088-
components are enabled.
1087+
Opt in to NVIDIA DRA kubelet-plugin eviction coordination with GPU
1088+
Operator driver upgrades, as a single key=value node label. Omit the
1089+
parameter to inject nothing: the kubelet plugin then needs no extra
1090+
node label. When set, and both components are enabled, the plugin runs
1091+
only on GPU nodes carrying this label. Unlabeled nodes are silently
1092+
excluded; if no GPU node carries it the kubelet-plugin DaemonSet
1093+
reaches DESIRED=0.
10891094
schema:
10901095
type: string
1091-
default: nvidia.com/dra-kubelet-plugin=true
10921096
example: "example.com/dra-ready=enabled"
10931097
BundleDeployer:
10941098
name: deployer

docs/contributor/component.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,33 @@ The narrow exception is a **bundler-owned derived integration value**:
237237
when AICR renders both sides of a cross-chart contract and allowing either
238238
side to drift would produce an invalid bundle, the bundler may enforce that
239239
contract after ordinary overrides. The DRA/GPU Operator integration is the
240-
worked example: AICR merges the configured DRA eviction label into
240+
worked example: when opted in, AICR merges the configured DRA eviction label into
241241
`kubeletPlugin.nodeSelector` and writes the same label key to GPU Operator's
242242
`NODE_LABEL_FOR_GPU_POD_EVICTION`. This exception must remain gated on both
243243
components being enabled, must reject dynamic declarations that could move
244244
either managed path to install-time configuration, and must be covered across
245245
every deployer. Ordinary workload placement still uses registry paths and must
246246
not grow a special flag.
247247

248+
**A bundler-owned contract that imposes cluster state must be opt-in.** The DRA
249+
eviction contract also illustrates the limit of the exception. Its selector is
250+
an exact-match `nodeSelector`, so rendering it unconditionally made a node label
251+
a precondition for the kubelet plugin to run at all — and an unlabeled node is
252+
then silently excluded, with `DESIRED=0` only when no node carries the label,
253+
and a successful `helm upgrade` either way. AICR does not
254+
own node labels, cannot verify them at bundle time, and generation is offline,
255+
so it cannot even warn accurately about what a given cluster carries.
256+
257+
The rule that follows: a derived integration value may enforce consistency
258+
between two charts AICR renders, but when it also requires state outside those
259+
charts, the enforcement is opt-in. Absent the opt-in, AICR writes neither side
260+
and owns neither path — no injection, no override protection, no dynamic-path
261+
rejection. The single configuration surface that carries the state (here
262+
`--dra-eviction-node-label` and its config/API/Go equivalents) doubles as the
263+
opt-in, so there is no second boolean and no ambiguous combination. See
264+
[DRA Driver Upgrade Eviction](../user/cli-reference.md#dra-driver-upgrade-eviction)
265+
for the user-facing contract and issue #2469 for the reasoning.
266+
248267
**Deciding where a knob belongs:**
249268

250269
| Situation | Where it goes | Why |

docs/integrator/aks-gpu-setup.md

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -122,23 +122,28 @@ spec:
122122
enabled: false
123123
```
124124
125-
### Label GPU nodes for the DRA kubelet plugin
126-
127-
A bundle that enables both `gpu-operator` and `nvidia-dra-driver-gpu` schedules
128-
the DRA kubelet plugin only on nodes labeled
129-
`nvidia.com/dra-kubelet-plugin=true` (or the pair given to
130-
`aicr bundle --dra-eviction-node-label`). Set it **in the node pool
131-
definition**, with the other required node labels — an ad hoc
132-
`kubectl label node` does not survive node replacement, recycling,
133-
autoscaling, or a pool scaled from zero, so later nodes arrive unlabeled.
134-
135-
An unlabeled GPU node fails silently: it runs no kubelet plugin and publishes
136-
no `ResourceSlices`, and neither Helm nor the bundle's `deploy.sh` reports an
137-
error. With no labeled GPU node at all the DaemonSet sits at `DESIRED=0`; with
138-
only some labeled, those nodes work while the rest silently lack DRA.
139-
This applies to existing clusters too — adding the selector during an
140-
upgrade removes a plugin that was previously working.
141-
See [Prepare DRA nodes before applying upgraded bundles](../user/bundling.md#prepare-dra-nodes-before-applying-upgraded-bundles).
125+
### Label GPU nodes for the DRA kubelet plugin (opt-in only)
126+
127+
DRA eviction coordination is opt-in and **does nothing on the default AKS
128+
profile**. Under `gpuStack=azure-managed` the node image installs the driver, so
129+
GPU Operator sets `driver.enabled=false`, deploys no driver pod and runs no
130+
Driver Manager — there is nothing to coordinate with, and no node label is
131+
needed. Bundles generated without `--dra-eviction-node-label` add no such
132+
selector, and the DRA kubelet plugin runs on every accelerated node.
133+
134+
The label matters only if you both run an operator-managed driver (see the
135+
`gpuStack=operator-managed` procedure below) and generate the bundle with
136+
`aicr bundle --dra-eviction-node-label key=value`. In that case set it **in the
137+
node pool definition**, with the other required node labels — an ad hoc
138+
`kubectl label node` does not survive node replacement, recycling, autoscaling,
139+
or a pool scaled from zero, so later nodes arrive unlabeled.
140+
141+
When you have opted in, an unlabeled GPU node fails silently: it runs no kubelet
142+
plugin and publishes no `ResourceSlices`, and neither Helm nor the bundle's
143+
`deploy.sh` reports an error. With no labeled GPU node at all the DaemonSet sits
144+
at `DESIRED=0`; with only some labeled, those nodes work while the rest silently
145+
lack DRA.
146+
See [Prepare DRA nodes when opting in to eviction coordination](../user/bundling.md#prepare-dra-nodes-when-opting-in-to-eviction-coordination).
142147

143148
## GPU Driver Setup
144149

@@ -258,8 +263,7 @@ az aks nodepool add \
258263
--resource-group <rg> \
259264
--name gpupool \
260265
--node-vm-size Standard_ND96isr_H100_v5 \
261-
--node-count 1 \
262-
--labels nvidia.com/dra-kubelet-plugin=true
266+
--node-count 1
263267
```
264268

265269
No changes to AICR recipes are needed — this is the AKS family's `gpuStack`
@@ -591,17 +595,40 @@ az aks nodepool add \
591595
--name gpupool \
592596
--node-vm-size Standard_ND96isr_H100_v5 \
593597
--gpu-driver none \
594-
--node-count 1 \
595-
--labels nvidia.com/dra-kubelet-plugin=true
598+
--node-count 1
596599
```
597600

601+
Add the eviction label to this pool only if you also opt in at bundle time. The
602+
node label and the flag value must be the **same `key=value` pair** — the flag
603+
selects the convention, and AICR renders exactly what you pass:
604+
605+
`az aks nodepool update --labels` **replaces** the pool's entire user-label map
606+
rather than merging, so repeat every label the pool already carries or they are
607+
dropped — including the accelerated-node selector the bundle relies on:
608+
609+
```shell
610+
az aks nodepool update \
611+
--cluster-name <cluster> --resource-group <rg> --name gpupool \
612+
--labels nodeGroup=gpu-worker nvidia.com/dra-kubelet-plugin=true
613+
```
614+
615+
Prefer setting both at pool creation time (`az aks nodepool add --labels ...`)
616+
so there is no map to preserve.
617+
618+
Then pass the same pair to `aicr bundle` in the generation step below. See
619+
[Label GPU nodes for the DRA kubelet plugin (opt-in only)](#label-gpu-nodes-for-the-dra-kubelet-plugin-opt-in-only).
620+
598621
Then select the mode at recipe generation time with the `gpuStack`
599622
configuration profile — one flag flips every ownership path together:
600623

601624
```shell
602625
aicr recipe --service aks --accelerator h100 --os ubuntu --intent training \
603626
--profile gpuStack=operator-managed -o recipe.yaml
604-
aicr bundle -r recipe.yaml -o ./bundles
627+
# AKS requires a keyed accelerated-node toleration; add
628+
# --dra-eviction-node-label only if you opted in and labelled the pool.
629+
aicr bundle -r recipe.yaml -o ./bundles \
630+
--accelerated-node-toleration nvidia.com/gpu:NoSchedule \
631+
--dra-eviction-node-label nvidia.com/dra-kubelet-plugin=true
605632
```
606633

607634
The `operator-managed` value sets `driver.enabled=true`, `toolkit.enabled=true`,

docs/integrator/gke-gpu-setup.md

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ gcloud container node-pools create POOL_NAME \
107107
--num-nodes=1 \
108108
--machine-type=a3-highgpu-8g \
109109
--accelerator type=nvidia-h100-80gb,count=8,gpu-driver-version=default \
110-
--node-labels="nvidia.com/dra-kubelet-plugin=true"
111110
```
112111

113112
Two flags deserve care:
@@ -231,7 +230,7 @@ gcloud container node-pools create POOL_NAME \
231230
--num-nodes=1 \
232231
--machine-type=a3-highgpu-8g \
233232
--accelerator type=nvidia-h100-80gb,count=8,gpu-driver-version=disabled \
234-
--node-labels="gke-no-default-nvidia-gpu-device-plugin=true,nvidia.com/dra-kubelet-plugin=true"
233+
--node-labels="gke-no-default-nvidia-gpu-device-plugin=true"
235234
```
236235

237236
The `--machine-type` and `--num-nodes` cautions from the default-profile
@@ -294,7 +293,7 @@ gcloud container node-pools describe POOL_NAME \
294293
gcloud container node-pools update POOL_NAME \
295294
--cluster CLUSTER_NAME \
296295
--location=LOCATION \
297-
--node-labels="EXISTING_KEY_1=EXISTING_VALUE_1,gke-no-default-nvidia-gpu-device-plugin=true,nvidia.com/dra-kubelet-plugin=true"
296+
--node-labels="EXISTING_KEY_1=EXISTING_VALUE_1,gke-no-default-nvidia-gpu-device-plugin=true"
298297
```
299298

300299
Replace `EXISTING_KEY_…=EXISTING_VALUE_…` with every label the `describe`
@@ -337,7 +336,7 @@ allocatable `nvidia.com/gpu`):
337336

338337
```bash
339338
kubectl get nodes -l cloud.google.com/gke-accelerator \
340-
-L gke-no-default-nvidia-gpu-device-plugin,nvidia.com/dra-kubelet-plugin
339+
-L gke-no-default-nvidia-gpu-device-plugin
341340
kubectl get pods -n kube-system -l k8s-app=nvidia-gpu-device-plugin -o wide
342341
kubectl get pods -n gpu-operator -l app=nvidia-device-plugin-daemonset -o wide
343342
kubectl get nodes -l cloud.google.com/gke-accelerator \
@@ -379,23 +378,35 @@ regenerate with `--profile gpuStack=bundle-installer`, and deploy the
379378
bundle. Nodes with a loaded driver are untouched (the installer's fast path
380379
skips them); the bundle takes over provisioning for new and rebooted nodes.
381380

382-
## Label GPU nodes for the DRA kubelet plugin
381+
## DRA kubelet plugin node labels are not required on GKE
382+
383+
DRA eviction coordination is opt-in, and by default it does nothing on
384+
supported GKE recipes. Every GKE overlay is COS, and both the default and
385+
`gpuStack=bundle-installer` profiles keep GPU Operator's `driver.enabled=false`
386+
`bundle-installer` provisions the driver through a separate
387+
`gcp-driver-installer` DaemonSet, not a GPU Operator driver pod. With no GPU
388+
Operator driver pod there is no Driver Manager to coordinate with.
389+
390+
So bundles generated for GKE add no eviction node selector, and the DRA kubelet
391+
plugin runs on every accelerated node with no extra label. Nothing in the node
392+
pool commands below needs `nvidia.com/dra-kubelet-plugin`.
393+
394+
If you opt in anyway, AICR renders the selector and every GPU node must carry
395+
the **same `key=value` pair** you passed, set in the node pool definition:
383396

384-
A bundle that enables both `gpu-operator` and `nvidia-dra-driver-gpu` schedules
385-
the DRA kubelet plugin only on nodes labeled
386-
`nvidia.com/dra-kubelet-plugin=true` (or the pair given to
387-
`aicr bundle --dra-eviction-node-label`). Set it **in the node pool
388-
definition**, with the other required node labels — an ad hoc
389-
`kubectl label node` does not survive node replacement, recycling,
390-
autoscaling, or a pool scaled from zero, so later nodes arrive unlabeled.
397+
```bash
398+
aicr bundle --recipe recipe.yaml \
399+
--dra-eviction-node-label nvidia.com/dra-kubelet-plugin=true \
400+
--output bundle
401+
402+
gcloud container node-pools create <pool> --cluster <cluster> \
403+
--node-labels="nvidia.com/dra-kubelet-plugin=true"
404+
```
391405

392-
An unlabeled GPU node fails silently: it runs no kubelet plugin and publishes
393-
no `ResourceSlices`, and neither Helm nor the bundle's `deploy.sh` reports an
394-
error. With no labeled GPU node at all the DaemonSet sits at `DESIRED=0`; with
395-
only some labeled, those nodes work while the rest silently lack DRA.
406+
See [DRA Driver Upgrade Eviction](../user/cli-reference.md#dra-driver-upgrade-eviction).
396407
This applies to existing clusters too — adding the selector during an
397408
upgrade removes a plugin that was previously working.
398-
See [Prepare DRA nodes before applying upgraded bundles](../user/bundling.md#prepare-dra-nodes-before-applying-upgraded-bundles).
409+
See [Prepare DRA nodes when opting in to eviction coordination](../user/bundling.md#prepare-dra-nodes-when-opting-in-to-eviction-coordination).
399410

400411
## Troubleshooting
401412

0 commit comments

Comments
 (0)