fix(chart): Helm-managed GPU device plugin (client#564) - #712
Conversation
…ubectl (client#564) The GPU device plugin was applied with an imperative `kubectl apply` of an upstream manifest fetched at install time, outside any Helm release. It was not release-tracked, so it lingered through `helm uninstall` and was re-created on every installer re-run, and it added a raw.githubusercontent.com network dependency to the install path. Render it as a Helm-managed DaemonSet (templates/gpu-device-plugin.yaml) gated on gpu.devicePlugin.enabled + vendor, in kube-system, with the manifest baked into the chart (no install-time download). The Linux installer sets the value from GPU_VENDOR in lockstep with the GPU request it writes for training jobs, and no longer applies the plugin imperatively; node verification moves to after the Helm install. A bounded, best-effort adoption shim labels/annotates a pre-existing unowned DaemonSet so a re-run after `helm uninstall` adopts it instead of failing with "exists and cannot be imported". Scope: the Windows non-WSL2 fallback keeps its imperative apply. That path must verify the node advertises a GPU BEFORE it writes the GPU request, which a Helm-managed resource (rolled out during Helm install) can't satisfy in a single install; the WSL2/CDI path deliberately doesn't use the NVML plugin at all. Chart.yaml version + appVersion bumped 1.9.39 -> 1.9.40 (chart-version-guard). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… edits (client#564) The Static-analysis job's supply-chain check (gen-manifest.sh --check) fails when a hashed sub-script changes without a manifest refresh. Re-hash the three edited scripts (install-k8s.sh, lib/gpu-plugins.sh, lib/install-client-helm.sh). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… --reuse-values (client#564) The Fleet auto-upgrade E2E upgrades from the last published release (no gpu block) with --reuse-values, which does NOT merge values.yaml defaults, so .Values.gpu was nil and .Values.gpu.devicePlugin.enabled panicked with a nil pointer. Access via parenthesised indexing ((.Values.gpu).devicePlugin) and default the per-vendor image/namespace sub-keys. Adds a helm-unittest case with gpu: null. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # scripts/manifest.sha256
…ull-secrets (client#564) Address Cursor Bugbot findings on the Helm-managed GPU device plugin: - HIGH: guard gpu values with the chart's `default dict` pattern so a `helm upgrade --reuse-values` from a pre-bump release (no gpu key) can never evaluate .enabled on nil. Go-template `and` is not short-circuit, so `and $dp $dp.enabled` still panicked; coalescing to dict removes it. - MEDIUM: route both vendor images through tracebloc.image with (dig "imageRegistry" "" $g) | default <vendor> so global.imageRegistry (#585 mirror/air-gap) re-homes them and an empty imageRegistry falls back to the vendor registry (was silently becoming docker.io); add imagePullSecrets + copy the pull secret into the plugin namespace (resource-monitor pattern) so mirrored/air-gapped hosts can pull. - Split images into registry/repository/tag/digest in values + schema; add unittests for digest pinning, mirror re-home, and pull-secret presence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ate (client#564) Address Cursor Bugbot findings on the Helm-managed GPU device plugin: - MEDIUM (chart bumps restart the plugin): the nvidia/amd pod templates embedded tracebloc.labels (helm.sh/chart + app.kubernetes.io/version), so every client chart bump changed the pod template and rolling-restarted the device plugin. Use version-free tracebloc.selectorLabels on the pod template (resource-monitor pattern); tracebloc.labels stays on DaemonSet metadata. - MEDIUM (verify races plugin rollout): verify_gpu ran after Helm but the release does not --wait and the old rollout gate was removed, so the node poll could expire while the plugin was still pulling and warn 'may still be initializing' on a healthy install. Restore a bounded, best-effort 'kubectl rollout status daemonset' (120s) before the node poll. - Update the gpu-nvidia.bats gating assertion to match the nil-safe 'default dict' chain (the pre-refactor literal was removed), and regenerate scripts/manifest.sha256 for the gpu-plugins.sh edit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ut timeout (client#564) Address the follow-up Cursor Bugbot findings on 012d001: - HIGH (adoption probe fails open): _adopt_orphaned_gpu_device_plugin read any failed 'kubectl get' as 'DaemonSet absent' and swallowed label/annotate failures with '|| true', so a leftover-but-unadopted DS made 'helm upgrade --install' die 'exists and cannot be imported' and abort the whole (GPU-optional) install. Now: distinguish absent/NotFound from a live API error (warn, don't fake-adopt); and if adoption can't be completed, delete the orphan so the chart recreates a clean Helm-owned copy instead of bricking Step e. Added bats coverage for both paths. - MEDIUM (rollout wait uses request-timeout): dropped --request-timeout=10s from the verify_gpu 'kubectl rollout status' — on a watch it capped the wait to ~10s and defeated --timeout=120s. --timeout is the correct explicit bound. - Regenerated scripts/manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…elop (client#564) develop's Chart.yaml reached 1.9.40 — the version this branch had bumped to — so chart-version-guard's version-check failed (chart content changed without a version strictly greater than develop). Bump version+appVersion to 1.9.41 in lockstep and merge origin/develop to stay current. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2438ef0. Configure here.
… can't abort a fresh install (client#564) Bugbot (High): the adoption probe used a bare 'probe=$(kubectl get ...); rc=$?'. Under set -e a NotFound on a fresh GPU host makes the assignment exit non-zero and aborts installer step e before the absent-vs-error branch runs. Use the house '|| rc=$?' idiom so a non-zero lookup is classified, not fatal. Add a subshell set -e regression test (the mocked 'run' tests can't catch it since run disables errexit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LukasWodka
left a comment
There was a problem hiding this comment.
Reviewed the full diff and the Bugbot iteration end-to-end. This correctly moves the GPU device plugin off the imperative kubectl apply of a fetched raw.githubusercontent.com manifest and into a Helm-tracked DaemonSet (client/templates/gpu-device-plugin.yaml), gated on gpu.devicePlugin.enabled (default false, so CPU-only and the Windows/WSL2 path render nothing), with the manifest baked into the chart and deliberately no helm.sh/resource-policy: keep — so it now reconciles on upgrade and is removed on helm uninstall, which is exactly what #564 asked for.
I confirmed the old Linux imperative path is fully gone, not just superseded: deploy_gpu_device_plugin / _apply_remote_manifest / _deploy_{nvidia,amd}_plugin are deleted from scripts/lib/gpu-plugins.sh, install-k8s.sh no longer calls them (verify_gpu now runs after the Helm install), and a bats guard asserts no kubectl apply/curl/old-helpers survive — so there's no orphaned DaemonSet double-management. The NVIDIA/AMD DaemonSets faithfully mirror upstream (kube-system, nvidia.com/gpu toleration + system-node-critical, FAIL_ON_INIT_ERROR=false so a non-GPU node no-ops instead of crash-looping, arch nodeSelector + CriticalAddonsOnly for AMD, hostPath device-plugins socket), the installer flips the flag in lockstep with GPU_VENDOR, and _adopt_orphaned_gpu_device_plugin cleanly handles the re-run-after-uninstall 'exists and cannot be imported' collision. The reuse-values nil-guard (default dict chain) and global.imageRegistry + pull-secret mirroring close the air-gap/reuse-values gaps Bugbot raised.
Chart bumped 1.9.40 -> 1.9.41 (version guard green), manifest.sha256 regenerated for the 3 touched scripts (Static-analysis/R8 green), all 9 Bugbot findings resolved, and every required check is green. Solid, well-tested change (helm-unittest + bats both assert real invariants rather than restating the template).
Minor, non-blocking: the adoption-fallback kubectl delete in install-client-helm.sh still carries only --timeout=30s while its sibling get/label/annotate calls all gained --request-timeout — a fully wedged apiserver could leave that one HTTP call unbounded. Bugbot flagged and it was resolved, so just noting for awareness. Nice work.

Problem
The GPU device plugin was applied with an imperative
kubectl applyof an upstream manifest fetched at install time, outside any Helm release. It wasn't release-tracked, so it lingered throughhelm uninstalland was re-created on every installer re-run, and it added araw.githubusercontent.comnetwork dependency to the install path. (scripts/install-k8s.ps1:2490in the issue is stale; the live Linux apply wasscripts/lib/gpu-plugins.sh→deploy_gpu_device_plugin.)Fix
Render the plugin as a Helm-managed DaemonSet —
client/templates/gpu-device-plugin.yaml, gated ongpu.devicePlugin.enabled+vendor, inkube-system, with the manifest baked into the chart (no install-time download). It's reconciled on upgrade and removed onhelm uninstall. Nohelm.sh/resource-policy: keep— sharing the release lifecycle is the point.lib/install-client-helm.shsetsgpu.devicePlugin.{enabled,vendor}fromGPU_VENDOR, in lockstep with the GPU request it already writes for training jobs.install-k8s.shno longer applies the plugin imperatively before Helm;verify_gpumoves to after the Helm install (the plugin now rolls out with the release)._adopt_orphaned_gpu_device_plugin) labels/annotates a pre-existing, Helm-unowned DaemonSet so a re-run afterhelm uninstalladopts it in place instead of failing with "exists and cannot be imported" — exactly the issue's re-run scenario.Chart.yamlversion + appVersion bumped 1.9.39 → 1.9.40 (chart-version-guard).Scoping: Linux vs Windows
Linux requests a GPU purely on
GPU_VENDORdetection, so a chart-managed plugin works cleanly. Windows can't use it here: the non-WSL2 fallback must verify the node advertises a GPU before it writes the GPU request, and a Helm-managed resource only rolls out during Helm install — so that path keeps its imperative apply for now. The WSL2/CDI path deliberately never runs the NVML plugin. Defaultgpu.devicePlugin.enabled: falsemeans Windows renders no chart DaemonSet (no double-deploy).Tests
client/tests/gpu_device_plugin_test.yaml(default renders nothing; nvidia/amd render; namespace+image override; Helm-ownership label; fail-on-missing-vendor).scripts/tests/gpu-nvidia.batsretargeted from the removed imperative-apply assertions to the chart-managed invariants;install-client-helm.bats+3 for the adoption shim.helm lint --strict(all 4 platforms),helm templaterenders, bats suites all green. (4 unrelated helm-unittest suites fail only under local helm v4 / unittest 1.1.0 vs CI-pinned v3.15.4 / 0.5.2 — same ondevelop.)Follow-ups (not in this PR)
Confirm-GpuNodecan be reordered (verify-before-request). The chart template already supports it.rocm/k8s-device-plugin:latest) to a digest for air-gapped/reproducible installs.Closes #564
🤖 Generated with Claude Code
Note
Medium Risk
Changes cluster-level DaemonSets in kube-system and install ordering for GPU paths; adoption/delete shims and bounded rollout waits reduce but do not eliminate upgrade friction on clusters with pre-existing plugins.
Overview
Moves the GPU device plugin from imperative
kubectl apply+ GitHub manifest fetch into the Helm chart, so it is reconciled on upgrade and removed onhelm uninstall(client#564). Chart 1.9.41 addsgpu.devicePlugin(NVIDIA/AMD DaemonSets inkube-systemby default), nil-safe gating,global.imageRegistry/ digest pinning, and optionalimagePullSecretswith the pull secret mirrored into the plugin namespace.The Linux installer stops deploying the plugin before cluster/Helm setup;
install-client-helm.shturns the plugin on fromGPU_VENDORand runs_adopt_orphaned_gpu_device_pluginso leftover imperative DaemonSets are adopted (or deleted) instead of blockinghelm upgrade --install.verify_gpuruns after Helm and waits on DaemonSet rollout before node capacity polling. Imperative deploy logic is removed fromgpu-plugins.sh; helm-unittest and bats cover rendering, adoption, and ordering.Reviewed by Cursor Bugbot for commit dd78549. Bugbot is set up for automated code reviews on this repo. Configure here.