Skip to content

feat(kubernetes): default gpu-operator to an OS-provided driver on Talos - #3585

Open
mattia-eleuteri wants to merge 2 commits into
cozystack:mainfrom
mattia-eleuteri:feat/kubernetes-gpu-operator-os-driver
Open

feat(kubernetes): default gpu-operator to an OS-provided driver on Talos#3585
mattia-eleuteri wants to merge 2 commits into
cozystack:mainfrom
mattia-eleuteri:feat/kubernetes-gpu-operator-os-driver

Conversation

@mattia-eleuteri

@mattia-eleuteri mattia-eleuteri commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Closes the gpu-operator half of #3563 — gap 3 (the operator cannot validate a driver it did not install) and gap 4 (nothing routes CUDA libraries to a tenant pod).

Tenant clusters have run Talos workers since 1.6.0, where the NVIDIA driver comes from a system extension baked into the worker image rather than from the operator's driver container. The addon default still described the Ubuntu shape, so a GPU node group came up with the operator fighting the OS for the driver and no path at all for the CUDA libraries.

Three changes, each closing one silent failure:

Change Without it
driver.enabled: false, toolkit.enabled: false the driver container loads its own module and clashes with the extension's; the toolkit container rewrites /etc/containerd/config.toml, which Talos owns and which already registers the nvidia runtime with enable_cdi = true and cdi_spec_dirs = ['/run/cdi']
validator.driver.env: DRIVER_ROOT=/, NVIDIA_DRIVER_ROOT=/, DISABLE_DEV_CHAR_SYMLINK_CREATION=true the driver validator looks for a driver-container install, finds none, never writes its sentinel, and the device plugin stays blocked behind toolkit-validation — the loop on Attempting to validate a driver container installation (gap 3)
devicePlugin.env: DEVICE_LIST_STRATEGY=cdi-annotations, NVIDIA_CTK_PATH=/usr/local/bin/nvidia-ctk, plus DEVICE_ID_STRATEGY, NVIDIA_DRIVER_ROOT, CDI_ANNOTATION_PREFIX tenant pods carry no runtimeClassName and did not need one on Ubuntu, where the toolkit made nvidia the default containerd runtime. Talos does not, so CDI is the only route: the pod gets the device and no CUDA libraries (gap 4)

Every value is the configuration validated on a production Talos GPU node group, minus the parts that only mattered while the driver container was in play. No DaemonSet, no new image, no new package — the fix is values.

The removed default

