Skip to content

vfio-pci for VMs, and the DRA SR-IOV driver

S2·E5The appliance that owned the card · The last open item on the acceptance list, Round Rock

S2·E5Analyze~25 minsources checked todaylab mutates hardwareverified against NVIDIA Network Operator v26.7.0 KubeVirt SR-IOV Integration, DRA SR-IOV Driver and Platform Support pages, all re-fetched 2026-09-09; sriov-network-device-plugin and sriov-network-operator repositories and the Kubernetes device-plugin and Topology Manager pages as fetched 2026-09-07

Builds on: How kubelet learns a NIC exists, SR-IOV plumbing: device plugin, CNI and operator

Before you read: what do you already know?

3 quick questions. Wrong answers are fine and expected; trying first makes the lesson stick.

After this lesson you can

  • Contrast the netDevice and vfio-pci device types by what the host keeps and what the consumer receives.
  • Assemble the three KubeVirt objects and the host prerequisites that put a VF inside a virtual machine.
  • Explain how a ResourceClaimTemplate selects a VF and which attributes a ResourceSlice publishes.
  • Decide whether a customer can use the DRA SR-IOV driver today and justify the answer from the two stated limits.

Episode 5 — The appliance that owned the card

The situation · The last open item on the acceptance list, Round Rock

The containers are done. The training run passes, the network lead’s notebook has a page per node group, and the only thing standing between the SE and row 14 of his spreadsheet is a legacy appliance the customer cannot retire this year. It runs as a KubeVirt virtual machine, it needs one of the same ConnectX cards, and it has never once reached Running. The customer’s conclusion is that the operator does not support the card.

You run one command before opening a single CRD: ls /sys/kernel/iommu_groups/ | wc -l returns zero. The kernel has no IOMMU on this boot, whatever the BIOS screen says, and NVIDIA lists IOMMU as a prerequisite of the whole KubeVirt path - intel_iommu=on iommu=pt on Intel, amd_iommu=on iommu=pt on AMD - alongside the vfio-pci module on the worker nodes.[1]

It is a prerequisite rather than a tuning flag for one reason worth saying in a single breath: a guest that owns a NIC can make that NIC issue DMA, and the IOMMU is what confines those addresses to the guest’s own memory. Which is also why the same VF behaves so differently in the two worlds. For a container it stays a host kernel netdevice. For a virtual machine the host gives the function away entirely - deviceType: vfio-pci, isRdma: false, and mlx5_core required inside the guest image instead.[1]

Passthrough is a firmware and kernel conversation before it is a Kubernetes one.

Segment 1 puts those two handovers side by side.

1One VF, two completely different handovers

Everything in this module so far has produced the same shape: a VF that stays a kernel network device on the host, and a pod that is allowed to use it. That is deviceType: netDevice, and it is the default when the device plugin’s ConfigMap says nothing.[3] The plugin also accepts accelerator and auxNetDevice, and the selector list differs between them.[3]

vfio-pci is not a fourth flavour of the same thing. It changes who owns the hardware. NVIDIA’s KubeVirt page defines it as passing “a VF’s PCI device directly into the guest VM via the VFIO userspace interface”.[1] The host stops driving the function: there is no mlx5_core binding, no host netdevice, nothing for ip link to show. The guest gets the PCI function and must bring its own driver.

Two consequences follow immediately, and both surprise people.

First, the RDMA flag goes off. NVIDIA’s example policy sets isRdma: false and the page is explicit that RDMA is incompatible with VFIO passthrough.[1] That is not a performance compromise. isRdma asks the host-side plumbing to expose RDMA resources for a host netdevice, and with vfio-pci there is no host netdevice; the guest runs its own RDMA stack over its own driver.

Second, the guest image becomes part of the network design. “NVIDIA VFs passed via VFIO require the mlx5_core driver inside the guest VM.”[1] Without it the device is present in the guest’s lspci and no interface appears, which reads on a ticket as a passthrough failure and is a guest-image problem.

The resource-name rules from lesson 2 do not change here. The device plugin still advertises <prefix>/<resourceName>, and the pod - in this case the virt-launcher pod KubeVirt creates for the VM - still has to request that exact string.[3][8]

