Skip to content

Commit 16d007b

Browse files
committed
feat(recipes)!: GKE bundle-installer replaces driver-installer
Add the gcp-driver-installer component — Google's cos-gpu-installer DaemonSet as a values-gated, AICR-managed component — and replace the GKE gpuStack value driver-installer with bundle-installer, which owns it. The gpuStack family on GKE COS is now: - gke-default (default, unchanged): GKE's managed driver install and GKE's managed device plugin as the nvidia.com/gpu advertiser. - bundle-installer: opt-out pools (gke-no-default-nvidia-gpu-device-plugin=true, created with gpu-driver-version=disabled) — the GPU Operator's device plugin is the sole advertiser and the bundle's gcp-driver-installer component supplies the driver. The version is pinned in the recipe (gcp-driver-installer.driverVersion, default matching the GPU Operator chart's driver pin; must be COS-qualified) and upgrades roll with the bundle — nothing is applied out-of-band. The component sits unconditionally in the gke-cos chain, gated on the nested installer.enabled path (top-level install/enabled are component-presence gates and would deadlock profile resolution — ADR-015 sketch amended); under gke-default it renders a template-less no-op chart. The two values are distinguished at generation time by the opt-out pool label (negated vs positive), which resolves ADR-015 Deferred Decision 5 without any post-deployment signal. BREAKING: driver-installer (shipped v0.19.0) is removed; selecting it fails closed with the valid-values list. Migration for pools that used a hand-applied standalone nvidia-driver-installer DaemonSet: delete that DaemonSet (the bundle's DaemonSet shares its name in kube-system and Helm will not adopt the pre-existing object), regenerate with --profile gpuStack=bundle-installer, and deploy the bundle. Nodes with a loaded driver are untouched (installer fast path). Also: CheckNVSentinelDriverLabelDetectable accepts bundle-installer (the bundle-carried installer IS a driver pod the labeler detects); the deployment validator's static-assert suppression is generalized (gatedHealthCheckSuppressed) so a values-gated component whose render is empty skips its DaemonSet health check; cos-nvidia-installer:fixed gets a digest-pin exemption (COS-node-local image, never pulled or mirrored); parity goldens, BOM, and recipe-health docs regenerated. Fixes #1716 Related #2344 Signed-off-by: Atif Mahmood <atif1996@users.noreply.github.com>
1 parent 709b179 commit 16d007b

28 files changed

Lines changed: 649 additions & 163 deletions

docs/design/015-recipe-configuration-profiles.md

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,12 @@ target state, gated by this rule.
213213

214214
```yaml
215215
# recipes/overlays/gke-cos.yaml — device-plugin ownership
216-
# Shown with the post-DD5 value set. DD5's durable marker, not yet
217-
# identified, will additionally land as symmetric constraints on
218-
# operator and operator-selfdriver at that event — the constraints
219-
# drawn here are therefore not yet the declarable post-DD5 state.
216+
# Shown with the value set as originally drawn. Amended 2026-08-24: the
217+
# family ships as gke-default plus bundle-installer — the drawn
218+
# operator-selfdriver, which REPLACED the operator value (shipped
219+
# v0.19.0 as driver-installer). The two shipped values differ in the
220+
# opt-out pool label at generation time, so Deferred Decision 5 needed
221+
# no durable marker.
220222
spec:
221223
profile:
222224
name: gpuStack
@@ -277,7 +279,14 @@ spec:
277279
componentRefs:
278280
- name: gcp-driver-installer
279281
overrides:
280-
install: true # the chart-level gate
282+
# Amended 2026-08-24: shipped as the nested installer.enabled,
283+
# not the top-level `install` drawn here — top-level
284+
# `install`/`enabled` are component-PRESENCE gates (IsEnabled),
285+
# so a false default would make the component "not enabled in
286+
# the surviving composition" and deadlock resolution for every
287+
# value; root `overrides.enabled` is separately rejected in
288+
# fragments. A nested key is an ordinary owned value path.
289+
installer: {enabled: true}
281290
- name: gpu-operator
282291
overrides:
283292
devicePlugin: {enabled: true}
@@ -297,9 +306,10 @@ step 2 describes. Selection:
297306
# gke-default (declared default; drawn above as csp-managed) — no flag needed
298307
aicr recipe --service gke --os cos --accelerator h100 --intent inference
299308
300-
# explicit alternative configuration (shipped name; drawn above as operator)
309+
# explicit alternative configuration (shipped name; drawn above as
310+
# operator-selfdriver)
301311
aicr recipe --service gke --os cos --accelerator h100 --intent inference \
302-
--profile gpuStack=driver-installer
312+
--profile gpuStack=bundle-installer
303313
```
304314

305315
A profile fragment may reference only components **enabled in the
@@ -324,7 +334,8 @@ explicit toggles from changing the pre-existing presence state, with
324334
subset-filter semantics defined under Override locking.
325335

326336
**Conditional installation is expressible in v1 through a values-gated
327-
component** — the `operator-selfdriver` value above is the pattern.
337+
component** — the `operator-selfdriver` value above (shipped as
338+
`bundle-installer`) is the pattern.
328339
The component sits unconditionally in the composition and renders
329340
nothing unless its gate value is selected; the profile value flips a
330341
plain values path — owned, locked, and validated like any other —
@@ -1457,13 +1468,27 @@ recurrence — the shape the Problem section expects.
14571468
installer's synthetic `enabled` joins `ownedPaths`), which is a
14581469
family-wide re-qualification and evidence re-signing event.
14591470

1471+
*Amended 2026-08-24 (issue #1716).* The landing event shipped the value
1472+
as `bundle-installer`, **replacing** `driver-installer` rather than
1473+
joining it: the two named the same pool shape
1474+
(`gpu-driver-version=disabled` + the opt-out label), differing only in
1475+
who applied the installer DaemonSet — precisely Deferred Decision 5's
1476+
ambiguity, which the replacement resolves without any signal. The
1477+
shipped family is `gke-default` / `bundle-installer`, distinguished at
1478+
generation time by the pool label alone. Selecting
1479+
`gpuStack=driver-installer` fails closed with the valid-values list;
1480+
migration: delete any hand-applied `nvidia-driver-installer` DaemonSet
1481+
(the bundle's DaemonSet shares its name) and regenerate with
1482+
`--profile gpuStack=bundle-installer`.
1483+
14601484
Any dcgm-exporter GPU-ID-mapping adjustment for `csp-managed` is an
14611485
external GKE behavior not verifiable from this repository. It is
14621486
verified and added during this step if required, with upstream
14631487
citations recorded in that PR. Before conversion, this step also
14641488
checks the family's `/v1` usage or announces a deprecation window so
14651489
clients do not discover the `/v1` rejection at cut-over.
1466-
4. Other consumers: once `operator-selfdriver` is declared, internal
1490+
4. Other consumers: once `operator-selfdriver` (shipped as
1491+
`bundle-installer`) is declared, internal
14671492
recipes (DGXC/NKX) migrate the cos-gpu-installer arrangement
14681493
(internal MR #27) to the public value. The values-gated
14691494
`gcp-driver-installer` component makes the case expressible without
@@ -1495,8 +1520,8 @@ work that resolves it.
14951520
GKE's managed driver install, so the standalone gate's prerequisite
14961521
needed the profile's per-value pairing), and the GKE `gpuStack`
14971522
profile now consumes the form per selected value (#1761 rollout
1498-
PR 3): positive for `driver-installer`, negated for the
1499-
`gke-default` default.
1523+
PR 3): positive for `driver-installer` (later `bundle-installer`),
1524+
negated for the `gke-default` default.
15001525
3. **AKS node-pool-mode signal — resolved by the 2026-07-27 amendment.**
15011526
The provider-facing AgentPool `gpuProfile.driver` property is the
15021527
durable ownership marker. AKS adoption projects it into a snapshot
@@ -1522,3 +1547,9 @@ work that resolves it.
15221547
absence, so the two values stay mutually distinguishable.
15231548
**Proposed: identify a durable signal during the value's adoption;
15241549
the `operator` and `csp-managed` values do not wait on it.**
1550+
1551+
*Resolved 2026-08-24 (issue #1716): no signal was needed.* The value
1552+
shipped as `bundle-installer`, **replacing** `operator`
1553+
(shipped `driver-installer`) — the two named the same pool shape, and
1554+
with the sibling gone the family's values are mutually distinguishable
1555+
at generation time by the opt-out pool label alone.

docs/integrator/automation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ are not time-bounded and persist until `Client.Close()` is called.
366366
> family (profiled and unprofiled) with the same criteria parameters, plus
367367
> optional `profile=gpuStack=azure-managed` or
368368
> `profile=gpuStack=operator-managed` on AKS, and
369-
> `profile=gpuStack=gke-default` or `profile=gpuStack=driver-installer`
369+
> `profile=gpuStack=gke-default` or `profile=gpuStack=bundle-installer`
370370
> on GKE
371371
> (see [GKE GPU Setup](gke-gpu-setup.md#gpu-device-plugin-ownership)).
372372
> `/v1/recipe` still works for unprofiled compositions but rejects any

docs/integrator/data-extension.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ When in doubt, `aicr --debug recipe ... --data <dir>` logs the resolved source
231231
`recipes/overlays/aks.yaml` declares the `gpuStack` configuration profile
232232
(`azure-managed` default, `operator-managed` alternative) over the GPU driver/toolkit
233233
ownership paths, and `recipes/overlays/gke-cos.yaml` declares its own `gpuStack`
234-
(`gke-default` default, `driver-installer` alternative) over device-plugin ownership —
234+
(`gke-default` default, `bundle-installer` alternative) over device-plugin ownership —
235235
the GKE default value (`gke-default`) additionally declares
236236
`advertiser: external`, and both GKE values trigger the #1327
237237
allocation-policy closure, so their effective lock set is larger than the

docs/integrator/gke-gpu-setup.md

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ synthesizes the GPU-node universe from the snapshot's `NodeTopology.label`
3838
readings (nodes carrying `cloud.google.com/gke-accelerator`) and quantifies a
3939
label predicate over it, in both directions — the positive form
4040
`gke-no-default-nvidia-gpu-device-plugin=true` (every GPU node carries the
41-
label) qualifies `driver-installer`, and the negated form
41+
label) qualifies `bundle-installer`, and the negated form
4242
`!gke-no-default-nvidia-gpu-device-plugin` (no GPU node carries the key)
4343
qualifies `gke-default`.
4444

@@ -54,8 +54,8 @@ aicr snapshot -o snapshot.yaml
5454
aicr recipe --service gke --accelerator h100 --os cos --intent training \
5555
--platform kubeflow \
5656
--snapshot snapshot.yaml -o recipe.yaml # gke-default default
57-
# ... or, for labeled pools with the standalone driver installer:
58-
# --profile gpuStack=driver-installer
57+
# ... or, for labeled pools (bundle-carried driver installer):
58+
# --profile gpuStack=bundle-installer
5959

6060
# 3. Bundle. NVSentinel needs no overrides: the gpuStack profile sets its
6161
# labeler flag per value. See the NVSentinel note below.
@@ -65,7 +65,7 @@ aicr bundle -r recipe.yaml -o ./bundles
6565
The reading qualifies the selection — it does not choose for you. Every
6666
combination is deterministic:
6767

68-
| GPU-node labels read | Default (`gke-default`) | `--profile gpuStack=driver-installer` |
68+
| GPU-node labels read | Default (`gke-default`) | `--profile gpuStack=bundle-installer` |
6969
|---|---|---|
7070
| all GPU nodes label-absent | ✅ resolves | ❌ fails closed: constraint expects the label on every GPU node |
7171
| all GPU nodes `gke-no-default-nvidia-gpu-device-plugin=true` | ❌ fails closed: constraint expects no labeled GPU node | ✅ resolves |
@@ -85,9 +85,9 @@ the snapshot, and the check is NEVER skipped when a snapshot is present:
8585
| Invocation | Selected value | Node-label check |
8686
|---|---|---|
8787
| no `--profile`, no `--snapshot` | declaration default (`gke-default`) | none possible (no cluster data) — the constraint is still recorded in the recipe and enforced at `aicr validate` readiness |
88-
| `--profile gpuStack=driver-installer`, no `--snapshot` | `driver-installer` | same — deferred to validate |
88+
| `--profile gpuStack=bundle-installer`, no `--snapshot` | `bundle-installer` | same — deferred to validate |
8989
| no `--profile`, `--snapshot` | default (`gke-default`) | checked at generation: no GPU node may carry the opt-out label, else generation fails closed naming the observed state |
90-
| `--profile gpuStack=driver-installer`, `--snapshot` | `driver-installer` | checked at generation: every GPU node must carry `gke-no-default-nvidia-gpu-device-plugin=true`, else fails closed |
90+
| `--profile gpuStack=bundle-installer`, `--snapshot` | `bundle-installer` | checked at generation: every GPU node must carry `gke-no-default-nvidia-gpu-device-plugin=true`, else fails closed |
9191

9292
If you need an unverified recipe deliberately, generate criteria-only (drop
9393
`--snapshot`): the artifact is honest about being unqualified, and the
@@ -165,7 +165,7 @@ override is needed:
165165

166166
Because the path is profile-owned, a bundle-time `--set` diverging from the
167167
selected value is **rejected** rather than silently applied. The explicit
168-
`false` under `driver-installer` is deliberate: skipping detection there would
168+
`false` under `bundle-installer` is deliberate: skipping detection there would
169169
keep the label applied across an unloaded driver.
170170

171171
The value renders the labeler's `--assume-driver-installed` argument — the
@@ -176,7 +176,7 @@ recommended, permanent mechanism for host-installed drivers (no automatic
176176
detection fallback will be added). Under `gke-default` a recipe that reaches
177177
bundle generation without it is a **blocking error**
178178
(`CheckNVSentinelDriverLabelDetectable`), so the silent half-rollout cannot
179-
ship. Under `driver-installer` the gate does not fire: the standalone installer
179+
ship. Under `bundle-installer` the gate does not fire: the bundle's installer
180180
supplies an observable driver pod.
181181

182182
**Labeling the nodes by hand does not persist.** Applying the label manually:
@@ -191,28 +191,34 @@ its next reconcile. Design 018 documents manual labeling as the procedure for
191191
this case, so an operator following it will see it work and later find the
192192
DaemonSets back at 0 desired.
193193

194-
### Alternative: Let GPU Operator Manage the Device Plugin
194+
### Alternative: Let the Bundle Own the GPU Stack
195195

196196
If you prefer the GPU Operator's device plugin to own `nvidia.com/gpu`
197197
advertisement, select the mode at recipe generation:
198198

199199
```shell
200200
aicr recipe --service gke --accelerator h100 --os cos --intent training \
201201
--platform kubeflow \
202-
--profile gpuStack=driver-installer -o recipe.yaml
202+
--profile gpuStack=bundle-installer -o recipe.yaml
203203
aicr bundle -r recipe.yaml -o ./bundles
204204
```
205205

206-
This value has real cluster prerequisites. The opt-out label forfeits GKE's
206+
This value has real pool prerequisites. The opt-out label forfeits GKE's
207207
managed driver install: the managed install (`gpu-driver-version=default` or
208208
`latest`) is finalized by an init container of the **same** kube-system
209209
DaemonSet the label disables, so a labeled pool paired with the managed
210210
install comes up **driverless** — never combine the label with
211-
`gpu-driver-version=default`/`latest`. Pools for the `driver-installer` value
212-
must instead be created with `gpu-driver-version=disabled`, with driver
213-
provisioning supplied by Google's standalone
214-
[`nvidia-driver-installer` DaemonSet](https://cloud.google.com/kubernetes-engine/docs/how-to/gpus#installing_drivers)
215-
applied to the cluster.
211+
`gpu-driver-version=default`/`latest`. Pools for the `bundle-installer` value
212+
must instead be created with `gpu-driver-version=disabled`. Driver
213+
provisioning is carried **inside the bundle**: the `gcp-driver-installer`
214+
component ships Google's cos-gpu-installer DaemonSet
215+
([#1716](https://github.com/NVIDIA/aicr/issues/1716)), ordered ahead of the
216+
GPU Operator, with the driver version pinned in the recipe
217+
(`gcp-driver-installer.driverVersion`, default matching the GPU Operator
218+
chart's driver pin). The pin must be COS-qualified: the installer validates
219+
the request against the COS build's curated per-GPU-type list and rejects
220+
unqualified versions. Version bumps take effect on replaced or rebooted
221+
nodes only (the installer skips nodes with a loaded nvidia module).
216222

217223
Set the label when you create the GPU node pool, alongside the disabled
218224
managed install:
@@ -283,7 +289,7 @@ and GPU pods will not schedule. That brief advertiser-free window is the
283289
accepted cost of the handoff direction — do **not** invert it by deploying
284290
the Operator's plugin onto a still-unlabeled pool, which would put two
285291
advertisers on the same nodes (the dual-advertisement state the
286-
[allocation-policy gates](#the-three-driver-installer-settings) exist to
292+
[allocation-policy gates](#the-three-bundle-installer-settings) exist to
287293
prevent). Have the bundle from step 5 generated in advance to keep the
288294
window short, and avoid scheduling GPU work during it.
289295
Note that `--node-labels` on update **replaces** the pool's full user-label
@@ -310,7 +316,7 @@ which the update rejects. Omitting an existing label removes it from the
310316
pool's nodes, which can break scheduling that depends on it.
311317

312318
**Step 5 — deploy the GPU Operator and wait for its plugin.** Deploy the
313-
AICR bundle generated with `--profile gpuStack=driver-installer`, then wait
319+
AICR bundle generated with `--profile gpuStack=bundle-installer`, then wait
314320
until the Operator's device-plugin pods are Running on the labeled nodes and
315321
every GPU node again reports non-zero allocatable `nvidia.com/gpu` — that
316322
closes the advertiser-free window opened in step 4. Confirm the full result
@@ -351,7 +357,7 @@ advertiser — if the GPU Operator is not yet deployed (or its plugin is not
351357
Ready), labeling leaves the node with **no** `nvidia.com/gpu` advertiser at
352358
all, and GPU pods will not schedule until the Operator's plugin comes up.
353359

354-
#### The three driver-installer settings
360+
#### The three bundle-installer settings
355361

356362
The three settings cover different parts of the GPU stack:
357363

@@ -360,14 +366,26 @@ The three settings cover different parts of the GPU stack:
360366
effect, forfeits GKE's managed driver install (the installer rides the
361367
DaemonSet the label disables).
362368
- `gpu-driver-version=disabled` records that GKE does not own driver
363-
provisioning on the pool — and is what makes the standalone installer act
364-
on its nodes (the installer ignores automatic-install pools). Never pair
365-
the label with `gpu-driver-version=default` — labeled pools come up
366-
driverless.
367-
- The standalone `nvidia-driver-installer` DaemonSet supplies the driver.
369+
provisioning on the pool — and is what makes the bundle's installer act
370+
on its nodes (it ignores automatic-install pools). Never pair the label
371+
with `gpu-driver-version=default` — labeled pools come up driverless.
372+
- The bundle's `gcp-driver-installer` DaemonSet supplies the driver.
368373
AICR's GKE-COS overlays keep `driver.enabled: false` because the GPU
369374
Operator cannot install a driver on COS node images.
370375

376+
#### Migrating from a hand-applied installer DaemonSet
377+
378+
Earlier arrangements (including AICR's replaced `driver-installer` profile
379+
value, shipped in v0.19.0) supplied the driver by applying Google's standalone
380+
[`nvidia-driver-installer` DaemonSet](https://cloud.google.com/kubernetes-engine/docs/how-to/gpus#installing_drivers)
381+
by hand on the same pool shape. Do not run that alongside a
382+
`bundle-installer` bundle: the bundle's DaemonSet shares the name
383+
`nvidia-driver-installer` in `kube-system`, and Helm will not adopt the
384+
pre-existing object. To migrate: delete the hand-applied DaemonSet,
385+
regenerate with `--profile gpuStack=bundle-installer`, and deploy the
386+
bundle. Nodes with a loaded driver are untouched (the installer's fast path
387+
skips them); the bundle takes over provisioning for new and rebooted nodes.
388+
371389
## Troubleshooting
372390

373391
### Labeled pool comes up driverless
@@ -394,11 +412,10 @@ the pairing "label + managed driver install" is never functional.
394412
[Retrofitting an existing pool](#retrofitting-an-existing-pool)) so GKE's
395413
DaemonSet returns and finalizes the managed install, and generate (or keep)
396414
recipes with the default selection.
397-
- **Commit to `driver-installer`:** apply Google's standalone
398-
`nvidia-driver-installer` DaemonSet and recreate the pools with
415+
- **Commit to `bundle-installer`:** recreate the pools with
399416
`gpu-driver-version=disabled` (or update their driver mode in place — see
400417
[Retrofitting an existing pool](#retrofitting-an-existing-pool)), then
401-
generate recipes with `--profile gpuStack=driver-installer`.
418+
generate recipes with `--profile gpuStack=bundle-installer`.
402419

403420
### No advertiser at all
404421

docs/integrator/recipe-development.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ directions fail closed on a truncated node list (a snapshot captured with
548548
reading), on an empty GPU-node universe, and on malformed or ambiguous
549549
label readings (an encoding collision between a disambiguated entry and a
550550
distinct dotted label name — see #2003). It is consumed by the GKE
551-
`gpuStack` profile values (the positive form qualifies `driver-installer`, the
551+
`gpuStack` profile values (the positive form qualifies `bundle-installer`, the
552552
negated form `gke-default`), where each selected value's constraint is
553553
verified at generation when generating from a snapshot (criteria-only
554554
generation has no snapshot evaluator and defers entirely to the

docs/user/api-reference.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ converted workflow to v2 as one cut-over.
514514
reject. Move GET clients to `GET /v2/recipe` / `GET /v2/query` (identical
515515
query parameters, plus optional `profile=gpuStack=azure-managed` or
516516
`profile=gpuStack=operator-managed` on AKS, and `profile=gpuStack=gke-default`
517-
or `profile=gpuStack=driver-installer` on GKE); move POST
517+
or `profile=gpuStack=bundle-installer` on GKE); move POST
518518
clients to `POST /v2/recipe` / `POST /v2/query`, converting the body to the
519519
strict envelope described above (a plain `criteria` object with an explicit
520520
`Content-Type`, not the v1 `RecipeCriteria` resource). Then POST the
@@ -523,12 +523,12 @@ unaffected on `/v1` until they adopt a profile.
523523

524524
```shell
525525
# GKE migration: /v2/recipe (omit profile= for the gke-default default,
526-
# or select gpuStack=driver-installer explicitly), then POST to /v2/bundle.
526+
# or select gpuStack=bundle-installer explicitly), then POST to /v2/bundle.
527527
# -f stops on an HTTP error so a 4xx/5xx recipe body is never staged and
528528
# an error response is never written to bundles.zip.
529529
set -euo pipefail
530530
curl -fsS -o recipe.json \
531-
"http://localhost:8080/v2/recipe?service=gke&accelerator=h100&os=cos&intent=training&profile=gpuStack=driver-installer"
531+
"http://localhost:8080/v2/recipe?service=gke&accelerator=h100&os=cos&intent=training&profile=gpuStack=bundle-installer"
532532
curl -fsS -X POST "http://localhost:8080/v2/bundle" \
533533
-H "Content-Type: application/json" -d @recipe.json -o bundles.zip
534534
```

0 commit comments

Comments
 (0)