The previous default fed NVreg_NvLinkDisable=1 to the driver container through the nvidia-kernel-module-params ConfigMap. With that container off the ConfigMap is mounted by nothing, so this removes the default and the parameter travels through the worker machine config instead, as nodeGroups.<name>.kernelModules (#3571). The wrapper chart keeps the ConfigMap feature for an operator who re-enables the driver container through valuesOverride, with its comment corrected to say nothing uses it by default any more. A test pins the absence so the dead default cannot come back and offer a second, inert source for the same setting.

Relationship to #3571

Independent files, no conflict, but only useful together: this makes driver.enabled: false correct, and #3571 is what actually loads the modules the extension ships. Merged alone, this changes a GPU node group from "operator fights the OS" to "operator stays out of the way and the modules still are not loaded" — better, but not yet working. #3571 alone leaves the operator fighting for the driver. Either order is safe; both are needed for a GPU node group to work.

What is verified, and what is not

Verified here: the rendered HelmRelease values, that valuesOverride still wins over each new key, and that the removed default is gone. The individual values are the ones running in production today, applied by hand through addons.gpuOperator.valuesOverride.

Not verified here: this exact default exercised end-to-end as the chart's own output. That needs a Talos GPU node group whose modules load, which needs #3571 released. I would rather say so than imply a green e2e that does not exist — happy to hold this as draft until I have run a single-node L40S cluster against the chart default, and I will report the result on this PR either way.

Not covered

HAMi. When addons.hami.enabled the operator's device plugin stays off and HAMi serves the GPUs; HAMi's own plugin needs equivalent CDI wiring on Talos, which this default does not attempt. Flagging rather than guessing — the production cluster this was derived from runs HAMi disabled, so I have no validated configuration for that path. Worth its own issue if a maintainer confirms the shape.

Also not in scope, and unchanged from the merge base: talos.installerRepository and talos.version reach the worker machine config unvalidated (being tracked separately by Aleksei Sviridkin (@lexfrei)), and the nvidia-operator-validator chain for the management cluster's own passthrough/vgpu variants, which never load the host driver at all.

Screenshots

No UI changes.

Screenshots

Downstream repositories

Release note

feat(kubernetes): The GPU Operator addon of tenant Kubernetes clusters now defaults to a driver provided by the operating system, which is the shape of the Talos workers these clusters have run since 1.6.0. `driver` and `toolkit` are disabled (the driver comes from a Talos system extension, and containerd configuration belongs to the machine config), the driver validator is pointed at the host root so it stops looping on "Attempting to validate a driver container installation", and the device plugin is switched to CDI (`DEVICE_LIST_STRATEGY=cdi-annotations`) so tenant pods receive the CUDA libraries — previously they received the GPU device and nothing else, since Talos does not make `nvidia` the default containerd runtime. The previous default, which fed `NVreg_NvLinkDisable=1` to the operator's driver container through a ConfigMap, is removed because that container is disabled; the parameter is now carried by the worker machine config through `nodeGroups.<name>.kernelModules`. Operators who overrode `addons.gpuOperator.valuesOverride` to work around this by hand can drop those overrides. NOTE: an override that sets `devicePlugin.env` replaces the list rather than merging it, so restate the full list when overriding it or CDI stops working. HAMi is not covered: when `addons.hami.enabled` the operator's device plugin stays off and HAMi's own plugin needs equivalent CDI wiring, which this change does not attempt.

Summary by CodeRabbit

  • New Features

    • GPU workloads on Talos workers now use NVIDIA drivers provided by the host operating system.
    • Improved GPU device discovery through CDI annotations and UUID-based device identification.
    • Enhanced compatibility with host-installed NVIDIA tooling and driver paths.
  • Bug Fixes

    • Prevented unnecessary GPU driver containers and kernel module configuration from being enabled by default.
    • Updated validation and symlink handling for host-provided drivers.
  • Documentation

    • Clarified GPU driver configuration options and advanced re-enablement requirements.

Closes the gpu-operator half of cozystack#3563. Tenant clusters have run Talos
workers since 1.6.0, where the NVIDIA driver comes from a system extension
baked into the worker image rather than from the operator's driver
container. The addon default still described the Ubuntu shape, so a GPU
node group came up with the operator fighting the OS for the driver and no
path at all for CUDA libraries to reach a tenant pod.

Three things change, each fixing one silent failure:

driver.enabled and toolkit.enabled go false. The driver container would
load its own module and clash with the extension's; the toolkit container
would rewrite /etc/containerd/config.toml, which Talos owns and which
already registers the nvidia runtime with enable_cdi = true and
cdi_spec_dirs = ['/run/cdi'].

validator.driver.env points DRIVER_ROOT and NVIDIA_DRIVER_ROOT at / and
disables /dev/char symlink creation. The driver validator otherwise looks
for a driver container install, finds none, never writes its sentinel, and
leaves the device plugin blocked behind toolkit-validation — the loop on
"Attempting to validate a driver container installation".

devicePlugin.env selects cdi-annotations and points NVIDIA_CTK_PATH at the
host's nvidia-ctk. Tenant pods carry no runtimeClassName and did not need
one on Ubuntu, where the toolkit made nvidia the default containerd
runtime; Talos does not, so CDI is the only route for the CUDA libraries.

The previous default fed NVreg_NvLinkDisable=1 to the driver container
through the nvidia-kernel-module-params ConfigMap. With that container off
the ConfigMap is mounted by nothing, so the default is removed and the
parameter travels through the worker machine config instead. The wrapper
chart keeps the ConfigMap feature for an operator who re-enables the
driver container, with its comment corrected to say nothing uses it by
default any more.

Every value here is the configuration validated on a production Talos GPU
node group, minus the parts that only mattered while the driver container
was in play.

Not covered: HAMi. When addons.hami.enabled the operator's device plugin
stays off and HAMi serves the GPUs, and HAMi's own plugin needs equivalent
CDI wiring on Talos that this default does not attempt.

Refs: cozystack#3563

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Mattia Eleuteri <mattia@hidora.io>
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 65d39c26-7f74-4dab-8087-9029da2a4e1d

📥 Commits

Reviewing files that changed from the base of the PR and between 10137b8 and 48007bd.

📒 Files selected for processing (1)
  • packages/apps/kubernetes/tests/gpu_operator_hami_test.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/apps/kubernetes/tests/gpu_operator_hami_test.yaml

📝 Walkthrough

Walkthrough

GPU Operator defaults now use Talos host-installed NVIDIA drivers instead of operator-managed driver containers. Validator and CDI settings were updated, HAMi integration was preserved, and tests cover disabled resources and environment override behavior.

Changes

Talos GPU Operator integration

Layer / File(s) Summary
Host driver and CDI configuration
packages/apps/kubernetes/templates/helmreleases/gpu-operator.yaml, packages/system/gpu-operator/values.yaml
The operator disables driver and toolkit containers, validates drivers from the host root, configures CDI device injection, and documents the required Talos kernel configuration.
Configuration and override validation
packages/apps/kubernetes/tests/gpu_operator_hami_test.yaml
Tests verify host-driver settings, absent driver-container ConfigMaps, CDI defaults, and wholesale replacement of devicePlugin.env.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Suggested labels: area/platform

Suggested reviewers: ivanhunters

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: configuring the Kubernetes GPU Operator to use an OS-provided NVIDIA driver on Talos.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mattia-eleuteri

Copy link
Copy Markdown
Collaborator Author

End-to-end run done, on hardware, and it holds. Reporting it as promised rather than leaving the claim on unit tests alone.

Setup: a fresh single-node tenant cluster, one node group with an L40S (nvidia.com/AD102GL_L40S), the open-kernel-modules schematic, machine.kernel.modules added by hand since #3571 is not released yet, and no DaemonSet of any kind. The values under test were applied through addons.gpuOperator.valuesOverride with content identical to this PR's default.

Check Result
NVIDIA modules loaded nvidia, nvidia_uvm, nvidia_modeset, nvidia_drm all Live in /proc/modules
nvidia-operator-validator Running, 0 restarts — no loop on Attempting to validate a driver container installation (gap 3)
nvidia-cuda-validator Succeeded
GPU advertised nvidia.com/gpu: 1 on the node
Tenant pod reaches the GPU nvidia-smi -LGPU 0: NVIDIA L40S (UUID: GPU-4bae1a13-…) (gap 4)
Device plugin env in effect exactly the six values this PR sets, plus CDI_ENABLED=true derived by the operator
Node stability 93 minutes on one bootID, GPU still advertised, 0 restarts on every gpu-operator pod

The last row matters because a Talos node whose extension services cannot start reboots at roughly 70 minutes of uptime while still reporting Ready; 93 minutes on a single boot rules that out. Note a one-time reboot right after the install is normal and is not that failure — the node object is younger than its CAPI Machine, which is expected.

The L40S is the point: everything behind this configuration was previously only exercised on Blackwell (GB202). It works unchanged on Ada, so the set is not GPU-generation-specific.

What this run does not establish, stated plainly:

  • It exercised the values, not this PR's default. The cluster runs the released chart, so the set arrived through valuesOverride; that the chart emits it by default is covered by the unit tests here and nothing more. Testing the default itself needs a release carrying this PR.
  • It says nothing about whether NVreg_NvLinkDisable=1 is needed. An L40S is PCIe and has no NVLink, so the run shows the parameter is harmless and that CUDA initialises with it. Its necessity rests on the SXM passthrough case in production, not on this cluster.
  • CDI injection was confirmed by its effect, not by inspecting containerd. Worth recording for whoever debugs this next: with DEVICE_LIST_STRATEGY=cdi-annotations no CDI annotation appears on the Pod object — the device plugin returns it in the Allocate response and kubelet passes it to the CRI runtime. Its absence from kubectl get pod -o yaml is expected and is not evidence that CDI failed. The evidence is nvidia-smi working inside a pod whose image ships no driver.
  • HAMi remains untested and uncovered, as the PR body says.

Taking this out of draft on the strength of the above.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/system/gpu-operator/values.yaml (1)

37-48: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the stale NVreg_NvLinkDisable=1 default.

Lines 37-43 state that this parameter is no longer a default. However, Lines 47-48 still supply it when an operator sets driver.enabled: true and kernelModuleConfig.create: true.

Leave content empty by default. Require an operator to set the parameter explicitly when it is required.

Proposed fix
   name: nvidia-kernel-module-params
-  content: |
-    NVreg_NvLinkDisable=1
+  content: ""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/system/gpu-operator/values.yaml` around lines 37 - 48, Update the
kernelModuleConfig default in values.yaml so its content is empty instead of
supplying NVreg_NvLinkDisable=1. Preserve the existing create and name settings,
requiring operators to explicitly provide the parameter when enabling this
configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/apps/kubernetes/tests/gpu_operator_hami_test.yaml`:
- Around line 46-66: Add contains assertions in the gpu-operator test for
NVIDIA_DRIVER_ROOT, DEVICE_ID_STRATEGY set to uuid, and CDI_ANNOTATION_PREFIX
set to cdi.k8s.io/. Place them alongside the existing validator.driver and
devicePlugin environment assertions, preserving the exact expected paths and
values.

---

Outside diff comments:
In `@packages/system/gpu-operator/values.yaml`:
- Around line 37-48: Update the kernelModuleConfig default in values.yaml so its
content is empty instead of supplying NVreg_NvLinkDisable=1. Preserve the
existing create and name settings, requiring operators to explicitly provide the
parameter when enabling this configuration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9efeb3d5-ea76-4de7-b09e-3bc34d570290

📥 Commits

Reviewing files that changed from the base of the PR and between 879d0f6 and 10137b8.

📒 Files selected for processing (3)
  • packages/apps/kubernetes/templates/helmreleases/gpu-operator.yaml
  • packages/apps/kubernetes/tests/gpu_operator_hami_test.yaml
  • packages/system/gpu-operator/values.yaml

Comment thread packages/apps/kubernetes/tests/gpu_operator_hami_test.yaml
@github-actions github-actions Bot added size/L This PR changes 100-499 lines, ignoring generated files area/kubernetes Issues or PRs related to the tenant Kubernetes app kind/feature Categorizes issue or PR as related to a new feature labels Aug 6, 2026
Review feedback on the addon default: the suite checked
DEVICE_LIST_STRATEGY and NVIDIA_CTK_PATH but not NVIDIA_DRIVER_ROOT,
DEVICE_ID_STRATEGY or CDI_ANNOTATION_PREFIX, so a regression in any of
those three could break CDI device resolution while the assertions still
passed. CDI device names key on the UUID strategy, the plugin resolves the
driver from NVIDIA_DRIVER_ROOT, and the annotation prefix has to be the one
containerd reads. Pin all of them, plus the validator's own
NVIDIA_DRIVER_ROOT alongside DRIVER_ROOT.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Mattia Eleuteri <mattia@hidora.io>
@mattia-eleuteri

Copy link
Copy Markdown
Collaborator Author

Two findings from the review: one taken, one I disagree with.

Taken — the missing assertions. 48007bd pins NVIDIA_DRIVER_ROOT, DEVICE_ID_STRATEGY: uuid and CDI_ANNOTATION_PREFIX: cdi.k8s.io/ on the device plugin, plus the validator's own NVIDIA_DRIVER_ROOT alongside DRIVER_ROOT. The reasoning is right: CDI device names key on the UUID strategy, the plugin resolves the driver from NVIDIA_DRIVER_ROOT, and the annotation prefix has to be the one containerd reads, so a regression in any of the three breaks device resolution while the two original assertions still pass.

Not taken — emptying kernelModuleConfig.content. I think this would make things worse rather than remove staleness.

That content is only read when an operator sets both driver.enabled: true and kernelModuleConfig.create: true, which is to say when they have deliberately re-enabled the driver container. In that configuration NVreg_NvLinkDisable=1 is the correct value for Cozystack, not an accident: individual GPUs are passed into worker VMs without the NVSwitches, so the driver otherwise waits forever for an NVLink fabric that can never come up — Fabric State stays In Progress and every CUDA call fails with system not yet initialized. Emptying the default means an operator who re-enables the driver container silently loses the parameter and lands in exactly that failure, which is the class of silent breakage this PR and #3571 exist to remove.

There is also no contradiction to resolve. "No variant enables it any more" and "the content is right when it is enabled" are both true at once: create: false already makes it inert, and content is the whole payload, so an operator who wants different parameters overrides that key and replaces it wholesale rather than merging into it. Nothing is silently inherited.

The comment above it was updated in this PR to say precisely that nothing enables it by default and that it is kept for an operator who re-enables the driver container, who then also has to set create: true. If that reads as stale rather than as a documented escape hatch, the fix is in the wording, and I would rather sharpen the comment than remove a default that is correct whenever it is reachable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/kubernetes Issues or PRs related to the tenant Kubernetes app kind/feature Categorizes issue or PR as related to a new feature size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant