Files
cozystack/packages/core/platform/values.yaml
Aleksei Sviridkin 1d747b3d8d feat(platform): auto-wire KubeVirt permittedHostDevices and HostDevices feature gate when gpu-operator is enabled (#2768)
Closes #2765.

## What this PR does

Makes the passthrough and vGPU variants of `cozystack.gpu-operator`
produce a KubeVirt CR that is immediately usable, without an out-of-band
`kubectl patch`. Today the gpu-operator stack reports green,
`nvidia.com/<model>` shows up under node `.status.allocatable`, but
every `VirtualMachine` referencing the resource via
`domain.devices.hostDevices` is rejected by the admission webhook
because `HostDevices` is not in `developerConfiguration.featureGates`
and the resource is not listed under `permittedHostDevices`. That manual
reconciliation is now driven by the platform.

The change has three parts.

`packages/system/kubevirt` now parameterizes the KubeVirt CR template.
`developerConfiguration.featureGates` keeps its existing static defaults
and appends `.Values.extraFeatureGates`;
`spec.configuration.permittedHostDevices` renders verbatim from
`.Values.permittedHostDevices` when set;
`spec.configuration.mediatedDevicesConfiguration` does the same.
Defaults stay empty so the rendered CR is byte-identical to the previous
template when no overrides are supplied. The chart's `make update`
target re-applies the four sed transforms after fetching the upstream
CR, so refreshing the KubeVirt release version stays a single command.

`packages/core/platform` adds two defaults tables under `files/`.
`gpu-passthrough-defaults.yaml` covers Hopper (H100/H200), Ada Lovelace
(L4/L40/L40S), Ampere (A100 PCIe/SXM, A40, A30, A10), Turing (T4) and
Volta (V100/V100S); PCI vendor:device pairs are stable, `resourceName`
slugs follow the `<arch>_<model>_<form>_<mem>` convention
`nvidia-sandbox-device-plugin` generates as of v25.x, and
`externalResourceProvider: true` is set everywhere because those
resources are advertised by the sandbox plugin rather than KubeVirt's
in-tree device plugin. `gpu-vgpu-defaults.yaml` covers the Pascal–Ampere
mdev profile range (A100-40C/80C, A40-24Q/48Q, A30-24C, A10-24Q,
V100D-32C, T4-16Q) — the same family span the upstream
`vgpu-device-manager` walks `/sys/class/mdev_bus/` for. Ada Lovelace and
Blackwell SR-IOV vGPU is intentionally out of scope for the default
table and is expected to be advertised via the user-override hook
described below.

`templates/bundles/iaas.yaml` now selects between the two tables via a
new `bundles.iaas.gpuOperatorVariant` knob (`default` for passthrough,
`vgpu` for mediated; default is passthrough). The same variant flows
through to the gpu-operator Package CR, replacing the previous
always-default call. When the package is enabled, the platform injects
`HostDevices` into `extraFeatureGates` plus the chosen defaults into
`permittedHostDevices` for the kubevirt component's values. Operators
extend or replace via top-level platform values:

```yaml
gpu:
  replaceDefaults: false              # append (default) vs. wipe-and-replace
  permittedHostDevices:
    pciHostDevices: []                # cluster-specific PCI passthrough entries
    mediatedDevices: []               # cluster-specific vGPU entries
  mediatedDevicesConfiguration: {}    # extra mediated-device types / per-node mappings
```

`docs/gpu-vgpu.md` is updated to drop the manual `kubectl patch` step
and describe the auto-wiring contract, the per-variant defaults, the
escape hatches, and the sandbox-device-plugin `resourceName` slug
convention so an operator whose node publishes a different name knows
where to override.

## Test coverage

`helm-unittest` covers eight branches in
`packages/core/platform/tests/bundles_gpu_kubevirt_wiring_test.yaml`:
gpu-operator disabled emits no extras; gpu-operator enabled at default
variant emits the HostDevices gate plus `pciHostDevices`; vgpu variant
switches to `mediatedDevices` plus `mediatedDevicesConfiguration`; user
overrides append by default; `replaceDefaults: true` wipes the platform
table; an unknown variant fails fast with a descriptive error.
`packages/system/kubevirt/tests/kubevirt_cr_test.yaml` pins the
empty-values render and the three populated shapes at the CR template
level. `make helm-unit-tests` passes locally on the full repo.

### Release note

```release-note
feat(platform): the KubeVirt CR is now auto-configured for GPU host-device passthrough (HostDevices feature gate plus permittedHostDevices populated from NVIDIA defaults) whenever cozystack.gpu-operator is in bundles.enabledPackages. Operators no longer need a manual kubectl patch to make VMs schedule against a GPU. The variant is selected via bundles.iaas.gpuOperatorVariant (default: passthrough; vgpu: mediated). Extend or replace the NVIDIA defaults via .gpu.permittedHostDevices and .gpu.replaceDefaults.

**Upgrade note:** the bundle now OWNS spec.configuration.permittedHostDevices on the KubeVirt CR. If you previously hand-edited it (kubectl edit kubevirt), the first reconcile after upgrade overwrites your manual entries with the rendered NVIDIA default table. Before upgrading, move any custom entries into .gpu.permittedHostDevices (set .gpu.replaceDefaults: true to keep only your list) and verify each resourceName against what your nodes advertise (kubectl describe node <node> | grep nvidia.com/) — the default table now carries the exact slug the sandbox device plugin generates, which upper-cases the card's PCI-IDs name and keeps the Tesla/GL tokens (e.g. a Tesla T4 is nvidia.com/TU104GL_TESLA_T4), but a different plugin build can publish a different string; a mismatch makes the admission webhook reject GPU VMs on their next restart or migration.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Bundle-managed GPU Operator with selectable passthrough vs vGPU
variants; platform now injects GPU settings into KubeVirt and forwards
vGPU manager overrides when enabled.
* Default platform mappings for PCI passthrough and vGPU mediated-device
resources added.

* **Documentation**
* Expanded GPU/vGPU deployment guide with bundle-managed vs manual
guidance, wiring/precedence rules, resource-name conventions, and
verification steps.

* **Tests**
* New and expanded tests for bundle→KubeVirt wiring, variant behaviors,
default/override merging, and idempotent template parameterization.

* **Chores**
* Added Helm test target and hardened KubeVirt template
update/idempotency checks.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-10 02:02:01 +03:00

406 lines
19 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
sourceRef:
kind: OCIRepository
name: cozystack-platform
namespace: cozy-system
path: /
migrations:
enabled: false
image: ghcr.io/cozystack/cozystack/platform-migrations:v1.4.0-rc.2@sha256:17390197a9b11d76a8dd1b24c6d1512adb7d3226b28b5bcba8551b14b1ec4be0
targetVersion: 44
# Bundle deployment configuration
bundles:
system:
enabled: false
variant: "isp-full" # Options: "isp-full", "isp-full-generic", "isp-hosted", "distro-full"
iaas:
enabled: false
# gpu-operator variant selector — applies when cozystack.gpu-operator is
# in bundles.enabledPackages. "default" runs the vfio-pci passthrough
# stack (whole GPU → one VM); "vgpu" runs the vGPU Manager stack. The
# platform mirrors this into the KubeVirt CR: passthrough auto-wires the
# HostDevices feature gate plus a default permittedHostDevices.pciHostDevices
# table covering common NVIDIA datacenter GPUs (vendor 10DE); vgpu
# auto-wires permittedHostDevices.mediatedDevices plus a starter
# mediatedDevicesConfiguration. Operators with additional or non-NVIDIA
# cards extend the defaults via gpu.permittedHostDevices /
# gpu.mediatedDevicesConfiguration / gpu.replaceDefaults below.
gpuOperatorVariant: default
paas:
enabled: false
naas:
enabled: false
disabledPackages: []
enabledPackages: []
# Network configuration
networking:
clusterDomain: "cozy.local"
podCIDR: "10.244.0.0/16"
podGateway: "10.244.0.1"
serviceCIDR: "10.96.0.0/16"
joinCIDR: "100.64.0.0/16"
# KubeOVN master nodes override (optional)
# By default, KubeOVN helm chart uses `lookup` to find control-plane nodes
# by label `node-role.kubernetes.io/control-plane`. On fresh clusters or
# during initial deployment, lookup may return empty results.
# Set this to comma-separated list of master node IPs to override.
kubeovn:
MASTER_NODES: ""
# Service publishing and ingress configuration
publishing:
host: "example.org"
ingressName: tenant-root
exposedServices:
- api
- dashboard
- vm-exportproxy
- cdi-uploadproxy
apiServerEndpoint: "" # example: "https://api.example.org"
externalIPs: []
# Enable PROXY-protocol on the host ingress-nginx and auto-deploy
# ouroboros (system/ouroboros) to fix the resulting hairpin-NAT
# problem (intra-cluster traffic to the cluster's own public hostnames
# arrives at ingress-nginx without the PROXY header it now requires).
# Single cozystack-side switch: flipping this to true configures both
# pieces in one go — ingress-nginx use-proxy-protocol and the
# cozystack.ouroboros Package CR.
#
# TALOS RE-RENDER NOTE (host scope only): the host CoreDNS is a
# Talos-managed Deployment; ouroboros mutates its live Corefile in
# place between BEGIN/END markers. A Talos machine-config push or
# upgrade re-renders the Corefile and overwrites the markers — there
# is a flap window of up to one ouroboros reconcile loop (bounded
# by `controller.resync`, default 10 minutes) until the controller
# re-applies the block. The reconcile loop is ALSO event-driven on
# Ingress/Gateway watches, so on a busy cluster the window often
# closes much sooner — but on a stable production cluster with no
# incidental Ingress/Gateway churn, the upper bound is the resync
# interval. In-cluster DNS for hairpinned hostnames briefly returns
# the upstream LB IP instead of the proxy ClusterIP during that
# window, which fails the same way pre-PR behaviour fails.
# Acceptable, but worth knowing during a Talos roll.
#
# PRECONDITION: the upstream L4 load balancer / WAN edge in front of
# ingress-nginx MUST already be injecting PROXY-protocol v1 headers
# before flipping this flag. Without that, every external request
# arrives at ingress-nginx without the PROXY header, ingress-nginx
# closes the connection, and ALL ingress traffic breaks. cozystack
# cannot configure that upstream LB for you — it lives outside the
# cluster (cloud LB, F5, MetalLB+haproxy, hcloud-cloud-controller,
# etc.). PROXY-protocol frames travel from the upstream LB to
# ingress-nginx (the LB opens a fresh TCP connection to the backend
# and prepends the frame on that connection), not back to the
# client — `nc` from a laptop will never see one. Pick one of:
#
# * tcpdump --interface any 'port 80 or port 443' -A on the node
# hosting ingress-nginx and watch for a leading "PROXY TCP4 ..."
# frame on inbound TCP from the LB BEFORE flipping this flag.
# * make any external HTTP request after the LB-side change but
# before flipping this flag, and watch
# `kubectl --namespace cozy-ingress-nginx logs deploy/ingress-nginx-controller`
# for "client sent invalid proxy protocol header" — that error
# means the LB started injecting PROXY frames but ingress-nginx
# is not yet configured to accept them, which is the transition
# state this flag is meant to resolve.
#
# If neither check shows PROXY frames, fix the upstream LB before
# touching this flag. Leave it false on clusters where PROXY-protocol
# is not yet wired at the upstream LB; ouroboros wastes resources
# when the bug it fixes does not exist.
#
# FAN-OUT NOTE: this flag affects ONLY the host control plane. To
# turn the same hairpin fix on inside a tenant cluster, also set
# `addons.ouroboros.enabled: true` in the per-tenant Kubernetes CR
# (and `addons.ingressNginx.enabled: true` is a hard precondition
# there). The flag does not cascade into tenants on its own.
#
# DISABLE PATH: flipping this back from true to false has TWO
# asymmetric effects. ingress-nginx side: the switch travels through
# _cluster.proxy-protocol in the cozystack-values Secret, so the next
# reconcile re-renders the host ingress-nginx (extra/ingress) without
# the use-proxy-protocol / real-ip-header / enable-real-ip keys
# (cozystack deliberately never sets use-forwarded-headers or
# compute-full-forwarded-for — those would allow any upstream proxy
# to spoof X-Forwarded-For without a paired proxy-real-ip-cidr — see
# the rationale in extra/ingress/templates/nginx-ingress.yaml). The
# cozystack.ingress-application Package stays the bare default
# throughout; the keys never ride on it. ingress-nginx then stops
# accepting PROXY-protocol headers. If the upstream L4 LB is
# still prepending PROXY frames at that moment, every external
# request to ingress-nginx breaks until the LB is also reconfigured
# — flip the LB OFF FIRST, then flip this flag. ouroboros side:
# stops emitting the cozystack.ouroboros Package CR, but every Package
# CR carries `helm.sh/resource-policy: keep`, so Helm leaves the
# existing Package on the cluster, ouroboros stays installed, and
# the live Corefile rewrite block keeps pointing at the still-running
# ouroboros-proxy Service. Flag-flip alone leaves ouroboros idempotent.
#
# To actually uninstall ouroboros, the operator removes the Package
# CR explicitly:
#
# kubectl delete package.cozystack.io cozystack.ouroboros
#
# or adds the package name to bundles.disabledPackages. THAT is the
# path that triggers the chart's pre-delete Helm hook
# (charts/ouroboros/templates/coredns-cleanup-hook.yaml, vendored at
# 0.8.0) — the hook quiesces the controller, sed-strips the
# BEGIN/END block from the live kube-system/coredns Corefile, and
# restores the cleaned ConfigMap via kubectl replace. The hook is
# automatic; operators do NOT need to run a manual sed recipe in
# the normal disable path. Documentation of the hook's exact
# behaviour lives in the upstream chart's README.
#
# Manual cleanup recipe (only if the chart's pre-delete hook fails
# to land — controller pod stuck in CrashLoop blocking the quiesce
# step, ConfigMap RBAC drift on the namespace, helm uninstall
# interrupted before the hook ran). Uses a JSON merge-patch so
# labels, annotations, and other data keys on the ConfigMap —
# including any Talos-managed metadata — are preserved:
#
# # === BEGIN cleanup-recipe ===
# existing=$(kubectl --namespace kube-system get configmap coredns \
# --output jsonpath='{.data.Corefile}')
# cleaned=$(printf '%s\n' "$existing" \
# | sed '/# === BEGIN ouroboros/,/# === END ouroboros ===/d')
# kubectl --namespace kube-system patch configmap coredns \
# --type merge \
# --patch "$(jq --null-input --arg c "$cleaned" \
# '{data: {Corefile: $c}}')"
# kubectl --namespace kube-system rollout restart deployment/coredns
# # === END cleanup-recipe ===
proxyProtocol: false
# Acknowledgement gate for the helm.sh/resource-policy: keep
# asymmetry (NOT a substitute for the chart's pre-delete cleanup
# hook — the hook handles the Corefile patch automatically when
# the chart actually uninstalls). The hazard this gate catches:
# flipping publishing.proxyProtocol from true to false stops
# emitting the cozystack.ouroboros Package CR, but resource-policy:
# keep means Helm leaves the existing Package on the cluster.
# ouroboros keeps running, the cleanup hook never fires, the
# rewrite block stays live. The platform render fails until the
# operator either restores proxyProtocol: true OR runs `kubectl
# delete package.cozystack.io cozystack.ouroboros` (which lets
# the chart actually uninstall and the hook run) and sets this
# flag to true to confirm. Once the cluster has been clean for
# one reconcile cycle, flip back to false.
#
# KNOWN HOLE: the guard keys off a `lookup` for the cozystack.ouroboros
# Package CR on the cluster. An operator who manually
# `kubectl delete package cozystack.ouroboros` before flipping the
# flag back bypasses the check entirely (the lookup returns nil, the
# render proceeds). In the chart 0.8.0 that path is mostly safe —
# `kubectl delete package` triggers helm uninstall and the chart's
# pre-delete hook patches the Corefile automatically. The hole is
# the rare case where the hook itself fails (controller pod stuck,
# ConfigMap RBAC drift, hook timeout) and the operator never
# noticed. The values comment + the acknowledge gate are
# defence-in-depth, not an airtight lock; an operator deliberately
# working around them gets to keep the broken pieces.
proxyProtocolAcknowledgeUnclean: false
certificates:
solver: http01 # "http01" or "dns01"
issuerName: letsencrypt-prod
# Rate-limit note: every tenant with gateway=true issues one
# Certificate for its wildcard + apex hostnames. If many tenants share
# the same apex domain with issuerName=letsencrypt-prod, the cluster
# can hit the Let's Encrypt limits (50 certs / registered domain /
# week, 5 duplicate certs / week, 300 new orders / account / 3h).
# Use letsencrypt-stage for non-production clusters or gate the
# number of certificates per tenant via tenant.spec.resourceQuotas
# with count/certificates.cert-manager.io. See
# https://letsencrypt.org/docs/rate-limits/.
#
# DNS-01 solver configuration. Read only when solver=dns01. The
# provider key selects which sub-block is consumed; others are
# ignored. Cloudflare keeps backwards-compatible defaults so the
# most common opt-in path (cloudflare token + dns01) needs only
# solver=dns01 + dns01.cloudflare.secretName.
dns01:
provider: cloudflare # cloudflare | route53 | digitalocean | rfc2136
cloudflare:
secretName: cloudflare-api-token-secret
secretKey: api-token
route53:
region: ""
accessKeyID: ""
secretName: ""
secretKey: secret-access-key
digitalocean:
secretName: digitalocean-api-token-secret
secretKey: access-token
rfc2136:
nameserver: ""
tsigKeyName: ""
tsigAlgorithm: HMACSHA256
secretName: ""
secretKey: tsig-secret-key
# Authentication configuration
authentication:
oidc:
enabled: false
insecureSkipVerify: false
keycloakExtraRedirectUri: ""
# Internal URL to access KeyCloak realm for backend-to-backend requests (bypasses external DNS).
# When set, oauth2-proxy uses --skip-oidc-discovery and routes all backend calls (token, jwks,
# userinfo, logout) through this URL while keeping browser redirects on the external URL.
# Example: http://keycloak-http.cozy-keycloak.svc:8080/realms/cozy
keycloakInternalUrl: ""
# Gateway API configuration
gateway:
# Enable Gateway API support across the platform. When true:
# - cert-manager ClusterIssuers use an http01.gatewayHTTPRoute solver
# that attaches to the root tenant's Gateway, so Certificate issuance
# works for tenants whose ingress is via Gateway API rather than
# ingress-nginx.
# - Exposed services (dashboard, keycloak, harbor, bucket, grafana,
# cozystack-api, vm-exportproxy, cdi-uploadproxy) render an
# HTTPRoute attached to the tenant's Gateway.
# Per-tenant opt-in is still governed by tenant.spec.gateway=true, which
# materialises the Gateway itself. This flag only controls platform-wide
# Gateway API integration (cert-manager solver + per-service HTTPRoutes).
enabled: false
# Namespaces that are allowed to attach HTTPRoute or TLSRoute to a tenant
# Gateway. The publishing tenant namespace is always included implicitly.
# Additional entries must be the exact namespace name (matched on the
# built-in kubernetes.io/metadata.name label). Tenants not on this list
# cannot attach a route to somebody else's Gateway, which prevents
# hostname hijacking across tenants.
#
# The `default` namespace is included because the Kubernetes API's
# TLSRoute (shipped by the cozystack-api package) has to live next to
# the `kubernetes` Service it points at, which is always in `default`.
attachedNamespaces:
- cozy-cert-manager
- cozy-dashboard
- cozy-keycloak
- cozy-system
- cozy-harbor
- cozy-bucket
- cozy-kubevirt
- cozy-kubevirt-cdi
- cozy-monitoring
- cozy-linstor-gui
- default
# Pod scheduling configuration
scheduling:
globalAppTopologySpreadConstraints: ""
# UI branding configuration
branding: {}
# Container registry mirrors configuration
#
# Example:
# registries:
# mirrors:
# docker.io:
# endpoints:
# - http://10.0.0.1:8082
# ghcr.io:
# endpoints:
# - http://10.0.0.1:8083
# gcr.io:
# endpoints:
# - http://10.0.0.1:8084
# registry.k8s.io:
# endpoints:
# - http://10.0.0.1:8085
# quay.io:
# endpoints:
# - http://10.0.0.1:8086
# cr.fluentbit.io:
# endpoints:
# - http://10.0.0.1:8087
# docker-registry3.mariadb.com:
# endpoints:
# - http://10.0.0.1:8088
# config:
# "10.0.0.1:8082":
# tls:
# insecureSkipVerify: true
registries: {}
# Resource allocation ratios
resources:
cpuAllocationRatio: 10
memoryAllocationRatio: 1
ephemeralStorageAllocationRatio: 40
# GPU host-device wiring for VirtualMachine workloads. Auto-injected into the
# KubeVirt CR's permittedHostDevices when bundles.enabledPackages contains
# cozystack.gpu-operator. Without it the admission webhook rejects any VM
# that references a GPU host-device by resourceName.
gpu:
# When false (default), the lists below are appended to the platform's
# NVIDIA defaults. When true, the user lists fully replace the defaults
# — use this for clusters with only non-NVIDIA accelerators or where a
# strictly curated allowlist is required.
replaceDefaults: false
permittedHostDevices:
# PCI passthrough devices. Always rendered when non-empty regardless of
# gpuOperatorVariant — pciHostDevices covers both the vfio-pci passthrough
# path (default variant) and the post-kubevirt#16890 SR-IOV vGPU path
# (vgpu variant on Ada Lovelace / Blackwell, where VFs are advertised
# via pciHostDevices, not via mediatedDevices). Default-table coverage:
# H100, H200, A100 PCIe/SXM, A40, A30, A10, L40, L40S, L4, T4, V100,
# V100S (PCI vendor 10DE). Item shape matches KubeVirt's pciHostDevices:
# - pciVendorSelector: "10DE:2335"
# resourceName: nvidia.com/GH100_H200_SXM_141GB
# externalResourceProvider: true
pciHostDevices: []
# Mediated (mdev) vGPU devices — pre-kubevirt#16890 path used by
# Pascal–Ampere on the legacy mdev driver branch. Default-table coverage:
# A100-40C/80C, A40-24Q/48Q, A30-24C, A10-24Q, V100D-32C, T4-16Q.
# Item shape matches KubeVirt's mediatedDevices:
# - mdevNameSelector: NVIDIA A100-40C
# resourceName: nvidia.com/A100-40C
# externalResourceProvider: true
mediatedDevices: []
# mediatedDevicesConfiguration makes KubeVirt itself create mdevs on the
# node (vgpu variant only). The platform ships NO default here: mdev
# creation is delegated to the GPU Operator's vGPU Device Manager
# (name-based), and the mediatedDeviceTypes ids below are numeric
# nvidia-NNN sysfs indices that vary per GPU SKU and driver branch — no
# value is portable, so look yours up on the host
# (/sys/bus/pci/devices/<BDF>/mdev_supported_types/*/name) before setting
# it. Supplying it opts into KubeVirt-driven creation on top of the
# Device Manager. Helm's mergeOverwrite semantics apply: a top-level key
# you supply REPLACES that key wholesale rather than appending. Shape
# matches KubeVirt's MediatedDevicesConfiguration:
# mediatedDeviceTypes:
# - nvidia-740
# nodeMediatedDeviceTypes:
# - nodeSelector:
# nvidia.com/gpu.product: NVIDIA-L40S
# mediatedDeviceTypes:
# - nvidia-740
mediatedDevicesConfiguration: {}
# NVIDIA vGPU Manager container image (vgpu variant only). The .run is
# proprietary, not redistributable, so the platform cannot ship default
# tags — the operator builds the container per
# https://github.com/NVIDIA/gpu-driver-container and supplies the
# private-registry coordinates here. The platform forwards these as
# components.gpu-operator.values.gpu-operator.vgpuManager to the emitted
# cozystack.gpu-operator Package CR when bundles.iaas.gpuOperatorVariant
# is vgpu. Forwarding triggers when ANY of repository, version, image
# (non-default), or imagePullSecrets is set — partial values still
# propagate so a private-registry-with-default-tag setup is not
# silently dropped.
vgpuManager:
repository: ""
image: vgpu-manager
version: ""
imagePullSecrets: []
# vGPU Device Manager controls whether nvidia.com's
# `vgpu-device-manager` DaemonSet is deployed (vgpu variant only). The
# DaemonSet walks /sys/class/mdev_bus/ to auto-create mediated devices
# from the mediatedDeviceTypes list. Default-off because that path
# only exists on Pascal–Ampere (V100, T4, A100, A30, …) with the mdev
# driver branch — on Ada Lovelace / Blackwell (L4, L40, L40S, B100)
# with the vGPU 17/20 driver branch the directory is missing and the
# DaemonSet crash-loops with "no parent devices found". Flip to true
# on Pascal–Ampere clusters; leave false on Ada/Blackwell and supply
# SR-IOV VFs via .gpu.permittedHostDevices.pciHostDevices instead.
# When true the platform also lets the default name-based mediatedDevices
# exposure list from files/gpu-vgpu-defaults.yaml flow into the KubeVirt
# CR; when false those defaults are suppressed (advertising mediated
# resources that nothing can ever allocate is a worse footgun than
# missing entries).
vgpuDeviceManager:
enabled: false