k8s-launch-kit-discover
Use this skill when the user wants to discover their Kubernetes cluster's network hardware capabilities using k8s-launch-kit (l8k). Activate for: cluster…
npx skills add https://github.com/nvidia/k8s-launch-kit --skill k8s-launch-kit-discoverl8k: Cluster Discovery
PREREQUISITE: Read
../k8s-launch-kit-shared/SKILL.mdfor install paths, global flags, and output modes.
Discover cluster hardware and produce a cluster-config.yaml describing NICs, GPUs, rails, and node groups.
Three things to know about the saved file:
- The resolved profile is persisted. Discovery fills missing profile fields
from hardware/defaults, preserves values from
--user-config, applies explicit CLI overrides last, and writes the finalprofile:block. - North-south-only groups are dropped. A node group whose NICs are all north-south (BlueField DPUs / OOB NICs) produces no manifests and is omitted from
cluster-config.yaml(it would otherwise consume an NV-IPAM subnet slice and shift the per-rail pools). North-south NICs alongside east-west NICs in a mixed group are still listed. - Documentation comments are preserved. The field-reference comments from the source config (
l8k-config.yamlor--user-config) are carried over into the savedcluster-config.yaml, with aGenerated by l8k discoverbanner at the top.
l8k discover is self-contained. It does NOT require a pre-installed
Network Operator. On every run it bootstraps the NIC Configuration Daemon
(and the 5 nic-configuration-operator CRDs, only if missing) into a private
namespace nvidia-k8s-launch-kit, reads NicDevice CRs published by the
daemon, then tears the namespace down on exit (unless --keep-namespace
is set). The daemon image is pulled from <networkOperator.repository>/ nic-configuration-operator-daemon:<networkOperator.componentVersion> (both
fields come from l8k-config.yaml).
Usage (from AI agent)
l8k discover \
--kubeconfig ~/.kube/config \
--save-cluster-config ./cluster-config.yaml \
--output json 2>/dev/null | jq .
Usage (human-interactive)
l8k discover --save-cluster-config <OUTPUT> [--kubeconfig <PATH>]
Flags
| Flag | Required | Default | Description |
|---|---|---|---|
--kubeconfig | — | $KUBECONFIG env var | Path to kubeconfig (optional — falls back to env var) |
--save-cluster-config | Yes | — | Output path for cluster-config.yaml |
--keep-namespace | — | false | Skip teardown of the nvidia-k8s-launch-kit bootstrap namespace (for debugging) |
--collapse-nic-rails | — | true | Advertise one rail per NIC: collapse a NIC's multi-plane east-west PFs to its master PF, keeping a rail per port only for NICs whose VPD model is genuinely dual-port ("2-port"/"Dual-port"). Set =false to keep one rail per PF (dev setups). See "Rail collapsing" below. |
--network-operator-namespace | — | — | Deprecated for discover: accepted but ignored. The daemon always runs in nvidia-k8s-launch-kit. Still used by l8k generate / l8k deploy. |
--user-config | — | — | Base config to merge with discovered hardware |
--node-selector | — | feature.node.kubernetes.io/pci-15b3.present=true | Value written into the saved cluster-config.yaml nodeSelector (for deploy time). It does not gate discovery scheduling or the NicDevice wait set — the daemon is restricted to Ready schedulable nodes and NIC-bearing nodes are detected via a sysfs 0x15b3 probe. |
--image-pull-secrets | — | — | Image pull secret names (comma-separated). Forwarded onto the bootstrapped DaemonSet pod spec. |
--fabric | — | discovered unanimous link type | Fabric override: ethernet or infiniband. |
--deployment-type | — | sriov | Deployment override: sriov, rdma_shared, or host_device. |
--multirail | — | true | Multirail override. Use --multirail=false to opt out; explicit false is persisted. |
--spectrum-x | — | disabled | Enable Spectrum-X with an RA version (RA2.1 or RA2.2). |
--multiplane-mode | — | derived with Spectrum-X | Spectrum-X mode override: none, swplb, hwplb, or uniplane. |
--number-of-planes | — | derived with Spectrum-X | Spectrum-X plane-count override: 1, 2, or 4. |
Examples
# Basic discovery
l8k discover \
--kubeconfig ~/.kube/config \
--save-cluster-config ./cluster-config.yaml
# Using $KUBECONFIG env var (no --kubeconfig needed)
l8k discover --save-cluster-config ./cluster-config.yaml
# Keep the bootstrap namespace for debugging
l8k discover \
--kubeconfig ~/.kube/config \
--keep-namespace \
--save-cluster-config ./cluster-config.yaml
# Merge with existing config
l8k discover --user-config my-config.yaml \
--kubeconfig ~/.kube/config \
--save-cluster-config ./cluster-config.yaml
# Persist explicit profile overrides
l8k discover \
--kubeconfig ~/.kube/config \
--fabric infiniband --deployment-type rdma_shared \
--multirail=false \
--save-cluster-config ./cluster-config.yaml
# Agent mode (JSON output)
l8k discover \
--kubeconfig ~/.kube/config \
--save-cluster-config ./cluster-config.yaml \
--output json 2>/dev/null
Output Format
The generated cluster-config.yaml contains the final profile plus a
clusterConfig[] hardware-group array:
profile:
fabric: ethernet
deployment: sriov
multirail: true
clusterConfig:
- identifier: "dgx-b200-nvidia-h100-nvl"
machineType: DGX-B200
gpuType: NVIDIA-H100-NVL
capabilities:
nodes:
sriov: true
rdma: true
pfs:
- deviceID: "101e"
networkInterface: "eth0"
rail: 0
workerNodes: [node-01, node-02]
nodeSelector:
nvidia.kubernetes-launch-kit.machine: "DGX-B200-NVIDIA-H100-NVL"
thirdPartyRDMAModules: [nv_peer_mem]
Discovery patches every node in the group with two labels:
nvidia.kubernetes-launch-kit.machine: <machineType>-<gpuType>— per-source-group identity, used as the source group'snodeSelector.nvidia.kubernetes-launch-kit.gpu: <gpuType>— used as the merged-groupnodeSelectorwhenl8k generateauto-merges source groups sharing a GPU type.
Label values keep their original case (matching nvidia.com/gpu.product style) since
upstream discovery already trims whitespace and replaces spaces with hyphens. Values
that would exceed the Kubernetes 63-char label-value limit are skipped (logged at
debug). The group's identifier is the lowercase resource-name form of the machine
label (RFC 1123 — required for downstream NicNodePolicy / SriovNetworkNodePolicy
naming). When machineType or gpuType couldn't be resolved (GPU operator labels
absent and hardware probe failed), a fallback group-N identifier is used and the
machine label is not written; the GPU label is still written when gpuType alone is
resolved.
Prerequisites
- Node Feature Discovery (NFD) is not required. Before bootstrapping the
daemon, discovery lists Nodes and renders node-name affinity for nodes that
are
Ready=Trueand notspec.unschedulable. The DaemonSet still has no NFDnodeSelectorand tolerates taints, so eligible control-plane/tainted nodes are included. NIC-bearing nodes are detected by a sysfs probe for PCI vendor0x15b3rather than the NFDfeature.node.kubernetes.io/pci-15b3.presentlabel. - Image-pull access from every worker node to
<networkOperator.repository>/nic-configuration-operator-daemon:<networkOperator.componentVersion>. Use--image-pull-secrets <name>(ornetworkOperator.imagePullSecretsin the config file) for private registries. - Network Operator does NOT need to be pre-installed.
Tips
-
Profile precedence is
hardware/default < existing YAML < explicit CLI. On a rerun with--user-config, only missing fields are recalculated. -
A fresh discovery ignores the example profile in the installed reference config. Supplying that file explicitly through
--user-configmakes its profile values user input and therefore preserves them. -
If fabric probes are mixed or unconfirmed, discovery still writes the file with an empty
profile.fabric; set--fabricon discovery or generation. -
The bootstrap namespace is always
nvidia-k8s-launch-kit— not configurable. The daemon's SA / ClusterRole / ClusterRoleBinding are renamed tok8s-launch-kit-nic-config-daemonso they don't collide with the cluster-scoped names a coexisting Network Operator install would create. -
CRDs (
nicdevices,nicconfigurationtemplates, etc.) are applied only when missing. If they already exist (because Network Operator or a prior l8k discover run created them), they're left alone — discovery never overwrites a different version. -
After discovery finishes the namespace is torn down (cascade-delete handles SA / RoleBinding / DaemonSet / pods / NicDevice CRs); the CRDs intentionally persist so any external NicDevice consumers survive. Cluster-scoped ClusterRole / ClusterRoleBinding are deleted explicitly.
-
Pass
--keep-namespaceto leave the namespace in place — useful when debugging daemon pod start-up failures (kubectl describe pod -n nvidia-k8s-launch-kit). -
Before bootstrapping, discovery pre-cleans the
nvidia-k8s-launch-kitnamespace (deletes any leftover DaemonSet/pods/NicDevice CRs from a crashed prior run and waits up to 2 min for it to clear) so a fresh daemon is never layered on stale state. -
Discovery waits up to 5 minutes for daemon pods to be Ready, but tolerates stuck pods: if some pods are wedged (e.g. ImagePullBackOff/CrashLoopBackOff on an unrelated node) it proceeds with the Ready ones rather than blocking the whole window, and only aborts if no pod ever becomes Ready. Common causes of a total failure: image tag missing in your registry, pull-secret missing.
-
If discovery reports "no nodes with an NVIDIA NIC (PCI vendor 15b3) were found", the daemon ran but no node's sysfs exposed a
0x15b3device (no Mellanox/NVIDIA NICs, or/sysnot mounted/readable in the pod). -
After determining each group's
(machineType, gpuType), discovery looks up a topology preset underpresets/using exact-match lookup on that pair. A matching preset overrides heuristic-derived topology fields (traffic class, rail, NUMA, GPU affinity). There is no any-GPU fallback — a preset with emptygpuType:is rejected at load time. If no preset matches, discovery proceeds with heuristic classification. -
If you already know the SKU and want to skip cluster discovery entirely, use
l8k generate --for <preset>(seek8s-launch-kit-generate).
Rail collapsing (one rail per NIC)
By default (--collapse-nic-rails, default true) discovery advertises one
rail per NIC, not one per PF. A single NIC can expose several east-west PFs
that are planes of one physical port (Spectrum-X multi-plane ConnectX-8/9). For
those, only the master PF (lowest PCI function) is written to
cluster-config.yaml, so the rail count reflects physical NICs — e.g. an 8-PF
ConnectX-8 node yields 4 rails, not 8. The per-PF planes are reconstructed at
generate time from the multiplane mode + numberOfPlanes.
The exception is a genuinely dual-port NIC: when the NIC's VPD model name
(read from NicDevice.Status.modelName, surfaced as the model field on each
PF) contains a port-count keyword like 2-port/Dual-port, each port is kept as
its own rail. Single-port (1P) and multi-plane models (no port-count keyword)
collapse. An empty/unreadable model collapses by default.
Pass --collapse-nic-rails=false to restore the legacy one-rail-per-PF behaviour
(handy on dev setups). Note: a collapsed group no longer has the same PF count as
a full-PF topology preset, so such presets won't apply by exact match and the
live (collapsed) classification is kept (a preset-deviation warning is emitted).
See Also
- k8s-launch-kit-shared — Global flags and output modes
- k8s-launch-kit-config — Understand the config file discovery produces
- k8s-launch-kit-generate — Generate manifests from cluster config