node-feature-discovery: turning node hardware into Kubernetes labels
Node feature discovery for Kubernetes
At a glance
- What is it?
- Node Feature Discovery is a Kubernetes SIG-Node project that inspects each node for CPU instructions, GPUs, network adapters and kernel features, then advertises what it found as node labels, annotations and extended resources so a scheduler can place workloads on matching hardware. It suits a cluster that schedules by capability rather than by node name, and it splits into a worker, a master and a garbage collector.
- Who is it for?
- node-feature-discovery suits a cluster that schedules on hardware capability, such as one that needs to place a workload on nodes with a particular CPU instruction set or a GPU present, and it fits a team already running Kubernetes SIG-Node components.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Three components with three separate jobs
Node Feature Discovery runs three components, and the split is the first thing to understand because it determines what permissions and what failure behaviour you have.
| Component | Runs as | What it does | | --- | --- | --- | | nfd-worker | DaemonSet on every node | Discovers hardware and OS features and publishes them as NodeFeature objects | | nfd-master | Deployment | Reads NodeFeature objects, evaluates rules, writes labels and annotations to Node objects, and maintains NodeFeatureGroup status | | nfd-gc | Deployment | Removes stale NodeFeature and NodeResourceTopology objects when nodes are deleted |
The design separates privilege from authority. The worker needs to read the machine's hardware, which means it runs close to the kernel and the device tree, and it communicates upward through NodeFeature custom resources rather than by patching Node objects directly. The master is the component that holds cluster write permissions, and it does the rule evaluation that decides which discovered features become labels. The garbage collector is the piece people forget, and it exists because a DaemonSet that discovers features on every node will otherwise leave NodeFeature objects behind for nodes that no longer exist.
That last point is what tells you the project is designed for real clusters. A tool that only worked on a static set of nodes would not need a garbage collection component at all, and the fact that nfd-gc also cleans up NodeResourceTopology objects, which belong to a different project, shows the authors have had to handle the interaction with the rest of the topology aware scheduling ecosystem.
Installing with Helm or Kustomize
The quick start requires Kubernetes v1.24 or later, and there are two supported paths. The Helm chart is the shorter one.
helm install -n node-feature-discovery --create-namespace nfd \
oci://registry.k8s.io/nfd/charts/node-feature-discovery --version 0.19.0The Kustomize path applies an overlay straight from the repository.
kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref=v0.19.0"Both commands pin to 0.19.0, and the reason is in the README. Using a release means you read the docs for your version, not this branch, and the master branch reflects work in progress and may not match your deployment. The documentation site lists the available versions, and the compatibility statement is that NFD is compatible with Kubernetes v1.24 and later and that the two most recent NFD minor releases are supported, with a versions reference page carrying the full deprecation policy.
The images are published to registry.k8s.io under the nfd namespace, and the Makefile shows the naming with the version derived from git describe over v tags, plus the chart and image tags kept in step through the same version variable.
Verifying the labels and using them in a nodeSelector
The verification step in the README is a plain get on the namespace followed by a jq query over the node labels.
kubectl -n node-feature-discovery get all
kubectl get no -o json | jq ".items[].metadata.labels"The output shows the standard Kubernetes labels alongside the discovered ones, with feature.node.kubernetes.io/cpu-cpuid.ADX and feature.node.kubernetes.io/cpu-cpuid.AESNI as the examples. The naming pattern is worth noticing, because it is the contract you depend on in a workload. Every discovered feature becomes a label under the feature.node.kubernetes.io/ prefix, with the source named first, so cpu-cpuid.AESNI means the CPUID source reported the AESNI instruction set.
From there a Pod selects on the label as with any other node selector.
nodeSelector:
feature.node.kubernetes.io/cpu-cpuid.AESNI: "true"That is the whole loop: the worker finds a capability, the master turns it into a label, and a scheduler constraint acts on it. The README frames the audience for this as schedulers, operators and controllers that need to place workloads on nodes with the right CPU instructions, GPUs, network adapters or kernel features.
What the Go dependencies reveal about the detection sources
The go.mod is a good map of how the detection works, because each detection source is a library. github.com/klauspost/cpuid/v2 at 2.4.0 is the CPUID reader, which is where the AESNI and ADX labels in the example come from. github.com/jaypipes/ghw at 0.26.0 is the hardware discovery library. The noderesourcetopology-api from the Kubernetes topology aware scheduling working group is the interface to the topology side, and podfingerprint from the same group is the other half of that integration.
The rest is standard Kubernetes machinery. The module is sigs.k8s.io/node-feature-discovery, and the api/nfd submodule is a separate Go module published as sigs.k8s.io/node-feature-discovery/api/nfd, which is the custom resource definition package that other projects depend on without depending on the whole controller. The k8s.io dependencies are pinned to v0.37.1, and the go directive is 1.27.
The rule engine is visible too. Masterminds/semver for version comparison, Masterminds/sprig for the templating functions available to rule expressions, and k8s.io/kubectl and k8s.io/pod-security-admission are present, which indicates the master also carries the ability to inspect or evaluate pod level attributes rather than only node level ones.
For testing, the project uses Ginkgo and Gomega for the end to end suite, testify and goconvey for unit tests, go-cmp for comparisons, and the sigs.k8s.io/e2e-framework for cluster level tests.
The Dockerfile shows the cross build problems this project has hit
The Dockerfile is more interesting than a typical one because of the comments explaining what broke. The builder image must be a Debian family, because the xx-apt-get helper requires apt-get, and the project pins it to golang:1.27-trixie to match the go.mod minimum Go version. The tonistiigi/xx image is pinned by sha256 digest with a comment giving the minimum acceptable version as 1.5.0, for arm/v7 and target variant handling, and it says to track that pin in Renovate or Dependabot alongside the runtime base image pins.
The build runs on the build platform rather than the target, and the comment says this avoids the QEMU translation cache instability that crashed go mod download under emulation, with the crash signatures in the referenced pull request. The cross toolchain packages gcc and libc6-dev are intentionally left unpinned, and the comment explains why, that Debian point releases shift frequently and the build stage is discarded, so pinning would create CI churn for no security gain. The hadolint ignore comment for that rule is there for the same reason.
The runtime image is the minimal one, scratch, by default, with debian:bookworm-slim as the full alternative. That is the right choice for a component that runs on every node, since the image size multiplies by the node count, and it also means the container has no shell, so anything that needs to debug inside a pod needs the full image instead.
The Makefile has the matching knobs, with BUILDER_IMAGE, BASE_IMAGE_FULL and BASE_IMAGE_MINIMAL as overridable variables, plus the documentation site built in a Ruby 3.4 container with the comment explaining that host networking is used because the Jekyll server binds to the same site url as the host.
Where node-feature-discovery is the wrong tool
Three cases argue against it. The first is a cluster that schedules on node pools rather than on hardware differences. The labels only earn their keep if something selects on them, so a uniform cluster gets the full running cost of a DaemonSet on every node, a control plane deployment and a garbage collector, in exchange for labels nobody queries.
The second case is a heterogeneous environment where inventory changes without a node restart. The worker discovers features and publishes them, and the master writes labels, but the question of when the worker re-scans and whether the labels are refreshed after a device is added or removed is not answered in the README, and the fact that the docs are the primary reference for the version you use suggests the behaviour is documented there rather than obvious. A cluster with a fixed set of node types, which is what most clusters have, avoids the problem entirely.
The third case is a platform that already provides this. Some cloud providers and bare metal operators expose hardware information as node labels already, and managed offerings frequently let you request a node with a given accelerator through a different mechanism. Running NFD there duplicates a function and adds a control plane component to maintain.
There is also the version matrix to plan around. The project supports the two most recent minor releases, and the README is explicit that the master branch may not match your deployment, so an installation that tracks master will break on a Kubernetes upgrade in a way a pinned release will not. The compatibility statement of Kubernetes v1.24 and later, with a full deprecation policy in the versions reference, is a reasonable commitment for a SIG-Node project, but it does mean the upgrade is a scheduled piece of work rather than something you inherit for free.
On the state of the work, the last push was on 2026-10-15, the newest release is v0.19.0 from 2026-07-10, and the project is Apache-2.0 licensed with a SECURITY_CONTACTS file, an OWNERS file and a code of conduct. The community channels are a Slack channel for node feature discovery and the SIG-Node mailing list.
Editorial conclusion
node-feature-discovery suits a cluster that schedules on hardware capability, such as one that needs to place a workload on nodes with a particular CPU instruction set or a GPU present, and it fits a team already running Kubernetes SIG-Node components. It does not suit a cluster that schedules on node pools alone, since the labels add value only when something selects on them, and it does not suit a heterogeneous environment where the hardware inventory changes often without a node restart. Before you deploy it, install the release matching your Kubernetes version rather than the master branch, and note that the last push was on 2026-10-15 with the newest release v0.19.0 from 2026-07-10.
Frequently asked questions
What does node-feature-discovery detect and publish?
It detects hardware features and system configuration on each node and advertises them as node labels, annotations and extended resources, so schedulers and controllers can place workloads on nodes with the right CPU instructions, GPUs, network adapters or kernel features.
How do I install node-feature-discovery?
With Helm, using the chart oci://registry.k8s.io/nfd/charts/node-feature-discovery with a version pin, or with Kustomize by applying the deployment overlay for a given tag. Kubernetes v1.24 or later is required, and the readme asks you to use a release rather than the master branch.
What are the three components of node-feature-discovery?
nfd-worker runs as a DaemonSet on every node and publishes discovered features as NodeFeature objects, nfd-master runs as a Deployment and evaluates rules to write labels and annotations onto Node objects, and nfd-gc removes stale NodeFeature and NodeResourceTopology objects when nodes are deleted.
How do I schedule a Pod on a node with a specific CPU feature?
Use a nodeSelector on the feature.node.kubernetes.io label, for example feature.node.kubernetes.io/cpu-cpuid.AESNI set to "true", after verifying with kubectl get no -o json and jq that the label has been applied.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/kubernetes-sigs-node-feature-discovery)Was this useful?