Skip to content

How kubelet learns a NIC exists

S2·E1The pod that never left Pending · Dell customer lab, Round Rock, first morning of acceptance week

S2·E1Understand~20 minsources checked todayverified against Kubernetes device-plugin concept page, NVIDIA Network Operator 26.7.0 platform-support and deployment guide, sriov-network-device-plugin and k8s-rdma-shared-dev-plugin READMEs - all re-fetched 2026-09-07

Builds on: Multus and the NetworkAttachmentDefinition, macvlan, ipvlan, host-device and cluster-wide IPAM

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

  • Describe the device-plugin registration handshake and name the socket kubelet listens on.
  • List the five DevicePlugin RPCs and say which one carries the NUMA hint.
  • Explain why an extended resource cannot be shared or overcommitted and what the RDMA shared plugin does instead.
  • Name the three places a resource name must agree and the four prefixes that produce it.

Episode 1 — The pod that never left Pending

The situation · Dell customer lab, Round Rock, first morning of acceptance week

Twelve PowerEdge nodes, one rail fabric, and a contract that pays out when a multi-node training run hits its number on Friday. It is Monday. The customer’s first test pod has been Pending for an hour, and the network lead is scrolling every Multus log on that node looking for the error that explains it. There is no error. The Dell SE refreshes the tab anyway, coffee going cold next to row 14 of his promise spreadsheet.

The silence is the ticket. CNI plugins run only after the scheduler has placed a pod, so a pod that is never placed produces no network error at all.

You ask for two lines instead of the logs: the node’s Allocatable block, and the pod’s resource request. The node advertises intel.com/hostdev. The pod asks for nvidia.com/hostdev. Nobody typed “intel” anywhere - the upstream sriov-network-device-plugin leaves resourcePrefix optional and defaults it to intel.com, on a 15b3 NVIDIA card as on any other.[2]

That default is the price of a design worth having. Kubernetes ships no knowledge of anybody’s NIC, so vendors advertise hardware to kubelet “without modifying Kubernetes core code” - a DaemonSet, a socket in a fixed directory, a name and a count.[1] Everything the scheduler believes about that card, it believes because an agent on the node said so.

Read the name the node is advertising before you read anything else.

Segment 1 follows that name from the socket to the node object.

1A vendor agent and two sockets

Kubernetes ships no knowledge of your NIC. The device-plugin framework exists so vendors can advertise hardware to kubelet “without modifying Kubernetes core code”, which is why every NIC resource in this course - nvidia.com/hostdev, rdma/rdma_shared_device_a, nvidia.com/rail0 - is produced by a DaemonSet and not by Kubernetes itself.[1] The framework has been stable since v1.26.[1]

The handshake has one fixed address and one variable one. Kubelet listens on the hardcoded registration socket /var/lib/kubelet/device-plugins/kubelet.sock, exporting a single Registration service.[1] The plugin serves its own gRPC socket in that same directory and, in the Register call, hands kubelet three things: the name of that socket, the Device Plugin API version, and the ResourceName it intends to advertise.[1]

Order matters and the documentation says so: start the gRPC service first, register second.[1] A plugin that registers before it can serve can be asked for devices it does not yet have.

Once registered, the plugin implements five RPCs: GetDevicePluginOptions, ListAndWatch (a stream), Allocate, GetPreferredAllocation and PreStartContainer.[1] ListAndWatch is the one that carries the inventory, and it is also how a plugin reports that a device has gone unhealthy, so kubelet stops scheduling onto it.[1] It is also the RPC that carries the topology hint: a plugin that wants Topology Manager alignment sends TopologyInfo back as part of the Device struct during ListAndWatch, and each Device in that stream can name the NUMA node it sits on.[1]

The last piece of the contract is restart handling. Plugins are told to watch for deletion of their own Unix socket, because that is how a kubelet restart appears from the outside, and to re-register afterwards.[1] A NIC resource that vanishes from a node after a reboot and never returns is almost always a plugin that failed to re-register - not a card that failed.

Rendering diagram…
Diagram source (Mermaid)
flowchart LR; A["1. Plugin DaemonSet starts and initializes the devices"] --> B["2. Plugin opens its own gRPC socket in /var/lib/kubelet/device-plugins/"]; B --> C["3. Register on kubelet.sock: socket name + API version + ResourceName"]; C --> D["4. ListAndWatch streams the device list and health"]; D --> E["5. Kubelet publishes the name and count in node Allocatable"]; E --> F["6. Scheduler places a pod that requests that exact name"]; F --> G["7. Allocate returns device nodes, env vars, mounts, CDI names"]; G --> H["8. Container starts with the device visible"]; E -. "kubelet restart deletes the socket" .-> B
The full path from a DaemonSet starting to a device appearing inside a container. Step 5 is the one you can see with kubectl; steps 2 and 3 are only visible on the node.