Who configured the device plugin?
device plugin → node allocatablenvidia.com/hostdevkubectl describe node | grep -A20 AllocatableNetworkAttachmentDefinitionnvidia.com/kubevirt_sriovannotation k8s.v1.cni.cncf.io/resourceNamepod specnvidia.com/kubevirt_sriovresources.limits / resources.requests≠ NAD names something else
step 2 / 4
pod/sriov-test

Hidden until step 4. Walk the three places first, then predict.

NVIDIA Network Operator

2. The NAD references that name

The NAD names the resource so Multus knows which allocated device to hand the delegate. It must match prefix included: nvidia.com/hostdev here.

Namespace matters as much as the name: the annotation is k8s.v1.cni.cncf.io/networks and a NAD in another namespace must be written ns/name (OpenShift Multus enforces isolation by default).

⚠ The annotation key k8s.v1.cni.cncf.io/resourceName is not quoted verbatim in this course's captured sources; the operator CRs (SriovNetwork, HostDeviceNetwork) expose it as a resourceName field that generates the NAD.

What this source writes
# NicClusterPolicy .spec.sriovDevicePlugin.config
{ "resourceList": [{
    "resourcePrefix": "nvidia.com",
    "resourceName": "hostdev",
    "selectors": { "vendors": ["15b3"], "isRdma": true }
}]}
Advertised: nvidia.com/hostdev · allocatable 8
  • NVIDIA pod examples request nvidia.com/hostdev: 1 in both requests and limits, with securityContext.capabilities.add: ["IPC_LOCK"].
  • The GPUDirect RDMA example requests nvidia.com/hostdev: 1 and nvidia.com/gpu: 1 in the same container, so both hint providers vote for NUMA alignment.
  • HostDeviceNetwork carries the same name in its own resourceName field and generates the NAD for you.

FAE angle. Most escalations are a runbook written for one of these four sources being applied to a cluster configured by another. Ask who wrote the ConfigMap before you read any YAML.

Network Operator — Deployment Guide (Kubernetes) · Kubernetes device plugins · Multus how-to · netop-sosreport
Same name, two other routes

vfio-pci for VMs. A SriovNetworkNodePolicy with deviceType: vfio-pci, numVfs: 8, nicSelector.vendor "15b3", pfNames [ens1f0] and deliberately isRdma: false. The VF is passed into the guest through the VFIO userspace interface, so the guest needs mlx5_core and the host needs intel_iommu=on iommu=pt (or amd_iommu=on iommu=pt). The resource-name mechanics are identical: the SriovNetwork references the same resourceName and generates the NAD.

DRA SR-IOV driver (Tech Preview). Dynamic Resource Allocation replaces the plain name match with a ResourceClaimTemplate: deviceClassName sriovnetwork.k8snetworkplumbingwg.io plus a CEL expression device.attributes["k8s.cni.cncf.io"].resourceName == "nvidia.com/sriov_resource". Devices are published as ResourceSlice objects carrying PCIe bus ID, NUMA node and vendor. Needs the dynamicResourceAllocation: true feature gate, is vanilla Kubernetes only (not OpenShift), and is not recommended for production.

KubeVirt SR-IOV integration · DRA SR-IOV driver

The vfio-pci path is a different device type but the same naming contract. Set the source to OpenShift and watch which of the three places you would have to change on a virtualisation cluster.

2The three objects, in the order they must exist

NVIDIA documents the KubeVirt path as three objects plus a set of host prerequisites.[1]

One - the node policy. It creates the functions and advertises them, and it is the only place vfio-pci appears.

apiVersion: sriovnetwork.openshift.io/v1
kind: SriovNetworkNodePolicy
metadata:
  name: kubevirt-policy
  namespace: nvidia-network-operator
spec:
  resourceName: kubevirt_sriov
  nodeSelector:
    feature.node.kubernetes.io/network-sriov.capable: "true"
  numVfs: 8
  nicSelector:
    vendor: "15b3"
    pfNames:
      - ens1f0
  deviceType: vfio-pci
  isRdma: false

