Skip to content

RDMA in Kubernetes: the NVIDIA Network Operator

S4·E5Nobody proved the fabric, and acceptance is at nine · Hotel lobby, an hour before the acceptance meeting

S4·E5Analyze~30 minsources checked todaylab mutates hardwareverified against NVIDIA Network Operator v26.7.0 index re-fetched 2026-09-07 (version and CRD groups confirmed), v25.7.0 MacVLAN RDMA-shared quick start, GPU Operator GPUDirect RDMA page, NCCL 2.31.2 troubleshooting, NCP-AIN study guide doc 4417000 MAY26

Builds on: Triage in order: symptom to counter to cause, GPUDirect RDMA: proving it is actually on

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

  • Break the Network Operator into the components it installs and the CRDs that drive each one.
  • Trace an RDMA pod from NicClusterPolicy through MacvlanNetwork and IPPool to the resource request in the pod spec.
  • Differentiate the shared device plugin path from the SR-IOV path by the isolation and QoS each gives.
  • Analyse a failing RDMA pod and decide whether the fault is in Kubernetes or in the fabric underneath it.

Episode 5 — Nobody proved the fabric, and acceptance is at nine

The situation · Hotel lobby, an hour before the acceptance meeting

You are in a hotel lobby with a laptop, a coffee the SE bought two hours ago, and pod specs the customer’s platform team sent overnight. Their message says the NVIDIA Network Operator is broken: one pair of RDMA pods dies instantly with a memory registration error, another pair runs at a fifth of line rate, and the evidence for both is a failing training job. Acceptance is at nine, and the rented capacity they are leaving lapses this week.

The operator exists to do in Kubernetes what the last four episodes did by hand — it installs the DOCA-OFED driver, the RDMA shared device plugin, the SR-IOV device plugin, Multus, Whereabouts and the NVIDIA IPAM plugin, and it is driven by NicClusterPolicy and the network CRDs.[1] Driver install, resource advertisement, secondary interfaces, addresses. That is its whole job, and none of it is congestion control.

Which is why the two symptoms in the mail are not one bug. A registration failure happens before the first byte moves, so it cannot be congestion, and in a container the equivalent of unlimited memlock is the IPC_LOCK capability the sample pods carry.[5][3] A fifth of line rate between pods that do move data is the counter map and the switch policy, exactly as on bare metal.[10]

Kubernetes does not create a fabric; it schedules pods onto one that already works. You have an hour to turn one Kubernetes ticket into two honest ones.

1What the operator is, and what it is made of

The NVIDIA Network Operator is the Kubernetes side of everything the previous four lessons configured by hand. The current documented release is v26.7.0, and the version index runs from v26.7.0 back through v26.4.1, v26.1.x, v25.10.0, v25.7.0 and earlier to v23.5.0.[1][2] It is described as software for enabling AI networking in Kubernetes, and it is designed to work alongside the GPU Operator to give “high-throughput, low-latency networking for scale-out, GPU computing clusters”.[1]

It installs a set of components rather than one thing: the containerized NVIDIA DOCA-OFED networking driver, the Kubernetes RDMA shared device plugin, the SR-IOV Network Device Plugin, Multus, the container networking plugins, Whereabouts, the NVIDIA IPAM plugin, NVIDIA NIC Feature Discovery and the Maintenance Operator.[1] Between them they cover driver installation, resource advertisement, secondary interfaces and address management, which is exactly the list of things a bare-metal RDMA host solves with packages and static configuration.[1]

The control surface is custom resources. The primary CRDs are NicClusterPolicy for cluster-wide NIC configuration and MacvlanNetwork, HostDeviceNetwork and IPoIBNetwork for the networks themselves; newer API groups add NIC configuration and firmware templates under configuration.net.nvidia.com and a Spectrum-X rail pool configuration under spectrumx.nvidia.com.[1] One operational detail circulates widely and cannot be confirmed on the pages fetched: that the operator acts only on a NicClusterPolicy named nic-cluster-policy and ignores instances with other names. Treat that as unverified and simply use the documented name, which costs nothing.[1]