2What a resource name is, and the arithmetic it obeys

The naming convention is vendor-domain/resourcetype - nvidia.com/gpu is the canonical example.[1] That string is not decoration. It is the entire scheduling contract: the scheduler compares the name a pod requests against the names nodes publish, character for character.

Extended resources obey three rules that shape every design in this module. They are integer only, they are not overcommittable, and devices “cannot be shared between containers”.[1] There is no 0.5 of a VF and no burst above the advertised count.

That is why the RDMA shared device plugin looks like it breaks the rules. Its required rdmaHcaMax field is the “maximum number of RDMA resources that can be provided by the device plugin resource”, it has no default, and it is what lets one physical HCA be advertised many times over.[3] Nothing in the kernel has been partitioned. The number is a scheduling budget, and the isolation you might assume from an integer resource simply is not there.[3][1] Lesson 3 turns that into a design choice.

The prefix half of the name has four different origins, and this is the most expensive default in the stack. In the upstream sriov-network-device-plugin, resourceName is required while resourcePrefix is optional and defaults to intel.com - on a 15b3 NVIDIA card, on any card.[2] The RDMA shared device plugin defaults its prefix to rdma, which is where rdma/rdma_shared_device_a comes from.[3][4] The NVIDIA Network Operator writes nvidia.com explicitly in its sriovDevicePlugin block, producing nvidia.com/hostdev.[4] OpenShift uses the openshift.io prefix for SR-IOV resources instead.[5]

Four sources, four strings, one scheduler that does exact matching.

3Allocate: what actually reaches the container

Allocate is where the abstraction becomes a file. The AllocateResponse tells the runtime how to modify the container: annotations, device nodes under /dev, environment variables, mounts, and fully-qualified CDI device names.[1] CDI support went alpha in v1.28, beta in v1.29 and GA in v1.31, behind the DevicePluginCDIDevices feature gate.[1]

For the SR-IOV plugin the environment-variable half is the part you will read in the field. It injects PCIDEVICE_<RESOURCE_NAME> holding the allocated device IDs and PCIDEVICE_<RESOURCE_NAME>_INFO holding JSON with mount points and metadata; the resource name is upper-cased with . and / replaced by underscores.[2] So nvidia.com/hostdev becomes PCIDEVICE_NVIDIA_COM_HOSTDEV. When you want proof from inside a running pod that a specific VF was handed to it, that variable is the proof.

One field does not travel in Allocate at all, and it is the one that matters most later. TopologyInfo is sent back “as part of the Device struct during ListAndWatch”, not in the AllocateResponse: it names the NUMA node of the device, and it is the only reason Topology Manager can align a NIC with a GPU at all.[1] Topology Manager aligns pods of every QoS class, but only for resources whose hint providers actually supply hints.[7] A plugin that publishes no TopologyInfo makes its devices invisible to alignment, and a single-numa-node policy will then align everything except the NIC.[7] Module 3 spends a whole lesson on the consequences.

Which devices show up at all depends on what the plugin was told to select and on what the operator supports. Network Operator 26.7.0 lists ConnectX-6, ConnectX-6 Dx, ConnectX-7, ConnectX-8 SuperNIC and ConnectX-9 SuperNIC, plus BlueField-3 DPU and BlueField-3 SuperNIC with the qualifier NIC mode only.[6] A BlueField left in DPU mode is not a Network Operator story.[6]

4Three places the name must agree

Every Pending-forever NIC pod in this course is the same bug in a different costume: the name is spelled three times and one spelling differs.

  1. Advertised by the device plugin, from resourcePrefix plus resourceName in its ConfigMap.[2][3]
  2. Referenced in the NetworkAttachmentDefinition annotation, so Multus knows which device plugin allocated the PCI address it must pass down.[4]
  3. Requested in the pod’s resources.requests and resources.limits.[4]

The evidence chain is short. On the node, ls -l /var/lib/kubelet/device-plugins/ shows whether the plugin is talking to kubelet at all.[1] From the API, kubectl describe node | grep -A20 Allocatable shows the name and count kubelet publishes.[8] If the name is there and the pod is still Pending, the pod is asking for a different string. If the name is not there, work down: the plugin DaemonSet, then its ConfigMap selectors, then the host itself, where NVIDIA’s own collector runs lsmod, ibstat, ibv_devinfo, mst status and dmesg.[8]

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

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