Note the API group: the CRDs are sriovnetwork.openshift.io/v1 even on vanilla Kubernetes, because that is the group the upstream SR-IOV Network Operator uses.[1][4] The node selector is an NFD label, so a node whose NFD never ran matches nothing and the policy applies to zero nodes without error.[1]

Two - the network. SriovNetwork is what turns the advertised resource into a NetworkAttachmentDefinition in the workload namespace, which is why you do not hand-write the NAD on this path.[1][4]

apiVersion: sriovnetwork.openshift.io/v1
kind: SriovNetwork
metadata:
  name: sriov-kubevirt-net
  namespace: nvidia-network-operator
spec:
  resourceName: kubevirt_sriov
  networkNamespace: default
  spoofChk: "off"
  trust: "on"

networkNamespace: default is the namespace the generated NAD lands in - and on OpenShift, where Multus enforces namespace isolation by default, that value is the whole ballgame.[5]

Three - the VM. The interface type is sriov and the network is a Multus reference to the generated NAD.[1]

spec:
  domain:
    devices:
      interfaces:
        - name: sriov-net
          sriov: {}
  networks:
    - name: sriov-net
      multus:
        networkName: sriov-kubevirt-net

IP configuration happens inside the guest, through cloud-init or DHCP - there is no CNI IPAM step for a device the host no longer owns.[1] KubeVirt support on Red Hat OpenShift is itself new in Network Operator 26.7.0, which is worth saying out loud to a customer who is quoting a 26.1 or 26.4 experience.[7]

3IOMMU is a BIOS conversation before it is a Kubernetes one

The prerequisites are three, and only one of them is inside Kubernetes.[1]

IOMMU must be enabled: intel_iommu=on iommu=pt on Intel hosts, amd_iommu=on iommu=pt on AMD.[1] On a PowerEdge that is two settings and a kernel argument: the processor’s virtualisation option under System BIOS > Processor Settings, and “SR-IOV Global Enable” under System BIOS > Integrated Devices, which “Enables or disables the BIOS configuration of Single Root I/O Virtualization (SR-IOV) devices” and is disabled by default[13] - plus intel_iommu=on iommu=pt or the AMD pair on the command line[1], and customers routinely set one without the other. The vfio-pci kernel module must be available on the worker nodes.[1] And mlx5_core must be inside the guest.[1]

The reason IOMMU is non-negotiable rather than a tuning flag: a device a guest controls can issue DMA. Without an IOMMU translating and constraining those addresses, a guest that owns a NIC owns host memory. This is why the failure is a refusal to start rather than a degraded mode.

What you can check in five minutes on any node, before anyone touches a CRD:

cat /proc/cmdline                          # look for intel_iommu=on iommu=pt or the AMD pair
dmesg | grep -i -e DMAR -e IOMMU | head    # firmware and kernel agreeing that it is on
ls /sys/kernel/iommu_groups/ | wc -l       # zero groups means it is off, whatever the BIOS says
lsmod | grep vfio                          # vfio_pci present or loadable

The third command is the one that settles arguments. An empty /sys/kernel/iommu_groups/ is the ground truth regardless of what the BIOS screen claims, and the group listing is also what tells you whether a VF is isolated on its own group or shares one with functions you did not intend to hand over.

4DRA: asking for a VF by its properties

Dynamic Resource Allocation is the second path that is not a plain netdevice VF, and it is a different question from vfio-pci. vfio-pci changes who consumes the VF; DRA changes how you ask for one.

The device-plugin model can express exactly two things: a name and a count.[8] Everything else - which NUMA node, which PCIe root, which PF - has to be inferred afterwards. TopologyInfo lets a plugin publish a NUMA hint, and Topology Manager then decides whether the resulting placement is acceptable, rejecting the pod with TopologyAffinityError if not.[8][9] The request itself never said what it needed.

The DRA SR-IOV driver inverts that. It publishes each VF as a device in a ResourceSlice, carrying attributes including dra.net/numaNode, resource.kubernetes.io/pciBusID and resource.kubernetes.io/pcieRoot, plus vendor, PCI address, VF id, PF name and eswitch mode.[2] A workload then allocates through a ResourceClaimTemplate that selects with a CEL expression:

apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: sriov-vf
spec:
  spec:
    devices:
      requests:
      - name: vf
        exactly:
          deviceClassName: sriovnetwork.k8snetworkplumbingwg.io
          count: 1
          selectors:
          - cel:
              expression: >
                device.attributes["k8s.cni.cncf.io"].resourceName == "nvidia.com/sriov_resource"

That expression matches the same resource name you have been chasing all module, but now as a device attribute rather than as a scheduling counter.[2] The interesting part is what else is in that attribute namespace: because PCIe bus ID and NUMA node are attributes, a claim can in principle express NIC-to-GPU alignment as a selection criterion rather than leaving it to Topology Manager to approve or reject after the fact.[2][9]

Two limits decide whether any of this is usable. The driver is “a Tech Preview feature: it has limited testing and is not recommended for production deployments”, and it “is supported only for Vanilla Kubernetes deployments with SR-IOV Network Operator”.[2] It requires the dynamicResourceAllocation: true feature gate on the SR-IOV Network Operator.[2] The page does not state a minimum Kubernetes version, so do not quote one - pin it against the operator’s own supported range of 1.32 to 1.36 instead and say that is what you are doing.[2][6]

◐ Level 2 — limited (same annual cycle)
  • Profile doca-all is "other profiles" in the matrix → Level 2. Every component is in cycle 25 (Oct 2025 → Jul 2026 (3.2 → 3.5)) → supported until the next October GA.
  • This is exactly the Spectrum-X validated stack v2.3.1 combination (Sep 2026).
  • Any FW or mode change on PowerEdge needs a full power cycle, not a warm reboot (Dell KB 000300192; NVIDIA modes page).

Matrix (policy): doca-ofed ↔ FW/BF-FW-Bundle = L1 · doca-ofed ↔ BF-Bundle = L1 · other profiles ↔ FW or BF-Bundle = L2 · DOCA-DPU ↔ BF-FW-Bundle = L2 · DOCA Services ↔ BF-Bundle/FW = L2. source ↗

⚠ = not confirmed on a fetched primary source (hover for why). Facts as of DOCA 3.5.0 (Sep 2026). Selections are saved.

The shipped matrix is the DOCA and BlueField version table rather than a DRA table - use it here for the habit this segment trains, which is reading a support boundary off a published table before promising it. The DRA boundary itself is two sentences of prose: Tech Preview, and vanilla Kubernetes only.

5Choosing between the three, out loud

Three questions decide the path, and they are worth memorising in this order.

Who consumes the VF? A container gets netDevice; a virtual machine gets vfio-pci.[1][3] This is not a preference. It follows from whether the consumer has its own kernel.

Does the consumer need RDMA on the host side? If yes, vfio-pci is out - the flag is documented as incompatible and the example sets isRdma: false.[1] A VM that needs RDMA does it inside the guest with the guest’s driver, and the host-side chained rdma plugin from lesson 3 has no role.

Is this cluster vanilla Kubernetes, and is Tech Preview acceptable? Both must be true for DRA.[2] If either is false, the answer is the device-plugin path with Topology Manager, which is what the rest of this course teaches.[8][9]

One certification note, phrased honestly. NCA-AIIO objective 3.4 is “Identify the key considerations for virtualizing accelerated infrastructure”, and this lesson over-serves it: the exam blueprint never names SR-IOV, VFIO, KubeVirt or DRA.[12] Treat the mapping as “this content is more than sufficient for that objective”, not as exam coverage.

From a virtualisation request to a decision you can defend

A Dell customer running vanilla Kubernetes 1.34 with Network Operator 26.7.0 asks: “we want four VMs per XE-class node, each with a 200G interface, and two of them run an RDMA storage client. Can we do it, and should we use DRA?”

Step 1 - split the ask by consumer. Four VMs means four VFs handed to guests, which means deviceType: vfio-pci on the node policy.[1] There is no container in this part of the request, so netDevice never enters it.

Step 2 - resolve the RDMA half. The two storage VMs do RDMA inside the guest. The host policy still sets isRdma: false because with VFIO there is no host netdevice to expose.[1] The deliverable for those two guests is a driver requirement on the image - mlx5_core present - not a Kubernetes setting.[1]