This lesson is also the whole of NCP-AIN Domain 4, worth 5 percent of the exam, whose two objectives are deploying the Network Operator to manage RDMA interfaces in Kubernetes clusters and verifying its functionality.[9] The study guide’s reading list for that domain still names Network Operator v25.7.0 while v26.7.0 ships, which is a useful thing to know before the exam and a useful thing to say to a customer comparing documents.[9][2]

Rendering diagram…
Diagram source (Mermaid)
flowchart LR; A["NicClusterPolicy: rdmaSharedDevicePlugin"] --> B["Device plugin advertises rdma/rdma_shared_device_a"]; C["IPPool: subnet + perNodeBlockSize + gateway"] --> D["MacvlanNetwork: master = PF netdev - ipam nv-ipam"]; D --> E["Pod annotation k8s.v1.cni.cncf.io/networks"]; B --> F["Pod requests + limits rdma/rdma_shared_device_a: 1"]; E --> G["Pod: second interface with an address"]; F --> G; H["securityContext capabilities add IPC_LOCK"] --> G; G --> I["kubectl exec -- ibv_devinfo"]
Every object in the shared-device path and what it contributes to the pod that finally runs ibv_devinfo.

2The shared-device path, object by object

NVIDIA’s quick start gives a working NicClusterPolicy for the shared path.[3]

apiVersion: mellanox.com/v1alpha1
kind: NicClusterPolicy
metadata:
  name: nic-cluster-policy
spec:
  rdmaSharedDevicePlugin:
    image: k8s-rdma-shared-dev-plugin
    repository: nvcr.io/nvidia/mellanox
    version: network-operator-v25.7.0
    config: |
      {
        "configList": [
          {
            "resourceName": "rdma_shared_device_a",
            "rdmaHcaMax": 63,
            "selectors": { "ifNames": ["ens1f0"] }
          }
        ]
      }

Two fields carry all the meaning. rdmaHcaMax: 63 is the sharing factor — the plugin README defines it as the “Maximum number of RDMA resources that can be provided by the device plugin resource”, and each pod requesting the resource consumes one unit, so 63 pods may share the HCA. The quick start prints the field without defining it.[3][12] selectors.ifNames is the netdev, not the RDMA device name.[3] That second point is a real operational hazard on a RoCE host: bonding into RoCE LAG changes the RDMA device name to mlx5_bond_0 while the selector keeps naming Ethernet interfaces, and any renaming scheme that moves ens1f0 silently stops the resource from being advertised.[3][7]

The network side is a MacvlanNetwork whose master is the PF netdev, with mode: "bridge", an mtu, a networkNamespace, and an ipam block of {"type":"nv-ipam","poolName":"macvlan-pool"}; the pool itself is an IPPool in nv-ipam.nvidia.com/v1alpha1 with subnet, perNodeBlockSize and gateway.[3]

The pod then has to ask for three things at once: the annotation k8s.v1.cni.cncf.io/networks: macvlan-network, the capability IPC_LOCK in securityContext.capabilities.add, and both requests and limits of rdma/rdma_shared_device_a: 1.[3] Missing the annotation gives a pod with no second interface; missing IPC_LOCK gives a pod that has the device and cannot register memory; missing the resource gives a pod that may be scheduled where the device does not exist.[3][5]

Verification is two commands, both documented: kubectl exec -it macvlan-test-pod -- ip addr show and kubectl exec -it macvlan-test-pod -- ibv_devinfo.[3]

3Shared or SR-IOV, and what GPUDirect adds

The design question a customer asks is which path to use, and it decomposes cleanly.[1][3]

The shared device plugin puts many pods on one physical function. Everything shares the PF’s queues, its GID table, and its QoS context, and the sharing factor is rdmaHcaMax. That is the right shape for storage clients and inference services, where the pods are numerous, individually modest, and not adversarial.[3]

SR-IOV gives each pod a virtual function through the SR-IOV Network Device Plugin.[1] A VF per pod is what buys isolation and a per-tenant QoS story, which is what a training job with a service-level expectation needs. It also inherits the bare-metal rule that NVIDIA states directly — “Setting the Trust State mode shall be done before enabling SR-IOV in order to propagate the Trust State to the VFs” — so a trust state set after the VFs exist does not reach them.[11]