sriov-network-device-plugin (prefix omitted)

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: intel.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
# ConfigMap sriovdp-config — no resourcePrefix key
{ "resourceList": [{
    "resourceName": "hostdev",
    "selectors": { "vendors": ["15b3"], "isRdma": true }
}]}
Advertised: intel.com/hostdev · allocatable 8
  • resourceName is required; resourcePrefix, deviceType and selectors are optional.
  • deviceType defaults to netDevice; the other values are accelerator and auxNetDevice.
  • Selectors for netDevice: vendors, devices, drivers, pciAddresses, acpiIndexes, pfNames, rootDevices, linkTypes, ddpProfiles, isRdma, needVhostNet, vdpaType.

FAE angle. An NVIDIA card advertising an intel.com resource is not broken hardware and not a mislabelled NIC — it is the plugin default. Do not chase the vendor id.

sriov-network-device-plugin · 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

Start with the hand-written ConfigMap that omits resourcePrefix. Predict the advertised name before you reveal it, then fix the mismatch by changing only one of the three places.

Back in the lab, before lunch

How it ended

The fix is one key in the ConfigMap - resourcePrefix: nvidia.com - and the plugin re-advertises. You prove the restart path in the same session by deleting the plugin’s own socket, because the framework tells plugins to treat that deletion as a kubelet restart and register again.[1] The network lead starts a three-column table in his notebook: socket file, advertised name, owning plugin. Twelve nodes agree.

Then the first pod that actually schedules dies during attachment, on a NetworkAttachmentDefinition somebody hand-wrote from an example the night before. The SE adds a row to the spreadsheet.

Lab

Read-only on the Dell-lab BlueField-3 or ConnectX host. No step below changes firmware, driver state or cluster configuration; there is nothing to roll back.

  1. Pre-flight inventory. Record, in this order: uname -r, lsmod | grep -E 'mlx5|ib_core', ibstat, ibv_devinfo, mst status, dmesg | tail -50. That list is exactly what NVIDIA’s own kubectl netop-sosreport collects on the node, so it is the sequence support will ask for.[8]

  2. List the real sockets. ls -l /var/lib/kubelet/device-plugins/ Expected: kubelet.sock plus one socket per registered plugin. If not: no extra sockets means no plugin registered, and every downstream symptom is explained by that.[1]

  3. Read what kubelet publishes. kubectl describe node <bf3-node> | grep -A20 Allocatable Expected: entries such as nvidia.com/hostdev or rdma/rdma_shared_device_a with integer counts. If not: compare against step 1 - a socket with no Allocatable entry means the plugin registered and then selected zero devices.

  4. Attribute each name to its owner. For every advertised name, decide from the prefix alone which plugin produced it: rdma/... is the RDMA shared device plugin’s default prefix, nvidia.com/... is what the Network Operator writes, intel.com/... is a hand-written SR-IOV ConfigMap with the prefix omitted.[2][3][4]

  5. Confirm the operator’s view. kubectl get nicclusterpolicy nic-cluster-policy -o yaml Expected: the rdmaSharedDevicePlugin and/or sriovDevicePlugin blocks whose config produced the names in step 2.[4] If not: an empty result means the policy is named something else, and the operator ignores it.

  6. Confirm the card is in a supported mode. Check that the BlueField-3 in this host is presenting as a NIC, since Network Operator 26.7.0 supports BlueField-3 SuperNIC in NIC mode only.[6]

  7. Write the mapping down. Produce a three-column table - socket file, advertised name, owning plugin - and keep it. In module 4 you will use exactly this table as step one of the triage ladder.

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 four sentences, how kubelet comes to believe a ConnectX card exists, and why the pod that asks for it can stay Pending forever without a single error from the CNI.

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

Sources

Facts in this lesson were checked against Kubernetes device-plugin concept page, NVIDIA Network Operator 26.7.0 platform-support and deployment guide, sriov-network-device-plugin and k8s-rdma-shared-dev-plugin READMEs - all re-fetched 2026-09-07. Dates are when each page was fetched.

  1. Kubernetes - Device Plugins · fetched 2026-09-07
  2. GitHub - k8snetworkplumbingwg/sriov-network-device-plugin · fetched 2026-09-07
  3. GitHub - Mellanox/k8s-rdma-shared-dev-plugin · fetched 2026-09-07
  4. NVIDIA Network Operator v26.7.0 - Deployment Guide with Kubernetes · 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-07
  7. Kubernetes - Control Topology Management Policies on a node · fetched 2026-09-07
  8. NVIDIA Network Operator v26.7.0 - SOS-Report Collection Script · fetched 2026-09-07

The same idea elsewhere

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