npx skills add ...
npx skills add nvidia/k8s-launch-kit --skill 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 discovery, hardware detection, NIC detection, finding what GPUs or NICs are in a cluster, creating a cluster config file, or when the user says 'discover' in the context of l8k or NVIDIA networking.
npx skills add nvidia/k8s-launch-kit --skill k8s-launch-kit-discover
PREREQUISITE: Read
../k8s-launch-kit-shared/SKILL.mdfor install paths, global flags, and output modes.
This workflow uses the default host target; --target host is equivalent.
The CLI snapshots discovery arguments and runs the concrete Host discovery
operation through the target registry; command syntax and artifacts are
unchanged.
Discover cluster hardware and produce a cluster-config.yaml describing NICs, GPUs, rails, and node groups.
Three things to know about the saved file:
--user-config,
discovery fills profile fields from hardware/defaults and applies explicit
CLI overrides last. With --user-config, it replaces only clusterConfig,
preserves every other section, and then applies explicit CLI overrides.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.l8k-config.yaml or --user-config) are carried over into the saved cluster-config.yaml, with a Generated by l8k discover banner 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).
| 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 | — | — | Config whose clusterConfig is refreshed; all other sections are preserved unless explicitly overridden by a CLI flag |
--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 discoverable NICs are detected via sysfs; restricted BlueFields are excluded. |
--image-pull-secrets | — | — | Image pull secret names (comma-separated). Forwarded to the discovery DaemonSet and persisted for generated policies, Network Operator Helm values, and authenticated chart downloads during deploy. |
--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, or hwplb. |
--number-of-planes | — | derived with Spectrum-X | Spectrum-X plane-count override: 1, 2, or 4. |
The generated cluster-config.yaml contains the final profile plus a
clusterConfig[] hardware-group array:
Discovery patches every node in the group with two labels:
nvidia.kubernetes-launch-kit.machine: <identifier> — per-source-group
identity, identical to the persisted group identifier and used as its
nodeSelector.nvidia.kubernetes-launch-kit.gpu: <gpuType> — used as the merged-group
nodeSelector when l8k generate auto-merges source groups sharing a GPU type.Group identifiers and machine-label values are lowercase and omit complete NVIDIA
segments. The same pass maps ThinkSystem to ts and PowerEdge to pe. They are
bounded to 30 bytes; long values retain balanced machine/GPU prefixes plus a
6-character deterministic hash, and unused prefix space moves to the longer component.
GPU-only labels retain their discovered case and vendor segment, use Kubernetes'
63-byte limit, and keep the existing 8-character shortening hash. 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.
Ready=True and not spec.unschedulable. The DaemonSet still has no
NFD nodeSelector and tolerates taints, so eligible control-plane/tainted
nodes are included. NIC-bearing nodes are detected by a sysfs probe for PCI
vendor 0x15b3 rather than the NFD
feature.node.kubernetes.io/pci-15b3.present label. BlueFields for which
mlxprivhost reports level: restricted are excluded because the daemon
will not publish NicDevice resources for them. Mixed nodes remain eligible
when they have at least one non-restricted NVIDIA NIC.<networkOperator.repository>/nic-configuration-operator-daemon:<networkOperator.componentVersion>.
Use --image-pull-secrets <name> (or networkOperator.imagePullSecrets
in the config file) for private registries.Fresh discovery writes validation.gpuDirect.enabled. It is true only when
every worker in every discovered group can satisfy its render bucket's
topology-derived validation.gpuDirect.gpuResourceType request; otherwise it
is false. With --user-config, the supplied validation section is preserved.
Do not add GPU counts or resource maps under clusterConfig.
Fresh profile precedence is hardware/default < explicit CLI. On a rerun
with --user-config, no profile fields are recalculated; explicit CLI flags
still override their corresponding values.
A fresh discovery ignores the example profile in the installed reference
config. Supplying that file explicitly through --user-config makes 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 --fabric on discovery or generation.
clusterConfig[].netplanManaged is true when any worker has an NVIDIA PF
selected by a host netplan match.macaddress plus set-name rule. Discovery
checks every worker and uses PF MACs only transiently; they are not saved in
the shared group config. If generation would emit a
NicInterfaceNameTemplate for an affected group, clean up the host's
set-name stanzas and re-run discovery before retrying generation; do not
clear the field manually.
The bootstrap namespace is always nvidia-k8s-launch-kit — not configurable. The
daemon's SA / ClusterRole / ClusterRoleBinding are renamed to
k8s-launch-kit-nic-config-daemon so 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-namespace to 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-kit namespace
(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 0x15b3 device (no Mellanox/NVIDIA
NICs, or /sys not mounted/readable in the pod).
If discovery reports "no discoverable NVIDIA NICs were found", every detected
BlueField is in zero-trust (restricted) mode. Those devices cannot be
configured from the host and intentionally do not publish NicDevice resources.
After determining each group's (machineType, gpuType), discovery looks up a topology preset under presets/ 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 empty gpuType: 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> (see k8s-launch-kit-generate).
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).