GPUDirect is a separate axis that the GPU Operator owns. Its helm surface is small: current releases enable DMA-BUF by default, --set driver.rdma.useHostMofed=true covers host-installed drivers, --set driver.rdma.enabled=true selects the legacy nvidia-peermem path, and --set gds.enabled=true adds GPUDirect Storage.[4] The legacy path carries its bare-metal ordering trap into the cluster: if the GPU driver was installed before the network drivers, it must be uninstalled and installed again so nvidia-peermem builds against the right RDMA APIs.[6] Inspecting the rollout is kubectl describe ds -n gpu-operator nvidia-driver-daemonset, where the mofed-validation init container should be present.[4]

The cross-pod proof NVIDIA documents is a perftest pair: on the server kubectl exec -it demo-pod-1 -- ib_write_bw --use_cuda=0 --use_cuda_dmabuf -d mlx5_0 -a -F --report_gbits -q 1, with the client adding -n 5000 and the server pod’s IP.[4]

DPF v26.4.1 · DOCA 3.5.0
Host Kubernetes clusterDPF custom resources (dpf-operator-system)DPU cluster (Arm nodes)kamajiClusterManagernode labels1 per cardBFB + bf.cfg over rshimjoinHelmControl plane nodeshost cluster · K8s 1.33–1.36DPF Operatorhelm: dpf-repository/dpf-operator --version v26.4.1DPFOperatorConfigoperator.dpu.nvidia.comDPUClusterKamaji tenant control planeWorker node (x86) + BlueField-3DMS pod → rshimhost-trustedDPU BMC · Redfishzero-trust · OOB netDPU detector (NFD)feature.node.kubernetes.io/dpu-enabledBFBimage CR (spec.url)DPUFlavornvconfig · grub · ovs · bf.cfgDPUSetdpuNodeSelector · dpuTemplateDPUone object per cardDPUServiceHelm chart → DPU clusterDPUServiceInterfacephysical · pf · vf · serviceDPUServiceChainswitches[].ports[]DPUDeploymentbundles dpus + services + chainsDPU node (Arm) · kubeletworker of the DPUClusterHBNpodOVN-KpodDTSpodbr-sfc (OVS) · SFC controllerServiceInterfaceSet / ServiceChainSetp0 / p1 → ToR (BGP/EVPN)
Reconcile · step 1 / 7host-trusted

1. BFB CR created

The BFB controller downloads spec.url ("Download the BFB from a remote server") and records status.versions {bsp, doca, uefi, atf} read from the image. v26.4.1 matrix: BFB 3.4 fully supported, 3.2 LTS allowed, non-LTS BFBs (3.3, 3.5) only with their matching DPF release.

DPU phases: BFB.status.phase: Initializing → Downloading → Ready

DOCA Platform Framework (DPF) v26.4.1

DPF "is a system that provisions and orchestrates NVIDIA BlueField DPUs" from Kubernetes: the operator installs controllers in the host cluster, CRs describe the desired image (BFB), sizing (DPUFlavor) and node set (DPUSet), each card becomes a DPU object, and the DPUs join a separate tenant control plane where DPUServices (Helm charts) run.

Why it matters. GA in DOCA 3.5.0. Latest tag v26.4.1 (2026-08-31); v26.4.0 shipped 2026-06-29. Requirements (v26.4.1): Kubernetes 1.33–1.36, Helm 3.5+, DPU firmware ≥ 32.38.1002, MFT 4.33.0-169, Ubuntu 24.04 + containerd; B3240/B3220/B3210; Socket Direct unsupported.

FAE note. Click any box for a YAML skeleton and what it owns. Switch trust mode to see who provisions, then walk the Reconcile stepper.

Source: networking-docs.nvidia.com · fields marked UNVERIFIED were not confirmed against the Go types.

The DPU-side picture of the same cluster. Switch trust modes to see which side of the card owns configuration when a BlueField is in the node.

4Kubernetes does not create a fabric

The single most useful sentence to say on a Dell AI Factory call: Kubernetes only plumbs a RoCE fabric that already works.[10][7] The node still needs the same GID selection, trust state, PFC and ECN configuration as bare metal, and the switches still need their half of the policy — RoCE data on priority 3, CNP on priority 6, an ECN threshold, and PFC on every hop if the design is lossless.[10]