Step 3 - check the host prerequisites before promising anything. cat /proc/cmdline for intel_iommu=on iommu=pt or the AMD pair; ls /sys/kernel/iommu_groups/ | wc -l greater than zero; lsmod | grep vfio.[1] If the count is zero this is a BIOS-and-reboot conversation, and it belongs in the maintenance window, not in the design review.

Step 4 - write the three objects. Node policy with numVfs, nicSelector.vendor: "15b3", pfNames naming a PF that is not the primary CNI uplink, deviceType: vfio-pci, isRdma: false; an SriovNetwork with networkNamespace set to where the VMs live; the VM with sriov: {} and a Multus network reference.[1]

Step 5 - answer the DRA half. Vanilla Kubernetes is satisfied, so the blocker is only the maturity statement: Tech Preview, “not recommended for production deployments”.[2] The defensible answer is: build on the device-plugin path now, prototype DRA in a lab cluster with dynamicResourceAllocation: true, and revisit when the status changes.[2]

Step 6 - name what you did not verify. The DRA page states no minimum Kubernetes version, so you are inheriting the operator’s 1.32 to 1.36 range and you should say so rather than inventing a number.[2][6]

Case closed

How it ended

One kernel argument and a reboot later the group count is non-zero and the appliance boots. Nothing about the card, the operator or the policy was ever wrong. The customer asks whether the newer DRA path would have avoided the detour, and the NVIDIA PM on the call declines to go past what is published: Tech Preview, not recommended for production, and supported only on vanilla Kubernetes with the SR-IOV Network Operator.[2]

Acceptance is signed and the purchase order clears. The operator relabels the two node groups so nobody rebuilds the wrong one at 02:10 again, and the SE finally closes row 14 - with a note under it that reads ask which name the node advertises first.

Lab

Mutating steps ahead. Needs a maintenance window, out-of-band access (BMC/iDRAC/rshim console) and a rollback path. Record the pre-flight inventory before changing anything. Never on a production host.

