The triage ladder: sos-report, error strings and the checklists
S4·E402:10, and the restart that deletes the answer · A bridge call opened by the night shift, five days before acceptance
Builds on: RDMA in pods: shared plugin vs exclusive netns, NUMA alignment with Topology Manager and CPU Manager
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
- Justify collecting the sos-report before making any change, using what the archive actually contains.
- Run the five-command host ladder in order and stop at the first rung that fails.
- Map a verbatim error string to its cause without re-deriving the mechanism each time.
- Judge whether a symptom is a host problem or a Kubernetes problem and name the one command that decides it.
Episode 4 — 02:10, and the restart that deletes the answer
The night-shift operator called it in at 02:10 because he is the one who notices things: a pod restarting on the node whose front panel he labelled rail0 and rail1 himself, with the label maker he brings to every rebuild. By the time you join, the operations lead has a sentence ready — “let us just restart the driver DaemonSet and see” — and he is not being reckless. It worked once, and acceptance is Friday.
The reason to stop him is not caution, it is evidence. The Network Operator ships its own collector, kubectl netop-sosreport, and what it gathers is the argument for running it first: every operator custom resource and CRD, deployments and DaemonSet specs, events and webhook configurations, node labels, conditions and allocatable resources — and current and previous container logs.[1] Previous-container logs are the whole case. If the answer is the shared RDMA device plugin exiting with Exiting.. can not change : incorrect RDMA subsystem network namespace, that string lives in a dead container’s log, and the node’s rdma system show mode is what proves it.[3][4] If speed is the objection, the documented fast form exists: --skip-diagnostics --log-lines 1000.[1]
The first thing an escalation breaks should never be the evidence.
What you need next is an order to work in, so that ten minutes buys a diagnosis instead of five opinions.
1Collect before you change
The Network Operator ships its own collector. The script is kubectl-netop_sosreport, invoked as kubectl netop-sosreport [OPTIONS], or standalone as ./network-operator-sosreport.sh [OPTIONS].[1] It requires kubectl in PATH, a valid kubeconfig, cluster-admin recommended, Bash 4.0+, Python 3.6+ for the HTML report, plus tar, gzip and sha256sum.[1]
What it gathers is the argument for running it first. It collects every Network Operator custom resource and CRD; operator deployments, pods, ConfigMaps, secret metadata, RBAC, events and webhook configurations; DaemonSet and Deployment specs, pod details, current and previous container logs, related ConfigMaps and Services; and node details with labels, annotations, conditions, status and allocatable resources.[1] The phrase that decides your first move is previous container logs. Restart the driver DaemonSet to “try something” and you have deleted the evidence of what it did last time.
Two documented invocations cover the field cases: --skip-diagnostics --log-lines 1000 when you need it fast, and --verbose --log-lines 10000 when you are preparing a support case.[1]
A documentation note worth carrying, because a stale bookmark wastes time: the 26.7.0 troubleshooting index resolves to the SOS-Report page, and the per-symptom material — troubleshooting pod failures, quick health checks, preparing for a support case — appears as example workflows inside that page, not as separate URLs.[2][1] Guessed per-symptom slugs return 404.
Host does not see the BlueField
2The host ladder: five commands, stop at the first failure
The collector runs its diagnostics inside the OFED driver pod on each node: lsmod | grep mlx, ibstat, ibv_devinfo, mst status, uname -r and dmesg (last 200 lines), plus ip link / ip addr.[1] That list is also the manual checklist, with one caveat — run by hand on the host, the middle three need OFED and MFT userspace installed there; inside the driver pod they always work. Run them in that order and stop at the first one that fails, because every later rung assumes the earlier ones passed.
| Rung | Question it answers | If it fails |
|---|---|---|
lsmod |
Is the driver loaded at all? | Nothing else is meaningful. Go to the DOCA-OFED driver container and its build against the running kernel |
ibstat |
Does the host see the HCA, and is the port Active? | Cabling, link, or a card that enumerated but never came up |
ibv_devinfo |
Does the verbs layer enumerate the device? | Userspace library and device permissions, not the fabric |
mst status |
Does the management interface see the card? | Firmware tooling and PCIe, not Kubernetes |
dmesg |
What did the kernel say about firmware and PCIe? | Read it before forming any theory |
Only after all five pass does the problem become a Kubernetes problem — and then it is one of a short list: the device plugin never registered, the NAD or annotation or namespace is wrong, the resource-name prefix does not match, the RDMA netns mode is wrong for the deployed design, or the MTU disagrees somewhere.[1][7][10]
On the Kubernetes side there is a rung above all of them that costs one command: the Network Operator acts only on a NicClusterPolicy named exactly nic-cluster-policy, and an instance with any other name is silently ignored.[13] “We installed the operator and nothing happened” is that sentence more often than it is anything else.
3Error strings, memorised
Some of this job is genuinely recall. These strings appear verbatim in logs and each maps to exactly one cause.
Exiting.. can not change : incorrect RDMA subsystem network namespace — the RDMA shared device plugin started while the node’s RDMA subsystem is in exclusive mode; the pod rdma-shared-dp-ds sits in CrashLoopBackOff.[3] Prove it with rdma system show, which displays the netns mode.[4] This is not a bug to work around: exclusive netns is required for per-pod RDMA isolation with SR-IOV plus rdma-cni, and it breaks the shared plugin, so the two models cannot run on one node.[12][3]
EBUSY from rdma system set netns exclusive — “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 mode must be set before namespaces exist. The man page also advises against changing the subsystem mode while RDMA traffic is running, even though it is supported.[4]
spectrum-x config version <name> not found — the profile ConfigMap is missing, lacks the watch label, or the name does not match spectrumXOptimized.version character for character.[5]
TopologyAffinityError — Topology Manager under restricted or single-numa-node could not satisfy alignment, so the pod is rejected and is not automatically retried onto a better node.[6]
nicdevice condition reasons — PartiallyApplied means some parameters were unsupported for that device and the message lists them; IncorrectSpec and SpecValidationFailed mean the NicConfigurationTemplate is invalid for that device; NonVolatileConfigUpdateFailed, RuntimeConfigUpdateFailed and FirmwareError mean the apply itself failed and the next stop is the nic-configuration-daemon log for that node.[5]
exclusive 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.
4The two checklists that catch the rest
The resource-name checklist. One name has to agree in three places: what the device plugin advertises, what the NAD references, and what the pod requests. Four different defaults collide there — intel.com is the sriov-network-device-plugin’s default resourcePrefix, which is why NVIDIA’s own configs always set "resourcePrefix": "nvidia.com" explicitly; the RDMA shared device plugin defaults to rdma, producing names like rdma/rdma_shared_device_a; and OpenShift writes openshift.io.[7][8][9] A pod requesting the wrong prefix stays Pending forever with no CNI error at all, because scheduling never gets far enough to call CNI.[7]
Hidden until step 4. Walk the three places first, then predict.
1. The device plugin advertises a name
Somebody hand-wrote the device-plugin ConfigMap and left resourcePrefix out.
resourcePrefix defaults to intel.com. The pool is advertised as intel.com/hostdev even on a 15b3 (NVIDIA) ConnectX.
Registration happens on the hardcoded socket /var/lib/kubelet/device-plugins/kubelet.sock; the plugin then serves its own gRPC socket in the same directory and implements GetDevicePluginOptions, ListAndWatch, Allocate, GetPreferredAllocation and PreStartContainer. ListAndWatch is also how an unhealthy device is signalled, and TopologyInfo in the same response is the NUMA hint Topology Manager later needs.
# ConfigMap sriovdp-config — no resourcePrefix key
{ "resourceList": [{
"resourceName": "hostdev",
"selectors": { "vendors": ["15b3"], "isRdma": true }
}]}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-sosreportSame 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.
The NAD checklist. Five items, all checkable in two minutes.[10][9][12]
- Is the NAD in the pod’s namespace? On OpenShift, Multus is pre-configured with namespace isolation enabled by default, so a NAD in namespace A cannot be referenced from namespace B.[9] Namespace-qualify as
testns1/macvlan-conf-3or with the JSON"namespace"key.[10] - Is the annotation key exactly
k8s.v1.cni.cncf.io/networks?[10] - Is
spec.configvalid JSON? A NAD may instead have nospecand be backed by a file in/etc/cni/multus/net.d/on the node — a half-migrated cluster has both and neither works.[10] - Does the NAD’s
resourceNamematch an advertised resource, prefix included?[7] - For chained RDMA, does
{"type":"rdma"}come after thesriovorib-sriovplugin in thepluginsarray?[12]
The MTU checklist. Three MTUs must agree: switch port, host PF, pod interface. Calico’s own rule is to “set the workload endpoint MTU and the tunnel MTUs to the same value (so all paths have the same MTU)”, subtracting 20 for IP-in-IP, 50 for VXLAN IPv4, 70 for VXLAN IPv6, 60 for WireGuard IPv4 and 80 for WireGuard IPv6.[11] A mismatch on the secondary interface produces RDMA that connects and then stalls on large messages — which reads like a fabric problem and is reported as “training is slow”.[11]
5Running the ladder under pressure
Symptom as reported: “the RDMA pod is Pending and the customer says the fabric is fine.”
- Collect.
kubectl netop-sosreport --skip-diagnostics --log-lines 1000. Fast form, because the customer is on the call. The archive still contains the CRs, the DaemonSet specs, the node allocatable list and previous container logs.[1] - Classify: host or Kubernetes? The pod is Pending, so CNI has never been invoked and no host rung can be the cause of this symptom. Skip the five host commands for now; they matter for a different symptom shape.
- Look at what the node advertises.
kubectl describe node <node> | grep -A20 Allocatable. The list showsrdma/rdma_shared_device_a: 63and nonvidia.com/entry.[8] - Look at what the pod requested.
kubectl get pod <pod> -o jsonpath='{.spec.containers[0].resources}'. It requestsnvidia.com/hostdev: 1. - Verdict in one line. The node advertises the shared RDMA plugin’s resource; the pod asks for the SR-IOV device plugin’s resource. Nobody advertises
nvidia.com/hostdev, so the pod will never schedule and no CNI error will ever appear.[7] - Fix, and say which one you chose. Either change the pod to request the advertised name, or add an
sriovDevicePluginsection toNicClusterPolicythat advertisesnvidia.com/hostdev.[8] These are different designs, not equivalent fixes — the shared plugin is a scheduling counter, SR-IOV plus rdma-cni is isolation.[12] - Confirm. Re-run step 3 and see the new name in Allocatable before re-submitting the pod.
Note what never happened: nobody restarted anything, nobody looked at a switch, and the whole diagnosis was three read-only commands after the collector.
Second cluster, different symptom: the pod is Running but rdma link inside it lists nothing.
- Collect first with
kubectl netop-sosreport ____. - Classify: the pod is Running, so CNI succeeded — this is a ____ problem or a ____ problem, not a scheduling one.
- Run the node ladder in order:
____,____,____,____,____. All five pass. - Ask the design question:
rdma system ____. It answersexclusive. - Check the NAD: is
{"type":"rdma"}placed ____ the sriov plugin in thepluginsarray? - Write the verdict and the single edit that fixes it, and state why the pod was Running rather than failing at CNI ADD.
Third cluster, under a stopwatch. Symptom: an MPIJob’s workers are Running, NCCL reports a transport, and throughput is roughly one tenth of expectation. Nothing is in CrashLoopBackOff, nothing is Pending, and the customer has already restarted the driver DaemonSet twice.
Produce a written triage. Acceptance criteria: it must state which evidence the two restarts destroyed and what you can still recover; it must name the ladder rungs you would skip and justify skipping them from the symptom shape; it must give the three MTU values you need and the command that reads each one; and it must end with one A/B test whose result would either confirm or eliminate RDMA as the cause in a single run.
Case notes, 02:40
The archive is on disk before anything restarts. The previous-container log for rdma-shared-dp-ds carries the error string verbatim, and rdma system show returns exclusive — the whole case in two outputs, because the node is running the design that requires exclusive netns and the shared plugin was deployed beside it.[3][4] The operator prints one more label before he goes home.
Acceptance passes on the Friday. What is left is the account meeting: a deck due, procurement asking about switches, and the customer’s architect asking “which CNI do you support?” as though it had a one-word answer.
Lab
On the Dell-lab BlueField-3 or ConnectX host. This lab mutates node state; every mutating step names its rollback and you should run it on a host you can reboot.
- Pre-flight inventory. Record all of:
uname -r,lsmod | grep -E "mlx5|ib_core",ibstat,ibv_devinfo,mst status,dmesg | tail -50,rdma system show, andip -br link. Save the output to a file with a timestamp. This is both your baseline and your rollback reference. Then capture the cluster objects you are about to edit:kubectl -n nvidia-network-operator get cm -o yaml > baseline-cm.yamlandkubectl get nicclusterpolicy nic-cluster-policy -o yaml > baseline-ncp.yaml. Host output alone is not a rollback for a step that edits a ConfigMap. - Run the ladder clean. Execute
lsmod,ibstat,ibv_devinfo,mst status,dmesgin that order and write one line per rung recording what “healthy” looks like on this host. Expected: five passes and a written baseline you can compare against later. Ifibstatormst statusis not found on the host, run the same rungs where the collector runs them:kubectl -n nvidia-network-operator exec ds/<mofed-ds> -- <cmd>. - Break one thing you can revert — option A, module unload. Pick a module whose removal you have verified is reversible on this host (check its dependency list with
lsmodfirst). Unload it, re-run the ladder, and record which rung caught it first. Expected:lsmodfails immediately and everything below it is meaningless. Rollback:modprobe <module>and re-run the ladder until it matches the step 2 baseline. - Break one thing you can revert — option B, wrong selector. Edit the device-plugin config so
ifNamesselects an interface that does not exist. Expected: nothing is advertised;kubectl describe node | grep -A20 Allocatableloses the resource. Rollback:kubectl apply -f baseline-cm.yaml, thenkubectl -n nvidia-network-operator rollout restart ds/<device-plugin-ds>, and re-runkubectl describe node <node> | grep -A20 Allocatableuntil the resource reappears with the count it had in step 1. - Optional, netns mode. Record the current mode with
rdma system show. If and only if the node is drained, runrdma system set netns exclusive, observe the shared device plugin’s crash loop and capture the exact error string. Rollback:rdma system set netns shared; if it returns EBUSY, reboot the node — that is the documented consequence of active namespaces, not a mistake you made. - Collect the archive for real.
kubectl netop-sosreport --verbose --log-lines 10000. Expected: an archive with a checksum. Open the node-diagnostics section and confirm it contains the same five command outputs you ran by hand. - Restore and verify. Re-run every command from step 1 and diff against the saved baseline. Expected: no differences. Do not end the session until that is true.
Goal: five seeded failures on kind, under a stopwatch, plus an inventory of what the collector produces. Nothing here needs an NVIDIA NIC; the point is the procedure and the symptom shapes.
- Set up.
kind create cluster --name triage-lab, install Multus (thick) and a fake device plugin or the sriov-network-device-plugin with a ConfigMap you control. Expected:kubectl get pods -Ahealthy. Record a baselinekubectl describe node | grep -A20 Allocatable. - Failure 1 — wrong
resourcePrefix. LeaveresourcePrefixout of the device-plugin ConfigMap so it advertises underintel.com, and have the pod requestnvidia.com/.... Expected: pod Pending, no CNI error. Time yourself; write down the symptom, the one command that proved it (describe node), and the fix. Rollback: restore the ConfigMap and restart the plugin DaemonSet. - Failure 2 — NAD in the wrong namespace. Create the NAD in
defaultand the pod indemo. Expected: pod fails at CNI with a Multus error naming the missing network. Note that this shape is different from failure 1 — the pod was scheduled. Rollback: delete the stray NAD. - Failure 3 —
rdmachained beforesriov. Write the NAD’spluginsarray in the wrong order. Expected: a CNI-time error. Record the exact text. Rollback: reorder and re-apply the NAD. - Failure 4 — MTU mismatch on the secondary interface. Give the secondary interface an MTU below the master’s and run a large-message transfer. Expected: it connects and then stalls on large messages, with small transfers fine. Rollback: reset the MTU.
- Failure 5 —
NicClusterPolicymisnamed. If you have the operator installed, apply a policy named anything other thannic-cluster-policy. Expected: nothing happens at all, and no error is logged. Rollback: delete the misnamed CR. - Inventory the archive. Run
kubectl netop-sosreport --skip-diagnosticsand untar the result. List every top-level directory and match at least six of them to items in the documented collection list. Expected: you can point to the previous-container logs directory specifically. - Write the table. Five rows: symptom shape, the one command that proved it, the fix, and how long it took you. Expected: your times drop between the first and fifth.
- Clean up.
kind delete cluster --name triage-lab.
Retrieval check
10 questions from memory. Answer before looking anything up; misses become flashcards.
Explain it to a Dell SE
Explain to a Dell L2 engineer, in five sentences, why the first action on an RDMA-in-Kubernetes escalation is to collect an archive rather than to fix anything.
Sources
Facts in this lesson were checked against NVIDIA Network Operator v26.7.0 SOS-Report page and troubleshooting index re-fetched 2026-09-07; Spectrum-X verify-and-troubleshoot re-fetched 2026-09-07; rdma-system(8) and Kubernetes Topology Manager docs as cited. Dates are when each page was fetched.
- NVIDIA Network Operator v26.7.0 — SOS-Report Collection Script · fetched 2026-09-07
- NVIDIA Network Operator v26.7.0 — Troubleshooting (index) · fetched 2026-09-07
- Issue #87 — rdma-shared-dp-ds crash-loops under exclusive netns mode · fetched 2026-09-07
- rdma-system(8) — Linux manual page · fetched 2026-09-07
- NVIDIA Network Operator v26.7.0 — Spectrum-X Verify and Troubleshoot · fetched 2026-09-07
- Kubernetes — Control Topology Management Policies on a node · fetched 2026-09-07
- GitHub — k8snetworkplumbingwg/sriov-network-device-plugin · fetched 2026-09-07
- NVIDIA Network Operator v26.7.0 — Deployment Guide with Kubernetes · fetched 2026-09-07
- NVIDIA Network Operator v26.7.0 — Deployment Guide with OpenShift · fetched 2026-09-07
- Multus CNI — How to use · fetched 2026-09-07
- Calico — Configure MTU to maximize network performance · fetched 2026-09-07
- GitHub — k8snetworkplumbingwg/rdma-cni · fetched 2026-09-07
- GitHub — Mellanox/network-operator (NVIDIA Network Operator) · fetched 2026-09-07
The same idea elsewhere
Other lessons that cover this ground, sometimes from another course's angle.