So the failures split into two piles, and telling them apart is the analysis skill this lesson is for.[3][5]

Symptom Kubernetes-side or fabric-side First check
Pod stuck Pending on a node that has the NIC Kubernetes Is the resource advertised? kubectl describe node for rdma/..., then the device plugin pod’s logs[3]
Pod runs but has no second interface Kubernetes The k8s.v1.cni.cncf.io/networks annotation and the MacvlanNetwork name[3]
ibv_devinfo inside the pod is empty Kubernetes Driver rollout and the selectors.ifNames value against the actual netdev name[3]
Memory registration fails immediately Kubernetes IPC_LOCK in the pod securityContext[5][3]
Pods talk but bandwidth is a fraction of line rate Fabric The counter map: per-priority bytes and pause on the node, then the switch[10]
Two pods fine and 32 pods collapse Fabric PFC on every hop and ECN marking, exactly as on bare metal[10]
GPU-to-GPU far slower than host-to-host Node GPUDirect: driver order, nvidia-peermem or DMA-BUF, NIC-to-GPU distance[4][6]

The practical consequence is a sequencing rule for any cluster bring-up: prove the fabric between two plain perftest pods before anyone schedules a training job.[4] A failing all-reduce is an expensive way to discover that priority 3 was never trusted on the host.[7]

Analyse a failing RDMA pod

Report. “The Network Operator is installed. Our pod comes up, ip addr show inside it shows the macvlan interface with an address, and ibv_devinfo shows mlx5_0 with state: PORT_ACTIVE and link_layer: Ethernet. But ib_write_bw between two of these pods dies instantly with a memory registration error, and on a different pair it runs but only reaches a fifth of line rate.”

Split the report into two problems first. They are not the same fault; one pair fails before any data moves and the other pair moves data slowly. A fault that occurs before the first byte cannot be congestion.[5]

Problem A, the instant failure. Registration happens at start-up, so the candidates are memory locking and the device itself. ibv_devinfo already proved the device is present and active, which eliminates the driver and the selector.[3] That leaves locking: in a container the equivalent of unlimited memlock is the IPC_LOCK capability, which NVIDIA’s own sample pods carry for this reason.[5][3] Check: kubectl get pod <name> -o jsonpath='{.spec.containers[0].securityContext.capabilities}'. Fix: add IPC_LOCK to securityContext.capabilities.add.[3]

Problem B, the slow pair. Data moves, so the plumbing is right and the question is which queue it is in. This is the bare-metal counter map applied inside a cluster: on the node, per-priority bytes and pause with ethtool -S; on the switch, its RoCE and PFC counters.[10] If the node shows the bytes on priority 0 rather than the RoCE priority, the trust state or the application marking is the fault and nothing in Kubernetes will fix it.[7] If the bytes are on the right priority and pause is climbing, walk the hops.[10]

What to tell the customer. Problem A is a pod-spec fix. Problem B is a fabric configuration finding that would have hit them identically on bare metal, and the evidence is the node and switch counters, not the pod.[5][10]

Two piles, before nine o'clock

How it ended

IPC_LOCK goes into the pod securityContext and the instant failure disappears; the slow pair stays slow, which is the point.[3][5] Two perftest pods and the per-priority counters put that half back where it belongs: host trust state and switch policy.[10][7]

What you say to the customer: “Kubernetes only plumbs a fabric that already works. Let us prove it with two perftest pods before we schedule another training job against it.”[4][10]

Acceptance signs at 09:40. The network lead’s notebook is full to the back cover, procurement wants to know when the next sixty-four ship, and the SE closes the spreadsheet over a cold coffee. On top of the rack the night operator has left one more label: NOT THE FABRIC — this time everyone can prove it.

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.

