| name | k8s-launch-kit-discover |
| version | 1.2.4 |
| description | 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. |
| metadata | {"requires":{"skills":["k8s-launch-kit-shared"]}} |
l8k: Cluster Discovery
PREREQUISITE: Read ../k8s-launch-kit-shared/SKILL.md for 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:
- 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 final profile: 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.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).
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 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: or . |
Examples
l8k discover \
--kubeconfig ~/.kube/config \
--save-cluster-config ./cluster-config.yaml
l8k discover --save-cluster-config ./cluster-config.yaml
l8k discover \
--kubeconfig ~/.kube/config \
--keep-namespace \
--save-cluster-config ./cluster-config.yaml
l8k discover --user-config my-config.yaml \
--kubeconfig ~/.kube/config \
--save-cluster-config ./cluster-config.yaml
l8k discover \
--kubeconfig ~/.kube/config \
--fabric infiniband --deployment-type rdma_shared \
--multirail=false \
--save-cluster-config ./cluster-config.yaml
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-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-h100-nvl"
thirdPartyRDMAModules: [nv_peer_mem]
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.
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=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.
- Image-pull access from every worker node to
<networkOperator.repository>/nic-configuration-operator-daemon:<networkOperator.componentVersion>.
Use --image-pull-secrets <name> (or networkOperator.imagePullSecrets
in the config file) for private registries.
- Network Operator does NOT need to be pre-installed.
Discovery also always 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. Do not add GPU counts or resource maps under clusterConfig.
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-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.
-
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).
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