On a PowerEdge with a ConnectX or BlueField-3 in NIC mode, in the Dell lab. Steps 3 and 4 change node state and step 2 may require a reboot - read the rollbacks before you start.

  1. Pre-flight inventory, recorded to a file. uname -r; cat /proc/cmdline; ls /sys/kernel/iommu_groups/ | wc -l; lspci -nnk | grep -A3 -i mellanox; ip -br link show; cat /sys/class/net/<pf>/device/sriov_numvfs; lsmod | grep -e vfio -e mlx5. Keep this file - every rollback below is a diff against it.

  2. Read the BIOS state without changing it. On the iDRAC or in System Setup, confirm the processor virtualisation option and SR-IOV Global Enable under Integrated Devices are on[13] - exact wording of the processor option varies by generation, so read it off this platform’s screen - then confirm the kernel agrees with ls /sys/kernel/iommu_groups/ | wc -l. Expected: a non-zero group count. If not: the firmware and the kernel disagree, and the fix is the kernel command line, not the BIOS.

  3. Only if the count is zero and you have a maintenance window: add intel_iommu=on iommu=pt (or the AMD pair) to the kernel command line and reboot.[1] Expected: after reboot, ls /sys/kernel/iommu_groups/ | wc -l is non-zero and dmesg | grep -i DMAR shows initialisation. Rollback: remove the argument from the bootloader config, regenerate it and reboot. Keep the previous kernel entry available so a bad edit is one reboot from recovered.

  4. Create VFs on a PF that is not the primary CNI uplink. echo 4 > /sys/class/net/<pf>/device/sriov_numvfs Expected: four VF entries in ip link show <pf> and four new functions in lspci -nn | grep -i mellanox. Rollback: echo 0 > /sys/class/net/<pf>/device/sriov_numvfs - delete any pod using a VF first or the write is refused.

  5. Bind exactly one VF to vfio-pci by hand, so you see the handover rather than trusting the operator to do it. For the VF at 0000:xx:xx.x: modprobe vfio-pci; echo 0000:xx:xx.x > /sys/bus/pci/devices/0000:xx:xx.x/driver/unbind; then bind it to vfio-pci through /sys/bus/pci/drivers/vfio-pci/new_id or driver_override plus /sys/bus/pci/drivers_probe. Expected: lspci -nnk -s 0000:xx:xx.x reports Kernel driver in use: vfio-pci, and that VF has disappeared from ip -br link show. That disappearance is the whole lesson made visible.[1] Rollback: unbind from vfio-pci, clear driver_override by writing an empty string to it, then echo 0000:xx:xx.x > /sys/bus/pci/drivers/mlx5_core/bind (or re-probe) and confirm the netdevice returns in ip -br link show.

  6. Read the isolation you just relied on. ls /sys/kernel/iommu_groups/*/devices/ | grep -B5 0000:xx:xx.x or walk /sys/bus/pci/devices/0000:xx:xx.x/iommu_group/devices/. Expected: the VF’s group and its members. Record whether the group contains only that VF or also the PF and sibling functions. If it contains more than you expected: that is the reason a passthrough request can be refused even with IOMMU on - everything in a group has to move together.

  7. Prove the RDMA claim rather than believing it. With the VF bound to vfio-pci, run rdma link and ibv_devinfo on the host and confirm that function contributes nothing.[1] Expected: the host’s RDMA view no longer includes that function. Read-only step.

  8. Optional, at a customer site with KubeVirt installed: apply the three objects from Segment 2 and boot a guest with mlx5_core in the image; confirm the interface appears inside the guest and not on the host.[1] Rollback: delete the VM, then the SriovNetwork, then the SriovNetworkNodePolicy - in that order, so nothing is left referencing a resource that no longer exists.

  9. Full teardown, in order: delete VM / pods, delete SriovNetwork, delete SriovNetworkNodePolicy, rebind the VF to mlx5_core (step 4 rollback), echo 0 > /sys/class/net/<pf>/device/sriov_numvfs, and if you changed the kernel command line in step 2 decide deliberately whether to keep it. Re-run the step 0 inventory and diff it against the saved file.

Retrieval check

10 questions from memory. Answer before looking anything up; misses become flashcards.

Explain it to a Dell SE

Explain to a Dell virtualisation specialist, in five sentences, why a VF that works perfectly for a container needs a different device type and a BIOS change before a virtual machine can use it, and why the newer DRA route is still the wrong answer for their OpenShift cluster this quarter.

14 flashcards for this lesson — 0 in deck. Spaced review lives at /review.

Sources

Facts in this lesson were checked against NVIDIA Network Operator v26.7.0 KubeVirt SR-IOV Integration, DRA SR-IOV Driver and Platform Support pages, all re-fetched 2026-09-09; sriov-network-device-plugin and sriov-network-operator repositories and the Kubernetes device-plugin and Topology Manager pages as fetched 2026-09-07. Dates are when each page was fetched.

  1. NVIDIA Network Operator v26.7.0 - KubeVirt SR-IOV Integration · fetched 2026-09-09
  2. NVIDIA Network Operator v26.7.0 - DRA SR-IOV Driver · fetched 2026-09-09
  3. GitHub - k8snetworkplumbingwg/sriov-network-device-plugin · fetched 2026-09-07
  4. GitHub - k8snetworkplumbingwg/sriov-network-operator · fetched 2026-09-07
  5. NVIDIA Network Operator v26.7.0 - Deployment Guide with OpenShift · fetched 2026-09-07
  6. NVIDIA Network Operator v26.7.0 - Platform Support · fetched 2026-09-09
  7. NVIDIA Network Operator v26.7.0 - Release Notes · fetched 2026-09-07
  8. Kubernetes - Device Plugins · fetched 2026-09-07
  9. Kubernetes - Control Topology Management Policies on a node · fetched 2026-09-07
  10. NVIDIA Network Operator v26.7.0 - Deployment Guide with Kubernetes · fetched 2026-09-07
  11. NVIDIA Network Operator v26.7.0 - Spectrum-X Quick Start · fetched 2026-09-07
  12. NVIDIA-Certified Associate AI Infrastructure and Operations Exam Study Guide (doc 4694224 Jan26) · fetched 2026-09-07
  13. Dell PowerEdge R760 Installation and Service Manual - Integrated Devices · fetched 2026-09-09

The same idea elsewhere

Other lessons that cover this ground, sometimes from another course's angle.