Dell-lab node with a real ConnectX or BlueField-3 PF. All steps that create cluster objects are mutating and are removed in reverse order at the end.

  1. Pre-flight, read-only. On the node: ibdev2netdev; ibv_devinfo | grep -E "hca_id|state|link_layer"; mlnx_qos -i <netdev> | tee ~/qos-before.txt; ofed_info -s. In the cluster: kubectl get nodes -o wide and kubectl describe node <node> | grep -A5 Capacity.[7]
  2. Install or confirm the Network Operator and apply the NicClusterPolicy with the real PF netdev in selectors.ifNames.[3] Expected: kubectl describe node lists rdma/rdma_shared_device_a with capacity rdmaHcaMax. Rollback: delete the policy and confirm the resource disappears.
  3. Apply the IPPool and MacvlanNetwork, launch two pods on different nodes, and verify with ip addr show and ibv_devinfo in each.[3] Rollback: delete pods, network, pool.
  4. Prove the fabric before anything GPU-related: run ib_write_bw pod to pod with --tclass set to the RoCE DSCP times four, and read ethtool -S <netdev> | grep prio3 on both nodes.[10] Expected: priority-3 bytes on both nodes. If they land on priority 0, stop and fix the host trust state; the cluster is not the problem.[7]
  5. GPUDirect check. Confirm the GPU Operator’s RDMA path: kubectl describe ds -n gpu-operator nvidia-driver-daemonset and confirm the mofed-validation init container ran; on the node lsmod | grep -E "nvidia_peermem|nvidia_fs".[4][6] Then run NVIDIA’s documented cross-pod check: server kubectl exec -it demo-pod-1 -- ib_write_bw --use_cuda=0 --use_cuda_dmabuf -d mlx5_0 -a -F --report_gbits -q 1, client the same with -n 5000 and the server pod IP.[4] Expected: GPU numbers close to the host-memory numbers; a third of them means the transfer is bouncing through host memory.
  6. Break it on purpose, once. Remove IPC_LOCK from one pod, re-run, observe the registration failure, restore it.[5] Then, if the node is safe to change, rename or bond the PF netdev and watch the resource disappear from kubectl describe node while ibv_devinfo on the node stays healthy. Rollback: restore the interface configuration captured in step 1 and confirm the resource returns.
  7. Restore and verify. Delete the cluster objects in reverse order, re-apply ~/qos-before.txt, and diff mlnx_qos -i <netdev> against the capture. Deliverable: a one-page runbook separating the Kubernetes-side checks from the fabric-side checks, with the exact command for each.

Retrieval check

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

Explain it to a Dell SE

Explain to a Dell platform engineer in five sentences what the NVIDIA Network Operator does and does not do for their RoCE cluster.

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 index re-fetched 2026-09-07 (version and CRD groups confirmed), v25.7.0 MacVLAN RDMA-shared quick start, GPU Operator GPUDirect RDMA page, NCCL 2.31.2 troubleshooting, NCP-AIN study guide doc 4417000 MAY26. Dates are when each page was fetched.

  1. NVIDIA Network Operator v26.7.0 · fetched 2026-09-07
  2. NVIDIA Cloud Orchestration documentation index (Network Operator versions) · fetched 2026-09-07
  3. MacVLAN Network with RDMA Shared Device: Network Operator v25.7.0 quick start · fetched 2026-09-07
  4. GPUDirect RDMA and GPUDirect Storage: NVIDIA GPU Operator · fetched 2026-09-07
  5. Networking Troubleshooting: NCCL 2.31.2 documentation · fetched 2026-09-07
  6. GPUDirect RDMA Peer Memory Client: NVIDIA Linux driver README · fetched 2026-09-07
  7. RDMA over Converged Ethernet (RoCE): MLNX_OFED 24.10-5.1.6.1 LTS · fetched 2026-09-07
  8. QoS Configuration (DOCA SDK) · fetched 2026-09-07 · DOCA 3.5.0
  9. NVIDIA-Certified Professional: AI Networking Exam Study Guide (doc 4417000 MAY26) · fetched 2026-09-07
  10. RDMA over Converged Ethernet (RoCE): Cumulus Linux 5.18 · fetched 2026-09-07
  11. Ethernet QoS (DOCA SDK 3.5.0) · fetched 2026-09-07 · DOCA 3.5.0
  12. k8s-rdma-shared-dev-plugin README (Mellanox) · fetched 2026-09-07

The same idea elsewhere

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