RDMA in pods: shared plugin vs exclusive netns
S2·E3The node that was hardened at 02:10 · A phone call from the night shift, day three of acceptance week
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 shared RDMA device plugin and SR-IOV plus rdma-cni by what each one actually isolates.
- Predict the outcome of every combination of rdma system netns mode and deployed design including the two failure strings.
- Order the prerequisites and the config edits that put an isolated RDMA device inside a pod.
- Justify a design choice for a given node and name the evidence that would falsify it.
Episode 3 — The node that was hardened at 02:10
The operator is standing at the rack with the label maker still in one hand. One node out of twelve has rdma-shared-dp-ds in CrashLoopBackOff. The DaemonSet is identical on all twelve, the image is identical, the ConfigMap is identical, and Friday’s run needs all twelve.
You ask for the container log rather than the DaemonSet: Exiting.. can not change : incorrect RDMA subsystem network namespace.[2] Then one command on the node explains the whole cluster - rdma system show reports exclusive. This node was rebuilt for per-tenant isolation by somebody who was told to harden it, and nobody told the platform team.
Neither side is wrong; they are opposite designs, and the fork is in the kernel rather than in Kubernetes. In the default shared mode the “RDMA device is accessible in all network namespaces”, which is exactly the property the shared device plugin sells - it advertises rdmaHcaMax units of one physical HCA and lets every scheduled pod reach it.[4][1] Exclusive mode withdraws that property so a device can be moved into one namespace and seen nowhere else, and it is close to a boot-time decision: switching a live node with namespaces already up returns EBUSY.[4]
rdma system show before you deploy anything RDMA - the mode is a property of the node, and the plugin has to match it.
Segment 1 takes the two designs apart by what each one actually isolates.
1A fork in the road, not a setting
There are two supported ways to get RDMA inside a pod, and a node can only be in one of them. This is the single most useful thing in the module, so state it the way you would to a customer: it is a design fork, not a bug.
Shared. k8s-rdma-shared-dev-plugin “provisions RDMA (InfiniBand and RoCE) HCA resources” and runs as a DaemonSet.[1] Every pod that lands on the node sees the same HCA. The kernel is in shared netns mode, where “RDMA device is accessible in all network namespaces”.[4]
Exclusive. rdma-cni is a “CNI compliant plugin for network namespace aware RDMA interfaces” that provides “network namespace isolation for RDMA workloads in a containerized environment”.[3] It is chained after an SR-IOV delegate and moves “the associated RDMA interfaces of the provided network interface to the container’s network namespace path”.[3] The kernel is in exclusive mode, where the manual page says exclusive “should be set before creating any network namespace” so that a dedicated RDMA device can be assigned to a particular namespace.[4]
The two are incompatible because they need opposite kernel states. Shared needs devices visible everywhere; exclusive needs them visible in exactly one place.[4] Deploy the shared plugin on an exclusive node and its pod rdma-shared-dp-ds sits in CrashLoopBackOff.[2]
Choosing is a workload question. Several small jobs that trust each other on one card, or an IPoIB/macvlan secondary path? Shared. Multi-tenant, one VF per pod, per-pod isolation and Spectrum-X style rails? Exclusive.[11]
shared mode · Shared device plugin · pods already running. Does the node come up ok, does a pod CrashLoopBackOff, or does the command itself fail?
Current: “RDMA devices are shared among network namespaces.”
One switch, node-wide, two values. `rdma system show` prints the netns mode, privileged-qkey state and monitoring support.
“If there are active network namespaces and if one or more RDMA devices exist, changing mode from shared to exclusive returns error code EBUSY.”
Persist it with `options ib_core netns_mode=0` in /etc/modprobe.d/ib_core.conf rather than setting it at runtime; the runtime path is the one that hits EBUSY.
rdma system show rdma system set netns exclusive rdma system set netns shared echo "options ib_core netns_mode=0" >> /etc/modprobe.d/ib_core.conf
FAE angle: ask for this one line of output before any other question about RDMA in pods. It tells you which half of the fork the customer is standing on.
2Shared: a counter with a required number
The shared config is one JSON document with a configList of pools:
{"periodicUpdateInterval": 300,
"configList": [{
"resourceName": "hca_shared_devices_a",
"resourcePrefix": "example_prefix",
"rdmaHcaMax": 1000,
"devices": ["ib0","ib1"]
}]}[1]rdmaHcaMax is the “maximum number of RDMA resources that can be provided by the device plugin resource”; it is required and has no default.[1] resourcePrefix defaults to rdma, which is where the familiar rdma/rdma_shared_device_a comes from.[1][5] periodicUpdateInterval defaults to 60 seconds if unset and is disabled at 0.[1]
Selectors are vendors (hex, such as "15b3"), deviceIDs (hex), drivers ("mlx5_core"), ifNames ("ib0") and linkTypes ("ether").[1] Matching is “logical OR between elements of a specific selector and logical AND … between selectors”, and unspecified selectors are ignored.[1] That last clause is why a config carrying only ifNames can capture more devices than the author expected.
In the Network Operator flow this is a block of NicClusterPolicy, with rdmaHcaMax: 63 and selectors: {ifNames: ["ens1f0"]} producing rdma/rdma_shared_device_a.[5] The pod then requests rdma/rdma_shared_device_a: 1 in both requests and limits and adds IPC_LOCK.[5]
Now the honesty note. The README does not document how devices are exposed to the container - whether /dev/infiniband is mounted, and whether rdmaHcaMax maps to an rdma cgroup limit. Treat the internal mechanism as unverified: confirm it on the node rather than describing it to a customer.[1]
Because extended resources are integer and non-shareable, rdmaHcaMax is the only lever you have, and what it limits is how many pods land here, not what any of them may do.[9]
3Exclusive: chain rdma after sriov
Exclusive mode is set one of two ways. Persistently, echo "options ib_core netns_mode=0" >> /etc/modprobe.d/ib_core.conf and reboot.[3] At runtime, rdma system set netns exclusive.[4]
The ordering rule is the part people learn the hard way: “If there are active network namespaces and if one or more RDMA devices exist, changing mode from shared to exclusive returns error code EBUSY.”[4] The manual page also advises against changing mode while RDMA traffic is running, even though it is supported.[4] On a node that has already joined a cluster, that makes the modprobe-plus-reboot path the realistic one.
Prerequisites are explicit: kernel 5.3.0 or newer (or an out-of-tree driver from MLNX_OFED 4.7+), an iproute2 built against 5.3.0 or newer, Multus CNI v3.4.1 or newer, SR-IOV capable hardware and an SR-IOV supporting CNI; Kubernetes 1.16+ for the DaemonSet deployment.[3]
The CNI config itself is trivially small:
{"cniVersion":"0.3.1","type":"rdma","args":{"cni":{"debug":true}}}[3]In a NAD it is simply the second plugin in the array:
{"cniVersion":"0.3.1","name":"sriov-network","plugins":[
{"type":"sriov","ipam":{"type":"host-local","subnet":"10.56.217.0/24"}},
{"type":"rdma"}]}[7]Order is not stylistic. rdma moves the RDMA interfaces associated with a network interface that already exists, so the delegate that creates that interface has to run first.[3] The InfiniBand variant is the same shape with ib-sriov and a PKey: {"type":"ib-sriov","pkey":"0x6","link_state":"enable","ibKubernetesEnabled":true,...} followed by {"type":"rdma"}.[5]
One documented limitation to carry into a design review: “RDMA workloads utilizing RDMA Connection Manager on Mellanox hardware require pre-allocated MAC addresses for all VFs due to kernel constraints.”[3]
Checks
no forbidden combination- warnNeeds kernel 5.3.0+, iproute2 5.3.0+, Multus v3.4.1+ and exclusive namespaces (rdma system set netns exclusive, or options ib_core netns_mode=0) — set before namespaces exist, or it returns EBUSY. This node can then no longer run k8s-rdma-shared-dev-plugin: rdma-shared-dp-ds crash-loops with "Exiting.. can not change : incorrect RDMA subsystem network namespace". A design fork, not a setting. [rdma-cni prerequisites + netns modes]
- warnresourceName must match a name the device plugin advertised, prefix included: sriov-network-device-plugin defaults to intel.com, the Network Operator writes nvidia.com, OpenShift writes openshift.io. A mismatch keeps the pod Pending with no CNI error at all (kubectl describe node | grep -A20 Allocatable). deviceID is never hand-written — Multus passes the allocated VF. [resourcePrefix default is intel.com]
- infonv-ipam needs the matching CR: poolType "ippool" reads an IPPool (subnet, gateway, perNodeBlockSize), "cidrpool" a CIDRPool (cidr, gatewayIndex, perNodeNetworkPrefix). Deployed by NicClusterPolicy.spec.nvIpam. [nv-ipam poolType must match an existing CR]
- infoCilium’s CNI-chaining docs never mention Multus. If secondary interfaces vanish after an upgrade, ask whether generic-veth chaining is in use — and whether L7 policy went with it. [Cilium + Multus is untested in Cilium’s docs]
Delegate — sriov
Slices a PF into VFs. Multus gets the allocated VF’s deviceID from the device plugin and invokes sriov with it.
- deviceID is required — a valid PCI address of an SR-IOV NIC’s VF, e.g. "0000:03:02.3". Do not hand-write it: Multus passes the allocated VF.
- vlan 0–4094, vlanQoS 0–7, vlanProto default "802.1q" (or "802.1ad"), mac, spoofchk on/off, trust on/off, link_state auto|enable|disable, min_tx_rate / max_tx_rate in Mbps.
- The DHCP IPAM plugin cannot be used for a VF bound to a DPDK driver (uio/vfio).
- Device-plugin side: resourceList entries take resourceName (required), resourcePrefix, deviceType (netDevice default, accelerator, auxNetDevice) and selectors (vendors, devices, drivers, pciAddresses, pfNames, rootDevices, linkTypes, isRdma).
- Operator route: SriovNetwork (SR-IOV Network Operator) generates the NAD from a resourceName; SriovNetworkNodePolicy creates the VFs.
FAE angle: the pod also gets PCIDEVICE_<RESOURCE_NAME> and PCIDEVICE_<RESOURCE_NAME>_INFO env vars (upper-cased, "." and "/" → "_") — the fastest in-pod proof that the VF was really allocated.
sourceapiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: sriov-net
namespace: default
annotations:
k8s.v1.cni.cncf.io/resourceName: nvidia.com/mlnx_sriov_netdevice
spec:
config: |
{
"cniVersion": "0.3.1",
"name": "sriov-net",
"plugins": [
{
"type": "sriov",
"vlan": 100,
"spoofchk": "off",
"trust": "on",
"link_state": "enable",
"ipam": {
"type": "nv-ipam",
"poolName": "pool1",
"poolType": "ippool"
}
},
{
"type": "rdma"
}
]
}⚠ The annotation key spelling k8s.v1.cni.cncf.io/resourceName is not quoted on the pages fetched for this course; the rule that a NAD resourceName must match an advertised device-plugin resource is.
Documented bare-metal Ethernet example name; the real name is whatever your device plugin resourceList advertised.
4The collision, in the customer's words
Two error signatures resolve most of this lesson’s field cases in under ten minutes.
Signature one - wrong pairing. The shared plugin started on a node whose RDMA subsystem is exclusive. The pod rdma-shared-dp-ds is in CrashLoopBackOff and the log line is verbatim:
Exiting.. can not change : incorrect RDMA subsystem network namespace[2]The issue has been open since 2023 with no maintainer resolution captured on the page, so present it as an observed incompatibility rather than as vendor guidance.[2]
Signature two - too late. Someone tried to fix signature one by flipping the mode on a running node and got EBUSY from rdma system set netns exclusive.[4] Active namespaces plus RDMA devices means the window has closed; the fix is the modprobe line and a reboot.[3]
The inverse case appears in NVIDIA’s own Spectrum-X troubleshooting: the symptom “RDMA visibility across Pods” is fixed by enabling RDMA subsystem namespace awareness with options ib_core netns_mode=0 and a reboot.[6] Read those two pages together and the picture is consistent: NVIDIA’s isolated designs assume exclusive, the shared plugin assumes shared, and neither document is wrong.
Spectrum-X shows what exclusive looks like at scale. Each rail gets the RDMA CNI with rdmaQoS: {tos: 96, tc: 96}, and the pod’s RDMA device is named rdma_<rail topology name>.[11]
5Choosing, and defending the choice
Node A - a shared inference node. Four small serving pods, one ConnectX-7 on ens1f0, macvlan secondary interfaces, one tenant, no VF budget worth managing.
Design: shared. Kernel stays in the default shared mode, where “RDMA device is accessible in all network namespaces”.[4]
NicClusterPolicy carries an rdmaSharedDevicePlugin block with rdmaHcaMax: 63 and selectors: {ifNames: ["ens1f0"]}, advertising rdma/rdma_shared_device_a.[5]
Pod spec: annotation k8s.v1.cni.cncf.io/networks: example-macvlannetwork, securityContext.capabilities.add: ["IPC_LOCK"], and rdma/rdma_shared_device_a: 1 in both requests and limits.[5]
Verification: kubectl exec -it <pod> -- rdma link lists a device.[6]
Why not exclusive: macvlan pods share the parent’s function, so there is no per-pod RDMA device for rdma-cni to move.[3] NVIDIA’s own quick start splits its use cases along the same line, pairing “IP over InfiniBand with RDMA Shared Device” and “MacVLAN Network with RDMA Shared Device” against “SR-IOV Network with RDMA” and “Host Device Network with RDMA”.[13]
Node B - a training node, multi-tenant, eight VFs, per-pod isolation required.
Design: exclusive. Set it before the node joins: echo "options ib_core netns_mode=0" >> /etc/modprobe.d/ib_core.conf and reboot, which sidesteps the EBUSY rule entirely.[3][4]
Device plugin: SR-IOV, resourcePrefix: nvidia.com, selectors: {vendors: ["15b3"], isRdma: true}.[8]
NAD: plugins: [{"type":"sriov", ...},{"type":"rdma"}], in that order.[7][3]
Pod: requests the SR-IOV resource, adds IPC_LOCK.[5]
Falsification: if rdma system show on node B says shared, the isolation you sold does not exist, whatever the NAD says.[4]
Node C - a Dell XE9680 with eight VFs per PF, two tenants, and a customer who has already deployed rdma-shared-dp-ds cluster-wide from an old runbook.
Fill in the plan:
rdma system showon node C is expected to report netns________, because the shared plugin is currently________.- The target design is
________, because the requirement is________. - The mode change must be made via
________________________and a reboot rather than at runtime, because________. - In the NAD,
{"type":"rdma"}must come________{"type":"sriov"}, because________. - Before the reboot, the shared plugin must be
________on this node, otherwise the expected symptom is________________________________________inCrashLoopBackOff. - The one command that proves success from inside a pod is
________________________.
A customer runs a mixed cluster: six nodes serving inference with the shared plugin, and four new GPU nodes that must give one isolated VF per training pod. They want “one NicClusterPolicy for everything” and they want it this week.
Write the recommendation. Acceptance criteria: you state explicitly that the two designs cannot coexist on one node and why; you propose a node-group split with the label or selector that expresses it; you name the mechanism that lets per-node-group device-plugin configuration exist at all in the Network Operator; you list the two error strings you expect if they ignore you; and you name the single command that audits every node for compliance afterwards.[4][2]
One node, twelve nodes
The node is not broken; it was built for the other design. You either return it to shared mode and let the shared plugin run, or you finish the exclusive path it was started on - VFs advertised with isRdma: true, a NAD chaining sriov then rdma, pods that request the VF resource.[8][3] Either is defensible. Running one node differently from eleven others, two days before an acceptance run, is not.
Twelve nodes agree by morning. The run still lands at a third of the number on the contract, and the network lead is already drafting a case against the switch vendor.
Lab
On the Dell-lab BlueField-3 host in NIC mode with a single-node Kubernetes cluster. Steps 4 and 5 change node-wide state; read both rollbacks first.
-
Pre-flight inventory, saved to a file.
rdma system show;lsmod | grep -E 'mlx5|ib_core';ibstat;ibv_devinfo;mst status;ip -br link show;kubectl describe node <node> | grep -A20 Allocatable;kubectl get pods -A -o wide. Those first five are the commands NVIDIA’s own sos-report collector runs on the node.[12] -
Collect a support bundle before you touch anything.
kubectl netop-sosreport --verbose --log-lines 10000Expected: a tarball. It captures previous-container logs that the next steps will destroy.[12] -
Deploy the shared plugin. Configure
rdmaHcaMax: 63withselectors: {ifNames: ["<pf>"]}and let it advertiserdma/rdma_shared_device_a.[5] Expected:kubectl describe node <node> | grep -A20 Allocatableshowsrdma/rdma_shared_device_a: 63. If not: check the socket in/var/lib/kubelet/device-plugins/first, then the selectors. Rollback: delete the DaemonSet and its ConfigMap. -
Run a pod that uses it. Request
rdma/rdma_shared_device_a: 1in requests and limits and addsecurityContext.capabilities.add: ["IPC_LOCK"].[5] Expected:kubectl exec -it <pod> -- rdma linklists a device andkubectl exec -it <pod> -- ibv_devinfoshows the port state.[6] If not: check thatIPC_LOCKwas added - every NVIDIA RDMA pod example includes it.[5] Rollback: delete the pod. -
Flip the node to exclusive and observe the collision.
rdma system set netns exclusiveExpected: eitherEBUSY(namespaces are already active - which is itself the documented result and a valid observation) or success followed byrdma-shared-dp-dsenteringCrashLoopBackOffwithExiting.. can not change : incorrect RDMA subsystem network namespace.[4][2] Rollback:rdma system set netns shared, then delete the crash-looping plugin pod so it restarts cleanly. Confirm withrdma system showbefore moving on. -
Optional, needs a reboot window - the persistent form.
echo "options ib_core netns_mode=0" | sudo tee -a /etc/modprobe.d/ib_core.confthen reboot.[3] Expected: after rebootrdma system showreports exclusive with no EBUSY, because the mode was set before namespaces existed. Rollback: remove that line from/etc/modprobe.d/ib_core.confand reboot again. Do not attempt to undo it at runtime on a node with workloads - that is the EBUSY case. -
Build the exclusive path. SR-IOV device plugin with
isRdma: true, a NAD chaining{"type":"sriov"}then{"type":"rdma"}, a pod requesting the SR-IOV resource withIPC_LOCK.[8][7][5] Expected:kubectl exec -it <pod> -- rdma linklists a device that the host no longer shows in the default namespace. Rollback: delete pod, NAD and device plugin, then return the node to shared mode per step 4. -
Diff against step 0 and confirm the node is back in its original mode and resource set.
Any Linux box with a kernel 5.3 or newer. No NIC is needed to explore the mode itself - the interesting states are reachable without hardware.
-
Check the tooling.
uname -randip -VExpected: kernel 5.3.0 or newer and an iproute2 built against 5.3.0 or newer - both are stated prerequisites.[3] If not: stop; therdmasubcommand may be absent or too old to report the mode. -
Read the current mode.
rdma system showExpected: a line reportingnetns shared(the default) plus the privileged-qkey state.[4] If not:command not foundmeans the iproute2 rdma tool is not installed; on a box with no RDMA devices the command may report the subsystem with an empty device list, which is still a valid reading. -
Reproduce EBUSY on purpose. With containers already running (any Docker or Podman container creates a network namespace), run
rdma system set netns exclusive. Expected:EBUSY, because “if there are active network namespaces and if one or more RDMA devices exist” the change is refused.[4] If not: on a box with no RDMA devices the condition is not met and the change may succeed - record which of the two conditions your box fails, that distinction is the whole point. Rollback:rdma system set netns shared. Run it even if the previous command appeared to fail, and re-run step 2 to confirm. -
Read the crash transcript. Open the captured
rdma-shared-dp-dslog from a node in exclusive mode and find the lineExiting.. can not change : incorrect RDMA subsystem network namespace.[2] Expected: you can point at the line and say which of the two components is refusing - the plugin, not the kernel. -
Write both NADs. One chained
sriovthenrdma, one deliberately reversed. Validate both as JSON. Expected: both parse. That is the lesson - the ordering error is not a syntax error, so nothing catches it before runtime.[3] -
Confirm you left the box as you found it.
rdma system showmust reportnetns sharedagain.
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, the two ways a pod can end up with RDMA, why a node has to pick one, and what the wrong pairing looks like in kubectl.
Sources
Facts in this lesson were checked against k8s-rdma-shared-dev-plugin README and issue 87, rdma-cni README, rdma-system(8), NVIDIA Network Operator 26.7.0 deployment guide and Spectrum-X verify page - all re-fetched 2026-09-07. Dates are when each page was fetched.
- GitHub - Mellanox/k8s-rdma-shared-dev-plugin · fetched 2026-09-07
- Issue 87 - When netns is set to exclusive mode rdma-shared-dp-ds will not start · fetched 2026-09-07
- GitHub - k8snetworkplumbingwg/rdma-cni · fetched 2026-09-07
- rdma-system(8) - Linux manual page · fetched 2026-09-07
- NVIDIA Network Operator v26.7.0 - Deployment Guide with Kubernetes · fetched 2026-09-07
- NVIDIA Network Operator v26.7.0 - Spectrum-X Verify and Troubleshoot · fetched 2026-09-07
- NVIDIA Network Operator v23.7.0 - K8s on Bare Metal - Ethernet · fetched 2026-09-07
- GitHub - k8snetworkplumbingwg/sriov-network-device-plugin · fetched 2026-09-07
- Kubernetes - Device Plugins · fetched 2026-09-07
- Multus CNI - How to use · fetched 2026-09-07
- NVIDIA Network Operator v26.7.0 - Spectrum-X Architecture and Components · fetched 2026-09-07
- NVIDIA Network Operator v26.7.0 - SOS-Report Collection Script · fetched 2026-09-07
- NVIDIA Network Operator v26.7.0 - Quick Start Guide for Kubernetes · fetched 2026-09-09
The same idea elsewhere
Other lessons that cover this ground, sometimes from another course's angle.
- RDMA in Kubernetes: the NVIDIA Network OperatorRoCE course · Same ground: verification, pod and shared
- The triage ladder: sos-report, error strings and the checklistsElsewhere in this course · Same ground: nad, prefix and rdma
- GPUDirect RDMA: dma-buf, nvidia-peermem and GDSElsewhere in this course · Same ground: prereqs, pod and misconception