Repository: kubernetes-sigs/node-feature-discovery
Pull request #1845 (2024-08-19 12:11:28)

NFD image compatibility proposal
Proposal to describe image requirements by using NFD labels and OCI artifact.

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/node-feature-discovery/pull/1845#" title="Author self-approved">mfranczy</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign [arangogutierrez](https://github.com/arangogutierrez) for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fnode-feature-discovery).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/node-feature-discovery/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["arangogutierrez"]} -->
netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *kubernetes-sigs-nfd* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 50f6638ff9a14248cca46843c194c239b28536a5 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/kubernetes-sigs-nfd/deploys/66c336721bd8bc0008e51160 |
mfranczy: /cc @ArangoGutierrez 
mfranczy: I am in the process of building nfd client prototype. I reduced the scope with commands. ORAS SDK provides a lot of useful functions so there is no need to introduce much for artifact manipulation (thanks ORAS folks!).
marquiz: Thanks @mfranczy for the proposal. Just thinking aloud below, not proposing anything or having any answers.

It looks like we need to rely on node labels to be able to integrate this with the kubernetes components (scheduler etc). If we want to do without changes to the kubernetes API or extend the kubernetes components with some plugins looking at NFD CRDs (or similar) or something exotic like that.

What we need to think is the node label management on NFD side. We have a bunch of built-in labels (that get populated by just deploying NFD) but a lot of stuff is only visible in NodeFeatures only. For automatizing the creation NodeFeatureRules (a future, step, yes) we need to understand what we get automatically, from the built-in-labels and what we need custom labels for. We could use namespacing e.g. prefix all image compatibility labels with `oci.feature.node.kubernetes.io/` (or `opencontainers.org/` or similar).

Regarding the expressions, would CEL (or parts of it) be useful there?
https://github.com/google/cel-spec
mfranczy: @marquiz thanks for looking into it.

> It looks like we need to rely on node labels to be able to integrate this with the kubernetes components (scheduler etc). If we want to do without changes to the kubernetes API or extend the kubernetes components with some plugins looking at NFD CRDs (or similar) or something exotic like that.

I think looking at NFD CRDs would be a good choice for start.

> What we need to think is the node label management on NFD side. We have a bunch of built-in labels (that get populated by just deploying NFD) but a lot of stuff is only visible in NodeFeatures only. For automatizing the creation NodeFeatureRules (a future, step, yes) we need to understand what we get automatically, from the built-in-labels and what we need custom labels for. We could use namespacing e.g. prefix all image compatibility labels with oci.feature.node.kubernetes.io/ (or opencontainers.org/ or similar).

Right, a few missing pieces I already identified, like discovery of runtime configuration (`/proc/sys`) that could be a built-in source only visible to NodeFeatures. Basically for image compatibility there is a lot of things already covered by the project (like kernel modules, config, version, usb and pci devices etc.). Maybe we don't need to introduce a new namespacing for the image compatibility purpose. From my perspective we could extend the built-in functionality and keep the original NFD labels (or features - if the label is not populated to a node) to describe the compatibility. What do you think?

> Regarding the expressions, would CEL (or parts of it) be useful there?
https://github.com/google/cel-spec

Looks very promising, I think we could use it. That's a good idea.

vsoch: > Considering the danymic situation, extending scheduler with plugin to validate the image compatibility and node features seems to be more viable.

In that context agree - the scheduler would determine compatibility, but then the container runtime doing the pull would do so based on more fine-grained selection for optional performance. It would be scoped to what the scheduler scoped it to (architecture, GPU and devices, etc).
mfranczy: > > Regarding the expressions, would CEL (or parts of it) be useful there?
> > https://github.com/google/cel-spec
> 
> Looks very promising, I think we could use it. That's a good idea.

@marquiz I will try to implement this in the prototype and will inform if there is anything that stops us to use CEL.
mfranczy: @marquiz there is nothing that prevents to add extra fields for CEL expressions in the spec.

However, @marquiz @ArangoGutierrez after looking deeply into NodeFeatureRules, I believe we could just use the rules object (or part of it) to describe image requirements in the artifact. It provides everything what we need to describe image requirements, it already allows to use expressions, it's possible to set a custom hierarchy, it operates directly on features and most important we don't need to create any complex translator from the new spec to NFR for the deployment in k8s cluster.

If you're ok with the idea then I would update the proposal to include NFR rules object into compatibility artifact.
marquiz: > If you're ok with the idea then I would update the proposal to include NFR rules object into compatibility artifact.

In principle, I like this idea. That just means that the Kubernetes needs to be made aware of this stuff which complicates matters in the short term. With this, I also agree with @ChaoyiHuang @vsoch that integrating this in the kubernetes control plane (with new functionality, say a scheduler plugin) is a more viable and maintainable solution for the longer term
mfranczy: @marquiz @ArangoGutierrez I have updated the proposal based on our discussion and consider it complete from my side.

Patch:
diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml
index 43528ed0f..39b9d774b 100644
--- a/.github/workflows/gh-pages.yml
+++ b/.github/workflows/gh-pages.yml
@@ -10,16 +10,12 @@ on:
 concurrency:
   group: gh-pages
 
-permissions:
-  contents: read
-
 jobs:
   update-docs:
     name: Update gh-pages documentation
     runs-on: ubuntu-latest
-    permissions:
-      contents: write
     steps:
+
     - name: Install dependencies
       run: |
         sudo apt-get install -y jq curl
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 94298f2b6..272a4be52 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -6,15 +6,10 @@ on:
 concurrency:
   group: gh-pages
 
-permissions:
-  contents: read
-
 jobs:
   update-helm-repo:
     name: Update gh-pages helm repo index
     runs-on: ubuntu-latest
-    permissions:
-      contents: write
     steps:
     - name: Install Helm
       uses: azure/setup-helm@v4
diff --git a/.gitignore b/.gitignore
index fa9f63d35..c008b2e03 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,3 @@ demo/helper-scripts/*.pdf
 demo/helper-scripts/*.log
 /kustomization.yaml
 coverage.out
-.idea/
diff --git a/Makefile b/Makefile
index 667cdaf27..9e2a21ee9 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@ IMAGE_BUILD_CMD ?= docker build
 IMAGE_BUILD_EXTRA_OPTS ?=
 IMAGE_PUSH_CMD ?= docker push
 CONTAINER_RUN_CMD ?= docker run
-BUILDER_IMAGE ?= golang:1.23-bookworm
+BUILDER_IMAGE ?= golang:1.22-bookworm
 BASE_IMAGE_FULL ?= debian:bookworm-slim
 BASE_IMAGE_MINIMAL ?= scratch
 
diff --git a/README.md b/README.md
index 6496b7669..7d62d860d 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ features and system configuration!
 #### Quick-start – the short-short version
 
 ```bash
-$ kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref=v0.16.5"
+$ kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref=v0.16.4
   namespace/node-feature-discovery created
   customresourcedefinition.apiextensions.k8s.io/nodefeaturerules.nfd.k8s-sigs.io created
   customresourcedefinition.apiextensions.k8s.io/nodefeatures.nfd.k8s-sigs.io created
@@ -38,7 +38,7 @@ $ kubectl -n node-feature-discovery get all
   pod/nfd-worker-nwdp6              1/1     Running   0          18s
 ...
 
-$ kubectl get no -o json | jq ".items[].metadata.labels"
+$ kubectl get no -o json | jq '.items[].metadata.labels'
   {
     "kubernetes.io/arch": "amd64",
     "kubernetes.io/os": "linux",
diff --git a/Tiltfile b/Tiltfile
index e5fe0dded..72eb335b2 100644
--- a/Tiltfile
+++ b/Tiltfile
@@ -2,7 +2,7 @@
 
 BASE_IMAGE_MINIMAL="gcr.io/distroless/base"
 BASE_IMAGE_FULL="debian:bullseye-slim"
-BUILDER_IMAGE="golang:1.23-bookworm"
+BUILDER_IMAGE="golang:1.22-bullseye"
 HOSTMOUNT_PREFIX="/host-"
 IMAGE_TAG_NAME = os.getenv('IMAGE_TAG_NAME', "master")
 IMAGE_REGISTRY = os.getenv('IMAGE_REGISTRY', "gcr.io/k8s-staging-nfd")
diff --git a/cmd/nfd-master/main.go b/cmd/nfd-master/main.go
index 4ba235349..08a35324f 100644
--- a/cmd/nfd-master/main.go
+++ b/cmd/nfd-master/main.go
@@ -33,7 +33,8 @@ import (
 
 const (
 	// ProgramName is the canonical name of this program
-	ProgramName = "nfd-master"
+	ProgramName    = "nfd-master"
+	GrpcHealthPort = 8082
 )
 
 func main() {
@@ -107,6 +108,7 @@ func main() {
 	utils.ConfigureGrpcKlog()
 
 	// Get new NfdMaster instance
+	args.GrpcHealthPort = GrpcHealthPort
 	instance, err := master.NewNfdMaster(master.WithArgs(args))
 	if err != nil {
 		klog.ErrorS(err, "failed to initialize NfdMaster instance")
@@ -147,8 +149,6 @@ func initFlags(flagset *flag.FlagSet) (*master.Args, *master.ConfigOverrideArgs)
 			" DEPRECATED: will be removed in a future release along with the deprecated gRPC API.")
 	flagset.IntVar(&args.MetricsPort, "metrics", 8081,
 		"Port on which to expose metrics.")
-	flagset.IntVar(&args.GrpcHealthPort, "grpc-health", 8082,
-		"Port on which to expose the grpc health endpoint.")
 	flagset.BoolVar(&args.Prune, "prune", false,
 		"Prune all NFD related attributes from all nodes of the cluster and exit.")
 	flagset.BoolVar(&args.VerifyNodeName, "verify-node-name", false,
diff --git a/cmd/nfd-topology-updater/main.go b/cmd/nfd-topology-updater/main.go
index dc6bee50d..ee03415e2 100644
--- a/cmd/nfd-topology-updater/main.go
+++ b/cmd/nfd-topology-updater/main.go
@@ -38,6 +38,7 @@ const (
 	// ProgramName is the canonical name of this program
 	ProgramName       = "nfd-topology-updater"
 	kubeletSecurePort = 10250
+	GrpcHealthPort    = 8082
 )
 
 var DefaultKubeletStateDir = path.Join(string(hostpath.VarDir), "lib", "kubelet")
@@ -56,6 +57,7 @@ func main() {
 	utils.ConfigureGrpcKlog()
 
 	// Get new TopologyUpdater instance
+	args.GrpcHealthPort = GrpcHealthPort
 	instance, err := topology.NewTopologyUpdater(*args, *resourcemonitorArgs)
 	if err != nil {
 		klog.ErrorS(err, "failed to initialize topology updater instance")
@@ -113,8 +115,6 @@ func initFlags(flagset *flag.FlagSet) (*topology.Args, *resourcemonitor.Args) {
 		"Kube config file.")
 	flagset.IntVar(&args.MetricsPort, "metrics", 8081,
 		"Port on which to expose metrics.")
-	flagset.IntVar(&args.GrpcHealthPort, "grpc-health", 8082,
-		"Port on which to expose the grpc health endpoint.")
 	flagset.DurationVar(&resourcemonitorArgs.SleepInterval, "sleep-interval", time.Duration(60)*time.Second,
 		"Time to sleep between CR updates. zero means no CR updates on interval basis. [Default: 60s]")
 	flagset.StringVar(&resourcemonitorArgs.Namespace, "watch-namespace", "*",
diff --git a/cmd/nfd-worker/main.go b/cmd/nfd-worker/main.go
index d6b478103..5c204aea4 100644
--- a/cmd/nfd-worker/main.go
+++ b/cmd/nfd-worker/main.go
@@ -32,7 +32,8 @@ import (
 
 const (
 	// ProgramName is the canonical name of this program
-	ProgramName = "nfd-worker"
+	ProgramName    = "nfd-worker"
+	GrpcHealthPort = 8082
 )
 
 func main() {
@@ -79,6 +80,7 @@ func main() {
 	utils.ConfigureGrpcKlog()
 
 	// Get new NfdWorker instance
+	args.GrpcHealthPort = GrpcHealthPort
 	instance, err := worker.NewNfdWorker(worker.WithArgs(args))
 	if err != nil {
 		klog.ErrorS(err, "failed to initialize NfdWorker instance")
@@ -136,8 +138,6 @@ func initFlags(flagset *flag.FlagSet) (*worker.Args, *worker.ConfigOverrideArgs)
 		"Do not publish feature labels")
 	flagset.IntVar(&args.MetricsPort, "metrics", 8081,
 		"Port on which to expose metrics.")
-	flagset.IntVar(&args.GrpcHealthPort, "grpc-health", 8082,
-		"Port on which to expose the grpc health endpoint.")
 	flagset.StringVar(&args.Options, "options", "",
 		"Specify config options from command line. Config options are specified "+
 			"in the same format as in the config file (i.e. json or yaml). These options")
diff --git a/deployment/components/master-config/kustomization.yaml b/deployment/components/master-config/kustomization.yaml
index 878f95bda..f042ce3d8 100644
--- a/deployment/components/master-config/kustomization.yaml
+++ b/deployment/components/master-config/kustomization.yaml
@@ -1,6 +1,9 @@
 apiVersion: kustomize.config.k8s.io/v1alpha1
 kind: Component
 
+generatorOptions:
+  disableNameSuffixHash: true
+
 configMapGenerator:
 - files:
   - nfd-master.conf=nfd-master.conf.example
diff --git a/deployment/components/topology-updater-config/kustomization.yaml b/deployment/components/topology-updater-config/kustomization.yaml
index 5500539bb..73644ab49 100644
--- a/deployment/components/topology-updater-config/kustomization.yaml
+++ b/deployment/components/topology-updater-config/kustomization.yaml
@@ -1,6 +1,9 @@
 apiVersion: kustomize.config.k8s.io/v1alpha1
 kind: Component
 
+generatorOptions:
+  disableNameSuffixHash: true
+
 configMapGenerator:
 - files:
   - nfd-topology-updater.conf=nfd-topology-updater.conf.example
diff --git a/deployment/components/worker-config/kustomization.yaml b/deployment/components/worker-config/kustomization.yaml
index b124379e9..348c3b616 100644
--- a/deployment/components/worker-config/kustomization.yaml
+++ b/deployment/components/worker-config/kustomization.yaml
@@ -1,6 +1,9 @@
 apiVersion: kustomize.config.k8s.io/v1alpha1
 kind: Component
 
+generatorOptions:
+  disableNameSuffixHash: true
+
 configMapGenerator:
 - files:
   - nfd-worker.conf=nfd-worker.conf.example
diff --git a/deployment/helm/node-feature-discovery/templates/master.yaml b/deployment/helm/node-feature-discovery/templates/master.yaml
index c47086cc1..1f16f6c7d 100644
--- a/deployment/helm/node-feature-discovery/templates/master.yaml
+++ b/deployment/helm/node-feature-discovery/templates/master.yaml
@@ -23,11 +23,10 @@ spec:
       labels:
         {{- include "node-feature-discovery.selectorLabels" . | nindent 8 }}
         role: master
+      {{- with .Values.master.annotations }}
       annotations:
-        checksum/config: {{ include (print $.Template.BasePath "/nfd-master-conf.yaml") . | sha256sum }}
-        {{- with .Values.master.annotations }}
         {{- toYaml . | nindent 8 }}
-        {{- end }}
+      {{- end }}
     spec:
     {{- with .Values.priorityClassName }}
       priorityClassName: {{ . }}
@@ -40,7 +39,6 @@ spec:
       enableServiceLinks: false
       securityContext:
         {{- toYaml .Values.master.podSecurityContext | nindent 8 }}
-      hostNetwork: {{ .Values.master.hostNetwork }}
       containers:
         - name: master
           securityContext:
@@ -56,16 +54,11 @@ spec:
             name: grpc
           - containerPort: {{ .Values.master.metricsPort | default "8081" }}
             name: metrics
-          - containerPort: {{ .Values.master.healthPort  | default "8082" }}
-            name: health
           env:
           - name: NODE_NAME
             valueFrom:
               fieldRef:
                 fieldPath: spec.nodeName
-        {{- with .Values.master.extraEnvs }}
-          {{- toYaml . | nindent 8 }}
-        {{- end}}
           command:
             - "nfd-master"
           resources:
@@ -116,8 +109,7 @@ spec:
             - "-feature-gates={{ $key }}={{ $value }}"
             {{- end }}
             - "-metrics={{ .Values.master.metricsPort  | default "8081" }}"
-            - "-grpc-health={{ .Values.master.healthPort  | default "8082" }}"
-            {{- with .Values.master.extraArgs }}
+            {{- with .Values.master.args }}
             {{- toYaml . | nindent 12 }}
             {{- end }}
           volumeMounts:
diff --git a/deployment/helm/node-feature-discovery/templates/nfd-gc.yaml b/deployment/helm/node-feature-discovery/templates/nfd-gc.yaml
index f5f7575c7..0d9c141f2 100644
--- a/deployment/helm/node-feature-discovery/templates/nfd-gc.yaml
+++ b/deployment/helm/node-feature-discovery/templates/nfd-gc.yaml
@@ -39,7 +39,6 @@ spec:
     {{- end }}
       securityContext:
         {{- toYaml .Values.gc.podSecurityContext | nindent 8 }}
-      hostNetwork: {{ .Values.gc.hostNetwork }}
       containers:
       - name: gc
         image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
@@ -49,16 +48,13 @@ spec:
           valueFrom:
             fieldRef:
               fieldPath: spec.nodeName
-      {{- with .Values.gc.extraEnvs }}
-        {{- toYaml . | nindent 8 }}
-      {{- end}}
         command:
           - "nfd-gc"
         args:
           {{- if .Values.gc.interval | empty | not }}
           - "-gc-interval={{ .Values.gc.interval }}"
           {{- end }}
-          {{- with .Values.gc.extraArgs }}
+          {{- with .Values.gc.args }}
           {{- toYaml . | nindent 10 }}
           {{- end }}
         resources:
diff --git a/deployment/helm/node-feature-discovery/templates/topologyupdater.yaml b/deployment/helm/node-feature-discovery/templates/topologyupdater.yaml
index 222e72980..a94aac870 100644
--- a/deployment/helm/node-feature-discovery/templates/topologyupdater.yaml
+++ b/deployment/helm/node-feature-discovery/templates/topologyupdater.yaml
@@ -22,11 +22,10 @@ spec:
       labels:
         {{- include "node-feature-discovery.selectorLabels" . | nindent 8 }}
         role: topology-updater
+      {{- with .Values.topologyUpdater.annotations }}
       annotations:
-        checksum/config: {{ include (print $.Template.BasePath "/nfd-topologyupdater-conf.yaml") . | sha256sum }}
-        {{- with .Values.topologyUpdater.annotations }}
         {{- toYaml . | nindent 8 }}
-        {{- end }}
+      {{- end }}
     spec:
       serviceAccountName: {{ include "node-feature-discovery.topologyUpdater.serviceAccountName" . }}
       dnsPolicy: ClusterFirstWithHostNet
@@ -39,7 +38,6 @@ spec:
     {{- end }}
       securityContext:
         {{- toYaml .Values.topologyUpdater.podSecurityContext | nindent 8 }}
-      hostNetwork: {{ .Values.topologyUpdater.hostNetwork }}
       containers:
       - name: topology-updater
         image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
@@ -57,9 +55,6 @@ spec:
           valueFrom:
             fieldRef:
               fieldPath: status.hostIP
-      {{- with .Values.topologyUpdater.extraEnvs }}
-        {{- toYaml . | nindent 8 }}
-      {{- end}}
         command:
           - "nfd-topology-updater"
         args:
@@ -89,16 +84,13 @@ spec:
           # Disable kubelet state tracking by giving an empty path
           - "-kubelet-state-dir="
           {{- end }}
-          - "-metrics={{ .Values.topologyUpdater.metricsPort | default "8081"}}"
-          - "-grpc-health={{ .Values.topologyUpdater.healthPort  | default "8082" }}"
-          {{- with .Values.topologyUpdater.extraArgs }}
+          - -metrics={{ .Values.topologyUpdater.metricsPort | default "8081"}}
+          {{- with .Values.topologyUpdater.args }}
           {{- toYaml . | nindent 10 }}
           {{- end }}
         ports:
-          - containerPort: {{ .Values.topologyUpdater.metricsPort | default "8081"}}
-            name: metrics
-          - containerPort: {{ .Values.topologyUpdater.healthPort  | default "8082" }}
-            name: health
+          - name: metrics
+            containerPort: {{ .Values.topologyUpdater.metricsPort | default "8081"}}
         volumeMounts:
         {{- if .Values.topologyUpdater.kubeletConfigPath | empty | not }}
         - name: kubelet-config
diff --git a/deployment/helm/node-feature-discovery/templates/worker.yaml b/deployment/helm/node-feature-discovery/templates/worker.yaml
index 028b481ad..2aae58474 100644
--- a/deployment/helm/node-feature-discovery/templates/worker.yaml
+++ b/deployment/helm/node-feature-discovery/templates/worker.yaml
@@ -22,11 +22,10 @@ spec:
       labels:
         {{- include "node-feature-discovery.selectorLabels" . | nindent 8 }}
         role: worker
+      {{- with .Values.worker.annotations }}
       annotations:
-        checksum/config: {{ include (print $.Template.BasePath "/nfd-worker-conf.yaml") . | sha256sum }}
-        {{- with .Values.worker.annotations }}
         {{- toYaml . | nindent 8 }}
-        {{- end }}
+      {{- end }}
     spec:
       dnsPolicy: ClusterFirstWithHostNet
     {{- with .Values.priorityClassName }}
@@ -39,7 +38,6 @@ spec:
       serviceAccountName: {{ include "node-feature-discovery.worker.serviceAccountName" . }}
       securityContext:
         {{- toYaml .Values.worker.podSecurityContext | nindent 8 }}
-      hostNetwork: {{ .Values.worker.hostNetwork }}
       containers:
       - name: worker
         securityContext:
@@ -63,36 +61,30 @@ spec:
           valueFrom:
             fieldRef:
               fieldPath: metadata.uid
-      {{- with .Values.worker.extraEnvs }}
-        {{- toYaml . | nindent 8 }}
-      {{- end}}
         resources:
         {{- toYaml .Values.worker.resources | nindent 12 }}
         command:
         - "nfd-worker"
         args:
-        {{- if not .Values.featureGates.NodeFeatureAPI }}
+{{- if not .Values.featureGates.NodeFeatureAPI }}
         - "-server={{ include "node-feature-discovery.fullname" . }}-master:{{ .Values.master.service.port }}"
-        {{- end }}
-        {{- if .Values.tls.enable }}
+{{- end }}
+{{- if .Values.tls.enable }}
         - "-ca-file=/etc/kubernetes/node-feature-discovery/certs/ca.crt"
         - "-key-file=/etc/kubernetes/node-feature-discovery/certs/tls.key"
         - "-cert-file=/etc/kubernetes/node-feature-discovery/certs/tls.crt"
-        {{- end }}
-        # Go over featureGate and add the feature-gate flag
-        {{- range $key, $value := .Values.featureGates }}
+{{- end }}
+# Go over featureGate and add the feature-gate flag
+{{- range $key, $value := .Values.featureGates }}
         - "-feature-gates={{ $key }}={{ $value }}"
-        {{- end }}
+{{- end }}
         - "-metrics={{ .Values.worker.metricsPort | default "8081"}}"
-        - "-grpc-health={{ .Values.worker.healthPort  | default "8082" }}"
-        {{- with .Values.gc.extraArgs }}
+        {{- with .Values.gc.args }}
         {{- toYaml . | nindent 8 }}
         {{- end }}
         ports:
-          - containerPort: {{ .Values.worker.metricsPort | default "8081"}}
-            name: metrics
-          - containerPort: {{ .Values.worker.healthPort  | default "8082" }}
-            name: health
+          - name: metrics
+            containerPort: {{ .Values.worker.metricsPort | default "8081"}}
         volumeMounts:
         - name: host-boot
           mountPath: "/host-boot"
@@ -126,11 +118,11 @@ spec:
         - name: nfd-worker-conf
           mountPath: "/etc/kubernetes/node-feature-discovery"
           readOnly: true
-        {{- if .Values.tls.enable }}
+{{- if .Values.tls.enable }}
         - name: nfd-worker-cert
           mountPath: "/etc/kubernetes/node-feature-discovery/certs"
           readOnly: true
-        {{- end }}
+{{- end }}
       volumes:
         - name: host-boot
           hostPath:
@@ -167,12 +159,12 @@ spec:
             items:
               - key: nfd-worker.conf
                 path: nfd-worker.conf
-        {{- if .Values.tls.enable }}
+{{- if .Values.tls.enable }}
         - name: nfd-worker-cert
           secret:
             secretName: nfd-worker-cert
-        {{- end }}
-      {{- with .Values.worker.nodeSelector }}
+{{- end }}
+    {{- with .Values.worker.nodeSelector }}
       nodeSelector:
         {{- toYaml . | nindent 8 }}
       {{- end }}
diff --git a/deployment/helm/node-feature-discovery/values.yaml b/deployment/helm/node-feature-discovery/values.yaml
index 37683b97c..524421a05 100644
--- a/deployment/helm/node-feature-discovery/values.yaml
+++ b/deployment/helm/node-feature-discovery/values.yaml
@@ -18,9 +18,7 @@ priorityClassName: ""
 
 master:
   enable: true
-  extraArgs: []
-  extraEnvs: []
-  hostNetwork: false
+  args: []
   config: ### <NFD-MASTER-CONF-START-DO-NOT-REMOVE>
     # noPublish: false
     # autoDefaultNs: true
@@ -58,7 +56,6 @@ master:
   # be removed with it in a future release
   port: 8080
   metricsPort: 8081
-  healthPort: 8082
   instance:
   featureApi:
   resyncPeriod:
@@ -159,9 +156,7 @@ master:
 
 worker:
   enable: true
-  extraArgs: []
-  extraEnvs: []
-  hostNetwork: false
+  args: []
   config: ### <NFD-WORKER-CONF-START-DO-NOT-REMOVE>
     #core:
     #  labelWhiteList:
@@ -407,7 +402,6 @@ worker:
 ### <NFD-WORKER-CONF-END-DO-NOT-REMOVE>
 
   metricsPort: 8081
-  healthPort: 8082
   daemonsetAnnotations: {}
   podSecurityContext: {}
     # fsGroup: 2000
@@ -482,10 +476,8 @@ topologyUpdater:
 ### <NFD-TOPOLOGY-UPDATER-CONF-END-DO-NOT-REMOVE>
 
   enable: false
+  args: []
   createCRDs: false
-  extraArgs: []
-  extraEnvs: []
-  hostNetwork: false
 
   serviceAccount:
     create: true
@@ -499,7 +491,6 @@ topologyUpdater:
     create: true
 
   metricsPort: 8081
-  healthPort: 8082
   kubeletConfigPath:
   kubeletPodResourcesSockPath:
   updateInterval: 60s
@@ -543,9 +534,7 @@ topologyUpdater:
 
 gc:
   enable: true
-  extraArgs: []
-  extraEnvs: []
-  hostNetwork: false
+  args: []
   replicaCount: 1
 
   serviceAccount:
diff --git a/docs/deployment/helm.md b/docs/deployment/helm.md
index 9ac221f16..f773b12ba 100644
--- a/docs/deployment/helm.md
+++ b/docs/deployment/helm.md
@@ -177,10 +177,8 @@ API's you need to install the prometheus operator in your cluster.
 | ----------------------------------- | ------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
 | `master.*`                          | dict    |                                  | NFD master deployment configuration                                                                                                                                                                    |
 | `master.enable`                     | bool    | true                             | Specifies whether nfd-master should be deployed                                                                                                                                                        |
-| `master.hostNetwork`                | bool    | false                            | Specifies whether to enable or disable running the container in the host's network namespace                                                                                                           |
 | `master.port`                       | integer |                                  | Specifies the TCP port that nfd-master listens for incoming requests. **NOTE**: this parameter is related to the deprecated gRPC API and will be removed with it in a future release                   |
 | `master.metricsPort`                | integer | 8081                             | Port on which to expose metrics from components to prometheus operator                                                                                                                                 |
-| `master.healthPort`                 | integer | 8082                             | Port on which to expose the grpc health endpoint                                                                                                                                                       |
 | `master.instance`                   | string  |                                  | Instance name. Used to separate annotation namespaces for multiple parallel deployments                                                                                                                |
 | `master.resyncPeriod`               | string  |                                  | NFD API controller resync period.                                                                                                                                                                      |
 | `master.extraLabelNs`               | array   | []                               | List of allowed extra label namespaces                                                                                                                                                                 |
@@ -205,8 +203,7 @@ API's you need to install the prometheus operator in your cluster.
 | `master.deploymentAnnotations`      | dict    | {}                               | NFD master deployment [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)                                                                                    |
 | `master.nfdApiParallelism`          | integer | 10                               | Specifies the maximum number of concurrent node updates.                                                                                                                                               |
 | `master.config`                     | dict    |                                  | NFD master [configuration](../reference/master-configuration-reference)                                                                                                                                |
-| `master.extraArgs`                  | array   | []                               | Additional [command line arguments](../reference/master-commandline-reference.md) to pass to nfd-master                                                                                                |
-| `master.extraEnvs`                  | array   | []                               | Additional environment variables to pass to nfd-master                                                                                                                                                 |
+| `master.args`                       | array   | []                               | Additional [command line arguments](../reference/master-commandline-reference.md) to pass to nfd-master                                                                                                |
 | `master.revisionHistoryLimit`       | integer |                                  | Specify how many old ReplicaSets for this Deployment you want to retain. [revisionHistoryLimit](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#revision-history-limit)          |
 | `master.livenessProbe`              | dict    | {"grpc":{"port":8082},"initialDelaySeconds":10}                       | NFD master pod [liveness probe](https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/#liveness-probe)  |
 | `master.readinessProbe`             | dict    | {"grpc":{"port":8082},"initialDelaySeconds":5,"failureThreshold": 10} | NFD master pod [readiness probe](https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/#readiness-probe)|
@@ -217,9 +214,7 @@ API's you need to install the prometheus operator in your cluster.
 | ----------------------------------- | ------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | `worker.*`                          | dict   |                         | NFD worker daemonset configuration                                                                                                                                                                   |
 | `worker.enable`                     | bool   | true                    | Specifies whether nfd-worker should be deployed                                                                                                                                                      |
-| `worker.hostNetwork`                | bool   | false                   | Specifies whether to enable or disable running the container in the host's network namespace                                                                                                         |
-| `worker.metricsPort`                | int    | 8081                    | Port on which to expose metrics from components to prometheus operator                                                                                                                               |
-| `worker.healthPort`                 | int    | 8082                    | Port on which to expose the grpc health endpoint                                                                                                                                                     |
+| `worker.metricsPort*`               | int    | 8081                    | Port on which to expose metrics from components to prometheus operator                                                                                                                               |
 | `worker.config`                     | dict   |                         | NFD worker [configuration](../reference/worker-configuration-reference)                                                                                                                              |
 | `worker.podSecurityContext`         | dict   | {}                      | [PodSecurityContext](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod) holds pod-level security attributes and common container settins |
 | `worker.securityContext`            | dict   | {}                      | Container [security settings](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container)                                                   |
@@ -235,8 +230,7 @@ API's you need to install the prometheus operator in your cluster.
 | `worker.priorityClassName`          | string |                         | NFD worker pod [priority class](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/)                                                                                    |
 | `worker.annotations`                | dict   | {}                      | NFD worker pod [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)                                                                                         |
 | `worker.daemonsetAnnotations`       | dict   | {}                      | NFD worker daemonset [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)                                                                                   |
-| `worker.extraArgs`                  | array  | []                      | Additional [command line arguments](../reference/worker-commandline-reference.md) to pass to nfd-worker                                                                                              |
-| `worker.extraEnvs`                  | array  | []                      | Additional environment variables to pass to nfd-worker                                                                                                                                               |
+| `worker.args`                       | array  | []                      | Additional [command line arguments](../reference/worker-commandline-reference.md) to pass to nfd-worker                                                                                              |
 | `worker.revisionHistoryLimit`       | integer |                        | Specify how many old ControllerRevisions for this DaemonSet you want to retain. [revisionHistoryLimit](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/daemon-set-v1/ #DaemonSetSpec)          |
 | `worker.livenessProbe`              | dict    | {"grpc":{"port":8082},"initialDelaySeconds":10}                       | NFD worker pod [liveness probe](https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/#liveness-probe)  |
 | `worker.readinessProbe`             | dict    | {"grpc":{"port":8082},"initialDelaySeconds":5,"failureThreshold": 10} | NFD worker pod [readiness probe](https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/#readiness-probe)|
@@ -247,14 +241,12 @@ API's you need to install the prometheus operator in your cluster.
 | --------------------------------------------- | ------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
 | `topologyUpdater.*`                           | dict    |                          | NFD Topology Updater configuration                                                                                                                                                               |
 | `topologyUpdater.enable`                      | bool    | false                    | Specifies whether the NFD Topology Updater should be created                                                                                                                                     |
-| `topologyUpdater.hostNetwork`                 | bool    | false                    | Specifies whether to enable or disable running the container in the host's network namespace                                                                                                     |
 | `topologyUpdater.createCRDs`                  | bool    | false                    | Specifies whether the NFD Topology Updater CRDs should be created                                                                                                                                |
 | `topologyUpdater.serviceAccount.create`       | bool    | true                     | Specifies whether the service account for topology updater should be created                                                                                                                     |
 | `topologyUpdater.serviceAccount.annotations`  | dict    | {}                       | Annotations to add to the service account for topology updater                                                                                                                                   |
 | `topologyUpdater.serviceAccount.name`         | string  |                          | The name of the service account for topology updater to use. If not set and create is true, a name is generated using the fullname template and `-topology-updater` suffix                       |
 | `topologyUpdater.rbac.create`                 | bool    | true                     | Specifies whether to create [RBAC][rbac] configuration for topology updater                                                                                                                      |
 | `topologyUpdater.metricsPort`                 | integer | 8081                     | Port on which to expose prometheus metrics                                                                                                                                                       |
-| `topologyUpdater.healthPort`                  | integer | 8082                     | Port on which to expose the grpc health endpoint                                                                                                                                                 |
 | `topologyUpdater.kubeletConfigPath`           | string  | ""                       | Specifies the kubelet config host path                                                                                                                                                           |
 | `topologyUpdater.kubeletPodResourcesSockPath` | string  | ""                       | Specifies the kubelet sock path to read pod resources                                                                                                                                            |
 | `topologyUpdater.updateInterval`              | string  | 60s                      | Time to sleep between CR updates. Non-positive value implies no CR update.                                                                                                                       |
@@ -271,8 +263,7 @@ API's you need to install the prometheus operator in your cluster.
 | `topologyUpdater.config`                      | dict    |                          | [configuration](../reference/topology-updater-configuration-reference)                                                                                                                           |
 | `topologyUpdater.podSetFingerprint`           | bool    | true                     | Enables compute and report of pod fingerprint in NRT objects.                                                                                                                                    |
 | `topologyUpdater.kubeletStateDir`             | string  | /var/lib/kubelet         | Specifies kubelet state directory path for watching state and checkpoint files. Empty value disables kubelet state tracking.                                                                     |
-| `topologyUpdater.extraArgs`                   | array   | []                       | Additional [command line arguments](../reference/topology-updater-commandline-reference.md) to pass to nfd-topology-updater                                                                      |
-| `topologyUpdater.extraEnvs`                   | array   | []                       | Additional environment variables to pass to nfd-topology-updater                                                                                                                                 |
+| `topologyUpdater.args`                        | array   | []                       | Additional [command line arguments](../reference/topology-updater-commandline-reference.md) to pass to nfd-topology-updater                                                                      |
 | `topologyUpdater.revisionHistoryLimit`        | integer |                          | Specify how many old ControllerRevisions for this DaemonSet you want to retain. [revisionHistoryLimit](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/daemon-set-v1/#DaemonSetSpec)          |
 | `topologyUpdater.livenessProbe`               | dict    | {"grpc":{"port":8082},"initialDelaySeconds":10}                       | Topology updater pod [liveness probe](https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/#liveness-probe)  |
 | `topologyUpdater.readinessProbe`              | dict    | {"grpc":{"port":8082},"initialDelaySeconds":5,"failureThreshold": 10} | Topology updater pod [readiness probe](https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/#readiness-probe)|
@@ -283,7 +274,6 @@ API's you need to install the prometheus operator in your cluster.
 | ------------------------------------- | ------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | `gc.*`                                | dict    |                           | NFD Garbage Collector configuration                                                                                                                                                                     |
 | `gc.enable`                           | bool    | true                      | Specifies whether the NFD Garbage Collector should be created                                                                                                                                           |
-| `gc.hostNetwork`                      | bool    | false                     | Specifies whether to enable or disable running the container in the host's network namespace                                                                                                            |
 | `gc.serviceAccount.create`            | bool    | true                      | Specifies whether the service account for garbage collector should be created                                                                                                                           |
 | `gc.serviceAccount.annotations`       | dict    | {}                        | Annotations to add to the service account for garbage collector                                                                                                                                         |
 | `gc.serviceAccount.name`              | string  |                           | The name of the service account for garbage collector to use. If not set and create is true, a name is generated using the fullname template and `-gc` suffix                                           |
@@ -298,8 +288,7 @@ API's you need to install the prometheus operator in your cluster.
 | `gc.annotations`                      | dict    | {}                        | Garbage collector pod [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)                                                                                     |
 | `gc.deploymentAnnotations`            | dict    | {}                        | Garbage collector deployment [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)                                                                              |
 | `gc.affinity`                         | dict    | {}                        | Garbage collector pod [affinity](https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/)                                                                       |
-| `gc.extraArgs`                        | array   | []                        | Additional [command line arguments](../reference/gc-commandline-reference.md) to pass to nfd-gc                                                                                                         |
-| `gc.extraEnvs`                        | array   | []                        | Additional environment variables to pass to nfd-gc                                                                                                                                                      |
+| `gc.args`                             | array   | []                        | Additional [command line arguments](../reference/gc-commandline-reference.md) to pass to nfd-gc                                                                                                         |
 | `gc.revisionHistoryLimit`             | integer |                           | Specify how many old ReplicaSets for this Deployment you want to retain. [revisionHistoryLimit](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#revision-history-limit)           |
 
 <!-- Links -->
diff --git a/docs/deployment/kustomize.md b/docs/deployment/kustomize.md
index c7b1ca939..18d03ec77 100644
--- a/docs/deployment/kustomize.md
+++ b/docs/deployment/kustomize.md
@@ -26,7 +26,7 @@ later is required.
 The kustomize overlays provided in the repo can be used directly:
 
 ```bash
-kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref={{ site.release }}"
+kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref={{ site.release }}
 ```
 
 This will required RBAC rules and deploy nfd-master (as a deployment) and
@@ -77,7 +77,7 @@ The `default-job` overlay may be used to achieve this:
 
 ```bash
 NUM_NODES=$(kubectl get no -o jsonpath='{.items[*].metadata.name}' | wc -w)
-kubectl kustomize "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default-job?ref={{ site.release }}" | \
+kubectl kustomize https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default-job?ref={{ site.release }} | \
     sed s"/NUM_NODES/$NUM_NODES/" | \
     kubectl apply -f -
 ```
@@ -94,7 +94,7 @@ deployed as separate pods. The `master-worker-topologyupdater` overlay may be
 used to achieve this:
 
 ```bash
-kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/master-worker-topologyupdater?ref={{ site.release }}"
+kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/master-worker-topologyupdater?ref={{ site.release }}
 
 ```
 
@@ -104,7 +104,7 @@ To deploy just nfd-topology-updater (without nfd-master and nfd-worker)
 use the `topologyupdater` overlay:
 
 ```bash
-kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/topologyupdater?ref={{ site.release }}"
+kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/topologyupdater?ref={{ site.release }}
 
 ```
 
@@ -114,8 +114,8 @@ are deployed as separate pods;
 
 ```bash
 
-kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref={{ site.release }}"
-kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/topologyupdater?ref={{ site.release }}"
+kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref={{ site.release }}
+kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/topologyupdater?ref={{ site.release }}
 
 ```
 
@@ -126,8 +126,8 @@ to scrape metrics from node-feature-discovery,
 run the following command:
 
 ```bash
-kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref={{ site.release }}"
-kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/prometheus?ref={{ site.release }}"
+kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref={{ site.release }}
+kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/prometheus?ref={{ site.release }}
 ```
 
 ## Uninstallation
diff --git a/docs/deployment/uninstallation.md b/docs/deployment/uninstallation.md
index b88d121dd..fff0b0381 100644
--- a/docs/deployment/uninstallation.md
+++ b/docs/deployment/uninstallation.md
@@ -24,9 +24,9 @@ nfd-related node labels, annotations, extended resources and taints from the
 cluster.
 
 ```bash
-kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/prune?ref={{ site.release }}"
+kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/prune?ref={{ site.release }}
 kubectl -n node-feature-discovery wait job.batch/nfd-master --for=condition=complete && \
-    kubectl delete -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/prune?ref={{ site.release }}"
+    kubectl delete -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/prune?ref={{ site.release }}
 ```
 
 > **NOTE:** You must run prune before removing the RBAC rules (serviceaccount,
diff --git a/docs/get-started/quick-start.md b/docs/get-started/quick-start.md
index 02c96fc10..7a6aa5390 100644
--- a/docs/get-started/quick-start.md
+++ b/docs/get-started/quick-start.md
@@ -14,7 +14,7 @@ Deploy with kustomize -- creates a new namespace, service and required RBAC
 rules and deploys nfd-master and nfd-worker daemons.
 
 ```bash
-kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref={{ site.release }}"
+kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref={{ site.release }}
 ```
 
 ## Verify
@@ -34,7 +34,7 @@ deployment.apps/nfd-master   1/1     1            1           17s
 Check that NFD feature labels have been created
 
 ```bash
-$ kubectl get no -o json | jq ".items[].metadata.labels"
+$ kubectl get no -o json | jq '.items[].metadata.labels'
 {
   "kubernetes.io/arch": "amd64",
   "kubernetes.io/os": "linux",
@@ -82,7 +82,7 @@ To deploy nfd-topology-updater use the `topologyupdater` kustomize
 overlay.
 
 ```bash
-kubectl apply -k "https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/topologyupdater?ref={{ site.release }}"
+kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/topologyupdater?ref={{ site.release }}
 ```
 
 ### Verify nfd-topology-updater
diff --git a/docs/reference/worker-configuration-reference.md b/docs/reference/worker-configuration-reference.md
index 709f2eb35..cf04b3ac7 100644
--- a/docs/reference/worker-configuration-reference.md
+++ b/docs/reference/worker-configuration-reference.md
@@ -151,7 +151,8 @@ core:
 
 ### core.klog
 
-The following options specify the logger configuration.
+The following options specify the logger configuration. Most of which can be
+dynamically adjusted at run-time.
 
 > **NOTE:** The logger options can also be specified via command line flags
 > which take precedence over any corresponding config file options.
@@ -162,30 +163,40 @@ If true, adds the file directory to the header of the log messages.
 
 Default: `false`
 
+Run-time configurable: yes
+
 #### core.klog.alsologtostderr
 
 Log to standard error as well as files.
 
 Default: `false`
 
+Run-time configurable: yes
+
 #### core.klog.logBacktraceAt
 
 When logging hits line file:N, emit a stack trace.
 
 Default: *empty*
 
+Run-time configurable: yes
+
 #### core.klog.logDir
 
 If non-empty, write log files in this directory.
 
 Default: *empty*
 
+Run-time configurable: no
+
 #### core.klog.logFile
 
 If non-empty, use this log file.
 
 Default: *empty*
 
+Run-time configurable: no
+
 #### core.klog.logFileMaxSize
 
 Defines the maximum size a log file can grow to. Unit is megabytes. If the
@@ -193,40 +204,54 @@ value is 0, the maximum file size is unlimited.
 
 Default: `1800`
 
+Run-time configurable: no
+
 #### core.klog.logtostderr
 
 Log to standard error instead of files
 
 Default: `true`
 
+Run-time configurable: yes
+
 #### core.klog.skipHeaders
 
 If true, avoid header prefixes in the log messages.
 
 Default: `false`
 
+Run-time configurable: yes
+
 #### core.klog.skipLogHeaders
 
 If true, avoid headers when opening log files.
 
 Default: `false`
 
+Run-time configurable: no
+
 #### core.klog.stderrthreshold
 
 Logs at or above this threshold go to stderr (default 2)
 
+Run-time configurable: yes
+
 #### core.klog.v
 
 Number for the log level verbosity.
 
 Default: `0`
 
+Run-time configurable: yes
+
 #### core.klog.vmodule
 
 Comma-separated list of `pattern=N` settings for file-filtered logging.
 
 Default: *empty*
 
+Run-time configurable: yes
+
 ## sources
 
 The `sources` section contains feature source specific configuration parameters.
diff --git a/docs/usage/customization-guide.md b/docs/usage/customization-guide.md
index 09094d838..d1fdc50a6 100644
--- a/docs/usage/customization-guide.md
+++ b/docs/usage/customization-guide.md
@@ -1027,7 +1027,6 @@ The following features are available for matching:
 |                  |              | **`<parameter>`** | string | One parameter from `/etc/os-release` |
 | **`system.dmiid`** | attribute |       |            | DMI identification data from `/sys/devices/virtual/dmi/id/` |
 |                  |              | **`sys_vendor`** | string | Vendor name from `/sys/devices/virtual/dmi/id/sys_vendor` |
-|                  |              | **`product_name`** | string | Product name from `/sys/devices/virtual/dmi/id/product_name` |
 | **`system.name`** | attribute   |          |            | System name information |
 |                  |              | **`nodename`** | string | Name of the kubernetes node object |
 | **`usb.device`** | instance     |          |            | USB devices present in the system |
diff --git a/docs/usage/nfd-master.md b/docs/usage/nfd-master.md
index 2c4ba201d..839071678 100644
--- a/docs/usage/nfd-master.md
+++ b/docs/usage/nfd-master.md
@@ -32,28 +32,29 @@ received from nfd-worker instances through
 
 ## Master configuration
 
-NFD-Master supports configuration through a configuration file. The
+NFD-Master supports dynamic configuration through a configuration file. The
 default location is `/etc/kubernetes/node-feature-discovery/nfd-master.conf`,
 but, this can be changed by specifying the`-config` command line flag.
+Configuration file is re-read whenever it is modified which makes run-time
+re-configuration of nfd-master straightforward.
 
 Master configuration file is read inside the container, and thus, Volumes and
 VolumeMounts are needed to make your configuration available for NFD. The
 preferred method is to use a ConfigMap which provides easy deployment and
 re-configurability.
 
-The provided deployment methods (Helm and Kustomize) create an empty configmap
-and mount it inside the nfd-master containers.
+The provided nfd-master deployment templates create an empty configmap and
+mount it inside the nfd-master containers. In kustomize deployments,
+configuration can be edited with:
+
+```bash
+kubectl -n ${NFD_NS} edit configmap nfd-master-conf
+```
 
 In Helm deployments,
 [Master pod parameter](../deployment/helm.md#master-pod-parameters)
 `master.config` can be used to edit the respective configuration.
 
-In Kustomize deployments, modify the `nfd-master-conf` ConfigMap with a custom
-overlay.
-
-> **NOTE:** dynamic run-time reconfiguration was dropped in NFD v0.17.
-> Re-configuration is handled by pod restarts.
-
 See
 [nfd-master configuration file reference](../reference/master-configuration-reference.md)
 for more details.
diff --git a/docs/usage/nfd-topology-updater.md b/docs/usage/nfd-topology-updater.md
index 4674ea61f..8fb14f587 100644
--- a/docs/usage/nfd-topology-updater.md
+++ b/docs/usage/nfd-topology-updater.md
@@ -62,22 +62,27 @@ NFD-Topology-Updater supports configuration through a configuration file. The
 default location is `/etc/kubernetes/node-feature-discovery/topology-updater.conf`,
 but, this can be changed by specifying the`-config` command line flag.
 
+> **NOTE:** unlike nfd-worker, dynamic configuration updates are not supported.
+
 Topology-Updater configuration file is read inside the container,
 and thus, Volumes and VolumeMounts are needed
 to make your configuration available for NFD.
 The preferred method is to use a ConfigMap
 which provides easy deployment and re-configurability.
 
-The provided deployment templates create an empty configmap
+The provided nfd-topology-updater deployment templates
+create an empty configmap
 and mount it inside the nfd-topology-updater containers.
+In kustomize deployments, configuration can be edited with:
+
+```bash
+kubectl -n ${NFD_NS} edit configmap nfd-topology-updater-conf
+```
 
 In Helm deployments,
 [Topology Updater parameters](../deployment/helm.md#topology-updater-parameters)
 `toplogyUpdater.config` can be used to edit the respective configuration.
 
-In Kustomize deployments, modify the `nfd-worker-conf` ConfigMap with a custom
-overlay.
-
 See
 [nfd-topology-updater configuration file reference](../reference/topology-updater-configuration-reference.md)
 for more details.
diff --git a/docs/usage/nfd-worker.md b/docs/usage/nfd-worker.md
index 7e9b9b0f3..7baa4c411 100644
--- a/docs/usage/nfd-worker.md
+++ b/docs/usage/nfd-worker.md
@@ -19,9 +19,13 @@ This can be changed by using the
 [`core.sleepInterval`](../reference/worker-configuration-reference.md#coresleepinterval)
 config option.
 
+The worker configuration file is watched and re-read on every change which
+provides a mechanism of dynamic run-time reconfiguration. See
+[worker configuration](#worker-configuration) for more details.
+
 ## Worker configuration
 
-NFD-Worker supports configuration through a configuration file. The
+NFD-Worker supports dynamic configuration through a configuration file. The
 default location is `/etc/kubernetes/node-feature-discovery/nfd-worker.conf`,
 but, this can be changed by specifying the`-config` command line flag.
 Configuration file is re-read whenever it is modified which makes run-time
@@ -32,19 +36,18 @@ VolumeMounts are needed to make your configuration available for NFD. The
 preferred method is to use a ConfigMap which provides easy deployment and
 re-configurability.
 
-The provided deployment methods (Helm and Kustomize) create an empty configmap
-and mount it inside the nfd-master containers.
+The provided nfd-worker deployment templates create an empty configmap and
+mount it inside the nfd-worker containers. In kustomize deployments,
+configuration can be edited with:
+
+```bash
+kubectl -n ${NFD_NS} edit configmap nfd-worker-conf
+```
 
 In Helm deployments,
 [Worker pod parameter](../deployment/helm.md#worker-pod-parameters)
 `worker.config` can be used to edit the respective configuration.
 
-In Kustomize deployments, modify the `nfd-worker-conf` ConfigMap with a custom
-overlay.
-
-> **NOTE:** dynamic run-time reconfiguration was dropped in NFD v0.17.
-> Re-configuration is handled by pod restarts.
-
 See
 [nfd-worker configuration file reference](../reference/worker-configuration-reference)
 for more details.
diff --git a/enhancements/1845-nfd-image-compatibility/README.md b/enhancements/1845-nfd-image-compatibility/README.md
new file mode 100644
index 000000000..6d7f5c43f
--- /dev/null
+++ b/enhancements/1845-nfd-image-compatibility/README.md
@@ -0,0 +1,160 @@
+# KEP-1845: Image Compatibility with NFD
+
+## Summary
+
+Currently, there is no standard solution for describing container image requirements in relation to hardware or operating systems.
+Cloud-native technologies are being adopted by high-demand industries where container compatibility is critical for service performance and cluster preparation.
+This proposal introduces the concept of NFD image compatibility metadata.
+NFD features via NodeFeatureRules CRs can be effectively added to images to specify requirements for a host or operating system.
+
+The document has been prepared based on the experience and progress of the [OCI Image Compatibility working group](https://github.com/opencontainers/wg-image-compatibility/tree/main/docs/proposals).
+
+## Motivation
+
+Image compatibility metadata will help container image authors describe compatibility requirements in a standardized way.
+This metadata will be uploaded with the image to the image registry.
+As a result, container compatibility requirements will become discoverable and programmable, supporting various consumers and use cases where applications require a specific compatible environment.
+
+### Goals
+
+#### Phase 1
+
+- Use existing NFD features via the NodeFeatureRule API to describe container image requirements.
+- Create a new OCI artifact type for compatibility metadata.
+- Allow verification of node compatibilitym including nodes that are not yet part of the k8s cluster.
+- Add or extend the sources with missing features.
+
+#### Phase 2
+
+Phase 2 involves future prediction and shows the general direction.
+After the completion of Phase 1, either this document should be updated, or a new proposal should be created that considers the following points:
+
+- Update or generate pods with appropriate node selectors via a mutation webhook or a scheduler plugin.
+
+### Non-Goals
+
+- Make image compatibility a hard requirement for the NFD installation/usage.
+- Cover applications ABI compatibility.
+
+## Proposal
+
+Build a new NFD client tool with the following initial scope:
+
+- CRUD OCI artifact.
+- Validate nodes based on provided metadata.
+- Run directly on a host which is not part of the Kubernetes cluster, or run as a Kubernetes job on a Kubernetes node.
+
+### Design Details
+
+#### OCI Artifact
+
+[An OCI artifact](https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidelines-for-artifact-usage) should be created to store image compatibility metadata on the image side.  
+The artifact can be connected with an image over [the subject field](https://github.com/opencontainers/distribution-spec/blob/11b8e3fba7d2d7329513d0cff53058243c334858/spec.md#pushing-manifests-with-subject).
+
+##### Manifest
+
+```json
+{
+  "schemaVersion": 2,
+  "mediaType": "application/vnd.oci.image.manifest.v1+json",
+  "artifactType": "application/vnd.k8s.nfd.image-compatibility.v1",
+  "config": {
+    "mediaType": "application/vnd.oci.empty.v1+json",
+    "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
+    "size": 2
+  },
+  "layers": [
+    {
+      "mediaType": "application/vnd.k8s.nfd.image-compatibility.spec.v1+yaml",
+      "digest": "sha256:4a47f8ae4c713906618413cb9795824d09eeadf948729e213a1ba11a1e31d052",
+      "size": 1710
+    }
+  ],
+  "subject": {
+    "mediaType": "application/vnd.oci.image.manifest.v1+json",
+    "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+    "size": 7682
+  },
+  "annotations": {
+    "oci.opencontainers.image.created": "2024-03-27T08:08:08Z"
+  }
+}
+```
+
+##### Artifact Payload (Schema)
+
+- **version** - *string*  
+This REQUIRED property specifies the version of the API being used.
+
+- **compatibilities** - *array of object*  
+This REQUIRED property is a list of compatibility sets.
+
+  - **rules** - *object*  
+  This REQUIRED property is a reference to the spec of [NodeFeatureRule API](https://kubernetes-sigs.github.io/node-feature-discovery/v0.16/usage/custom-resources.html#nodefeaturerule).
+  The spec makes it possible to describe image requirements using the discovered features from NFD sources.
+  For further reading, please review [the documentation](https://kubernetes-sigs.github.io/node-feature-discovery/v0.16/usage/customization-guide.html#nodefeaturerule-custom-resource).
+
+  - **weight** - *int*  
+  This OPTIONAL property specify the [node affinity weight](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity-weight).
+
+  - **tag** - *string*  
+  This OPTIONAL property allows grouping or dividing of compatibility sets.
+
+  - **description** - *string*  
+  This OPTIONAL property is intended for a brief description of a compatibility set.
+
+Example
+
+```yaml
+version: v1alpha1
+compatibilities:
+- tag: "prefered"
+  weight: 10
+  description: "Prefered node configuration"
+  rules:
+  - name: "kernel and cpu"
+    matchFeatures:
+    - feature: kernel.loadedmodule
+      matchExpressions:
+        vfio-pci: {op: Exists}
+    - feature: cpu.model
+      matchExpressions:
+        vendor_id: {op: In, value: ["Intel", "Amd"]}
+- tag: "fallback"
+  weight: 1
+  description: "Minimal required configuration"
+  rules:
+  - name: "cpu"
+    matchFeatures:
+    - feature: cpu.model
+      matchExpressions:
+        vendor_id: {op: In, value: ["Intel", "Amd"]}
+```
+
+##### Discovery
+
+A compatibility artifact shall be associated with either an image index or a specific image via the subject field of the OCI Image Spec.
+The Referrers API should be used to discover artifacts.
+If an image has multiple artifacts, it is up to the client to choose the correct one.
+By default, it is recommended to select the most recent artifact based on the 'created' timestamp.
+
+#### NFD client
+
+A new standalone command-line utility should be implemented for the NFD project that shares the same functionality as the [nfd kubectl plugin](https://nfd.sigs.k8s.io/usage/kubectl-plugin).
+Both clients should implemented the following commands:
+
+- `validate` -  validate a NodeFeatureRule object (implemented in kubectl plugin).
+- `test` - test a NodeFeatureRule object against a node (implemented in kubectl plugin).
+- `dryrun` - process a NodeFeatureRule file against a local NodeFeature file to dry run the rule against a node before applying it to a cluster (implemented in kubectl plugin).
+- `compat` - compatibility command with the following subcommands:
+  - `attach-spec` - create an artifact with image compatibility specification and attach to the image (initially users have to create the spec by hand).
+  - `remove-spec` - remove an artifact with image compatibility specification from the image.
+  - `validate-spec` - validate an artifact and image compatibility specification.
+  - `validate-node` - validate image compatibility against a node.
+
+### Test Plan
+
+To ensure the proper functioning of the nfd client, the following test plan should be executed:
+
+- **Unit Tests:** Write unit tests for the client.
+- **Manual e2e Tests:** Run nfd client with sample data to CRUD artifact and validate a local host.
diff --git a/go.mod b/go.mod
index 04f2de524..819276d52 100644
--- a/go.mod
+++ b/go.mod
@@ -1,27 +1,27 @@
 module sigs.k8s.io/node-feature-discovery
 
-go 1.23.0
+go 1.22.2
 
 require (
 	github.com/fsnotify/fsnotify v1.7.0
 	github.com/golang/protobuf v1.5.4
 	github.com/google/go-cmp v0.6.0
 	github.com/google/uuid v1.6.0
-	github.com/jaypipes/ghw v0.13.0
+	github.com/jaypipes/ghw v0.12.0
 	github.com/k8stopologyawareschedwg/noderesourcetopology-api v0.1.2
 	github.com/k8stopologyawareschedwg/podfingerprint v0.2.2
 	github.com/klauspost/cpuid/v2 v2.2.8
-	github.com/onsi/ginkgo/v2 v2.20.2
-	github.com/onsi/gomega v1.34.2
-	github.com/opencontainers/runc v1.1.14
+	github.com/onsi/ginkgo/v2 v2.20.0
+	github.com/onsi/gomega v1.34.1
+	github.com/opencontainers/runc v1.1.13
 	github.com/prometheus/client_golang v1.18.0
 	github.com/smartystreets/goconvey v1.8.1
 	github.com/spf13/cobra v1.8.1
 	github.com/stretchr/testify v1.9.0
 	github.com/vektra/errors v0.0.0-20140903201135-c64d83aba85a
 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
-	golang.org/x/net v0.30.0
-	golang.org/x/time v0.7.0
+	golang.org/x/net v0.28.0
+	golang.org/x/time v0.6.0
 	google.golang.org/grpc v1.63.2
 	google.golang.org/protobuf v1.34.2
 	k8s.io/api v0.30.3
@@ -60,6 +60,7 @@ require (
 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
 	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
+	github.com/ghodss/yaml v1.0.0 // indirect
 	github.com/go-logr/logr v1.4.2 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-ole/go-ole v1.2.6 // indirect
@@ -73,14 +74,14 @@ require (
 	github.com/google/cel-go v0.17.8 // indirect
 	github.com/google/gnostic-models v0.6.8 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
-	github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect
+	github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
 	github.com/gorilla/websocket v1.5.0 // indirect
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect
 	github.com/imdario/mergo v0.3.6 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
-	github.com/jaypipes/pcidb v1.0.1 // indirect
+	github.com/jaypipes/pcidb v1.0.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/jtolds/gls v4.20.0+incompatible // indirect
@@ -119,13 +120,13 @@ require (
 	go.opentelemetry.io/proto/otlp v1.0.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.26.0 // indirect
-	golang.org/x/crypto v0.28.0 // indirect
+	golang.org/x/crypto v0.26.0 // indirect
 	golang.org/x/mod v0.20.0 // indirect
 	golang.org/x/oauth2 v0.17.0 // indirect
 	golang.org/x/sync v0.8.0 // indirect
-	golang.org/x/sys v0.26.0 // indirect
-	golang.org/x/term v0.25.0 // indirect
-	golang.org/x/text v0.19.0 // indirect
+	golang.org/x/sys v0.23.0 // indirect
+	golang.org/x/term v0.23.0 // indirect
+	golang.org/x/text v0.17.0 // indirect
 	golang.org/x/tools v0.24.0 // indirect
 	google.golang.org/appengine v1.6.8 // indirect
 	google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
diff --git a/go.sum b/go.sum
index 28404c15d..4fc11d10c 100644
--- a/go.sum
+++ b/go.sum
@@ -66,6 +66,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
 github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -114,8 +116,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA=
-github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
+github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
@@ -135,10 +137,10 @@ github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
 github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/jaypipes/ghw v0.13.0 h1:log8MXuB8hzTNnSktqpXMHc0c/2k/WgjOMSUtnI1RV4=
-github.com/jaypipes/ghw v0.13.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8=
-github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic=
-github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4=
+github.com/jaypipes/ghw v0.12.0 h1:xU2/MDJfWmBhJnujHY9qwXQLs3DBsf0/Xa9vECY0Tho=
+github.com/jaypipes/ghw v0.12.0/go.mod h1:jeJGbkRB2lL3/gxYzNYzEDETV1ZJ56OKr+CSeSEym+g=
+github.com/jaypipes/pcidb v1.0.0 h1:vtZIfkiCUE42oYbJS0TAq9XSfSmcsgo9IdxSm9qzYU8=
+github.com/jaypipes/pcidb v1.0.0/go.mod h1:TnYUvqhPBzCKnH34KrIX22kAeEbDCSRJ9cqLRCuNDfk=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
 github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
@@ -188,14 +190,14 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
-github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
-github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
-github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
-github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
+github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw=
+github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
+github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
+github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w=
-github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA=
+github.com/opencontainers/runc v1.1.13 h1:98S2srgG9vw0zWcDpFMn5TRrh8kLxa/5OFUstuUhmRs=
+github.com/opencontainers/runc v1.1.13/go.mod h1:R016aXacfp/gwQBYw2FDGa9m+n6atbLWrYY8hNMT/sA=
 github.com/opencontainers/runtime-spec v1.1.0-rc.2 h1:ucBtEms2tamYYW/SvGpvq9yUN0NEVL6oyLEwDcTSrk8=
 github.com/opencontainers/runtime-spec v1.1.0-rc.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
 github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
@@ -303,8 +305,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
-golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
+golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
+golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -318,8 +320,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
-golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
+golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
+golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
 golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
 golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -338,20 +340,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
-golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
+golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
-golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
+golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
+golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
-golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
-golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
-golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
+golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
+golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
diff --git a/pkg/nfd-master/nfd-api-controller.go b/pkg/nfd-master/nfd-api-controller.go
index 29cba271e..81b5bd35e 100644
--- a/pkg/nfd-master/nfd-api-controller.go
+++ b/pkg/nfd-master/nfd-api-controller.go
@@ -61,9 +61,9 @@ func init() {
 func newNfdController(config *restclient.Config, nfdApiControllerOptions nfdApiControllerOptions) (*nfdController, error) {
 	c := &nfdController{
 		stopChan:                       make(chan struct{}),
-		updateAllNodesChan:             make(chan struct{}),
+		updateAllNodesChan:             make(chan struct{}, 1),
 		updateOneNodeChan:              make(chan string),
-		updateAllNodeFeatureGroupsChan: make(chan struct{}),
+		updateAllNodeFeatureGroupsChan: make(chan struct{}, 1),
 		updateNodeFeatureGroupChan:     make(chan string),
 	}
 
@@ -206,10 +206,7 @@ func (c *nfdController) updateOneNode(typ string, obj metav1.Object) {
 		klog.ErrorS(err, "failed to determine node name for object", "type", typ, "object", klog.KObj(obj))
 		return
 	}
-	select {
-	case c.updateOneNodeChan <- nodeName:
-	case <-c.stopChan:
-	}
+	c.updateOneNodeChan <- nodeName
 }
 
 func (c *nfdController) updateAllNodes() {
@@ -220,10 +217,7 @@ func (c *nfdController) updateAllNodes() {
 }
 
 func (c *nfdController) updateNodeFeatureGroup(nodeFeatureGroup string) {
-	select {
-	case c.updateNodeFeatureGroupChan <- nodeFeatureGroup:
-	case <-c.stopChan:
-	}
+	c.updateNodeFeatureGroupChan <- nodeFeatureGroup
 }
 
 func (c *nfdController) updateAllNodeFeatureGroups() {
diff --git a/pkg/nfd-master/nfd-master.go b/pkg/nfd-master/nfd-master.go
index 5babc1208..e34bb3e5f 100644
--- a/pkg/nfd-master/nfd-master.go
+++ b/pkg/nfd-master/nfd-master.go
@@ -284,7 +284,7 @@ func (m *nfdMaster) Run() error {
 		klog.InfoS("Master instance", "instance", m.args.Instance)
 	}
 
-	// Read configuration
+	// Read initial configuration
 	if err := m.configure(m.configFilePath, m.args.Options); err != nil {
 		return err
 	}
@@ -302,6 +302,12 @@ func (m *nfdMaster) Run() error {
 
 	m.updaterPool.start(m.config.NfdApiParallelism)
 
+	// Create watcher for config file
+	configWatch, err := utils.CreateFsWatcher(time.Second, m.configFilePath)
+	if err != nil {
+		return err
+	}
+
 	if !m.config.NoPublish {
 		err := m.updateMasterNode()
 		if err != nil {
@@ -359,6 +365,36 @@ func (m *nfdMaster) Run() error {
 		case err := <-grpcErr:
 			return fmt.Errorf("error in serving gRPC: %w", err)
 
+		case <-configWatch.Events:
+			klog.InfoS("reloading configuration")
+			if err := m.configure(m.configFilePath, m.args.Options); err != nil {
+				return err
+			}
+
+			// Stop the updaterPool so that no node updates are underway
+			// while we reconfigure the NFD API controller (including the
+			// listers) below
+			m.updaterPool.stop()
+
+			// restart NFD API controller
+			if m.nfdController != nil {
+				klog.InfoS("stopping the nfd api controller")
+				m.nfdController.stop()
+			}
+			if m.args.CrdController {
+				err := m.startNfdApiController()
+				if err != nil {
+					return nil
+				}
+			}
+			// Restart the updaterPool
+			m.updaterPool.start(m.config.NfdApiParallelism)
+
+			// Update all nodes when the configuration changes
+			if m.nfdController != nil && nfdfeatures.NFDFeatureGate.Enabled(nfdfeatures.NodeFeatureAPI) {
+				m.nfdController.updateAllNodesChan <- struct{}{}
+			}
+
 		case <-m.stop:
 			klog.InfoS("shutting down nfd-master")
 			return nil
diff --git a/pkg/nfd-master/updater-pool.go b/pkg/nfd-master/updater-pool.go
index b65b1942f..fec602713 100644
--- a/pkg/nfd-master/updater-pool.go
+++ b/pkg/nfd-master/updater-pool.go
@@ -31,7 +31,6 @@ import (
 )
 
 type updaterPool struct {
-	started  bool
 	queue    workqueue.RateLimitingInterface
 	nfgQueue workqueue.RateLimitingInterface
 	sync.RWMutex
@@ -48,14 +47,14 @@ func newUpdaterPool(nfdMaster *nfdMaster) *updaterPool {
 	}
 }
 
-func (u *updaterPool) processNodeUpdateRequest(cli k8sclient.Interface) bool {
-	n, quit := u.queue.Get()
+func (u *updaterPool) processNodeUpdateRequest(cli k8sclient.Interface, queue workqueue.RateLimitingInterface) bool {
+	n, quit := queue.Get()
 	if quit {
 		return false
 	}
 	nodeName := n.(string)
 
-	defer u.queue.Done(nodeName)
+	defer queue.Done(nodeName)
 
 	nodeUpdateRequests.Inc()
 
@@ -63,21 +62,21 @@ func (u *updaterPool) processNodeUpdateRequest(cli k8sclient.Interface) bool {
 	if node, err := getNode(cli, nodeName); apierrors.IsNotFound(err) {
 		klog.InfoS("node not found, skip update", "nodeName", nodeName)
 	} else if err := u.nfdMaster.nfdAPIUpdateOneNode(cli, node); err != nil {
-		if n := u.queue.NumRequeues(nodeName); n < 15 {
+		if n := queue.NumRequeues(nodeName); n < 15 {
 			klog.InfoS("retrying node update", "nodeName", nodeName, "lastError", err, "numRetries", n)
 		} else {
 			klog.ErrorS(err, "node update failed, queuing for retry ", "nodeName", nodeName, "numRetries", n)
 			// Count only long-failing attempts
 			nodeUpdateFailures.Inc()
 		}
-		u.queue.AddRateLimited(nodeName)
+		queue.AddRateLimited(nodeName)
 		return true
 	}
-	u.queue.Forget(nodeName)
+	queue.Forget(nodeName)
 	return true
 }
 
-func (u *updaterPool) runNodeUpdater() {
+func (u *updaterPool) runNodeUpdater(queue workqueue.RateLimitingInterface) {
 	var cli k8sclient.Interface
 	if u.nfdMaster.kubeconfig != nil {
 		// For normal execution, initialize a separate api client for each updater
@@ -86,17 +85,17 @@ func (u *updaterPool) runNodeUpdater() {
 		// For tests, re-use the api client from nfd-master
 		cli = u.nfdMaster.k8sClient
 	}
-	for u.processNodeUpdateRequest(cli) {
+	for u.processNodeUpdateRequest(cli, queue) {
 	}
 	u.wg.Done()
 }
 
-func (u *updaterPool) processNodeFeatureGroupUpdateRequest(cli nfdclientset.Interface) bool {
-	nfgName, quit := u.nfgQueue.Get()
+func (u *updaterPool) processNodeFeatureGroupUpdateRequest(cli nfdclientset.Interface, ngfQueue workqueue.RateLimitingInterface) bool {
+	nfgName, quit := ngfQueue.Get()
 	if quit {
 		return false
 	}
-	defer u.nfgQueue.Done(nfgName)
+	defer ngfQueue.Done(nfgName)
 
 	nodeFeatureGroupUpdateRequests.Inc()
 
@@ -106,22 +105,22 @@ func (u *updaterPool) processNodeFeatureGroupUpdateRequest(cli nfdclientset.Inte
 	if nfg, err = getNodeFeatureGroup(cli, u.nfdMaster.namespace, nfgName.(string)); apierrors.IsNotFound(err) {
 		klog.InfoS("NodeFeatureGroup not found, skip update", "NodeFeatureGroupName", nfgName)
 	} else if err := u.nfdMaster.nfdAPIUpdateNodeFeatureGroup(u.nfdMaster.nfdClient, nfg); err != nil {
-		if n := u.nfgQueue.NumRequeues(nfgName); n < 15 {
+		if n := ngfQueue.NumRequeues(nfgName); n < 15 {
 			klog.InfoS("retrying NodeFeatureGroup update", "nodeFeatureGroup", klog.KObj(nfg), "lastError", err)
 		} else {
 			klog.ErrorS(err, "failed to update NodeFeatureGroup, queueing for retry", "nodeFeatureGroup", klog.KObj(nfg), "lastError", err, "numRetries", n)
 		}
-		u.nfgQueue.AddRateLimited(nfgName)
+		ngfQueue.AddRateLimited(nfgName)
 		return true
 	}
 
-	u.nfgQueue.Forget(nfgName)
+	ngfQueue.Forget(nfgName)
 	return true
 }
 
-func (u *updaterPool) runNodeFeatureGroupUpdater() {
+func (u *updaterPool) runNodeFeatureGroupUpdater(ngfQueue workqueue.RateLimitingInterface) {
 	cli := nfdclientset.NewForConfigOrDie(u.nfdMaster.kubeconfig)
-	for u.processNodeFeatureGroupUpdateRequest(cli) {
+	for u.processNodeFeatureGroupUpdateRequest(cli, ngfQueue) {
 	}
 	u.nfgWg.Done()
 }
@@ -130,11 +129,16 @@ func (u *updaterPool) start(parallelism int) {
 	u.Lock()
 	defer u.Unlock()
 
-	if u.started {
+	if u.queue != nil && !u.queue.ShuttingDown() {
 		klog.InfoS("the NFD master updater pool is already running.")
 		return
 	}
 
+	if u.nfgQueue != nil && !u.nfgQueue.ShuttingDown() {
+		klog.InfoS("the NFD master node feature group updater pool is already running.")
+		return
+	}
+
 	klog.InfoS("starting the NFD master updater pool", "parallelism", parallelism)
 
 	// Create ratelimiter. Mimic workqueue.DefaultControllerRateLimiter() but
@@ -148,20 +152,24 @@ func (u *updaterPool) start(parallelism int) {
 
 	for i := 0; i < parallelism; i++ {
 		u.wg.Add(1)
-		go u.runNodeUpdater()
+		go u.runNodeUpdater(u.queue)
 		if features.NFDFeatureGate.Enabled(features.NodeFeatureGroupAPI) {
 			u.nfgWg.Add(1)
-			go u.runNodeFeatureGroupUpdater()
+			go u.runNodeFeatureGroupUpdater(u.nfgQueue)
 		}
 	}
-	u.started = true
 }
 
 func (u *updaterPool) stop() {
 	u.Lock()
 	defer u.Unlock()
 
-	if !u.started {
+	if u.queue == nil || u.queue.ShuttingDown() {
+		klog.InfoS("the NFD master updater pool is not running.")
+		return
+	}
+
+	if u.nfgQueue == nil || u.nfgQueue.ShuttingDown() {
 		klog.InfoS("the NFD master updater pool is not running.")
 		return
 	}
@@ -171,13 +179,6 @@ func (u *updaterPool) stop() {
 	u.wg.Wait()
 	u.nfgQueue.ShutDown()
 	u.nfgWg.Wait()
-	u.started = false
-}
-
-func (u *updaterPool) running() bool {
-	u.RLock()
-	defer u.RUnlock()
-	return u.started
 }
 
 func (u *updaterPool) addNode(nodeName string) {
diff --git a/pkg/nfd-worker/nfd-worker.go b/pkg/nfd-worker/nfd-worker.go
index 4e4c3d09e..f0ed390bc 100644
--- a/pkg/nfd-worker/nfd-worker.go
+++ b/pkg/nfd-worker/nfd-worker.go
@@ -297,11 +297,14 @@ func (w *nfdWorker) runFeatureDiscovery() error {
 func (w *nfdWorker) Run() error {
 	klog.InfoS("Node Feature Discovery Worker", "version", version.Get(), "nodeName", utils.NodeName(), "namespace", w.kubernetesNamespace)
 
-	// Read configuration file
-	err := w.configure(w.configFilePath, w.args.Options)
+	// Create watcher for config file and read initial configuration
+	configWatch, err := utils.CreateFsWatcher(time.Second, w.configFilePath)
 	if err != nil {
 		return err
 	}
+	if err := w.configure(w.configFilePath, w.args.Options); err != nil {
+		return err
+	}
 
 	// Create watcher for TLS certificates
 	w.certWatch, err = utils.CreateFsWatcher(time.Second, w.args.CaFile, w.args.CertFile, w.args.KeyFile)
@@ -387,6 +390,24 @@ func (w *nfdWorker) Run() error {
 				return err
 			}
 
+		case <-configWatch.Events:
+			klog.InfoS("reloading configuration")
+			if err := w.configure(w.configFilePath, w.args.Options); err != nil {
+				return err
+			}
+			// Manage connection to master
+			if w.config.Core.NoPublish || !features.NFDFeatureGate.Enabled(features.NodeFeatureAPI) {
+				w.grpcDisconnect()
+			}
+
+			// Always re-label after a re-config event. This way the new config
+			// comes into effect even if the sleep interval is long (or infinite)
+			labelTrigger.Reset(w.config.Core.SleepInterval.Duration)
+			err = w.runFeatureDiscovery()
+			if err != nil {
+				return err
+			}
+
 		case <-w.certWatch.Events:
 			klog.InfoS("TLS certificate update, renewing connection to nfd-master")
 			w.grpcDisconnect()
@@ -396,6 +417,7 @@ func (w *nfdWorker) Run() error {
 			if w.healthServer != nil {
 				w.healthServer.GracefulStop()
 			}
+			configWatch.Close()
 			w.certWatch.Close()
 			return nil
 		}
diff --git a/source/network/network.go b/source/network/network.go
index 4039b082d..5f35d5064 100644
--- a/source/network/network.go
+++ b/source/network/network.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
-	"slices"
 	"strconv"
 	"strings"
 	"syscall"
@@ -130,10 +129,6 @@ func detectNetDevices() ([]nfdv1alpha1.InstanceFeature, []nfdv1alpha1.InstanceFe
 		return nil, nil, fmt.Errorf("failed to list network interfaces: %w", err)
 	}
 
-	ifaces = slices.DeleteFunc(ifaces, func(iface os.DirEntry) bool {
-		return iface.Name() == "bonding_masters"
-	})
-
 	// Iterate over devices
 	devIfacesinfo := make([]nfdv1alpha1.InstanceFeature, 0, len(ifaces))
 	virtualIfacesinfo := make([]nfdv1alpha1.InstanceFeature, 0, len(ifaces))
diff --git a/source/system/system.go b/source/system/system.go
index bcbf0c00a..3f32d21c6 100644
--- a/source/system/system.go
+++ b/source/system/system.go
@@ -103,7 +103,7 @@ func (s *systemSource) Discover() error {
 	}
 
 	// Get DMI ID attributes
-	dmiIDAttributeNames := []string{"sys_vendor", "product_name"}
+	dmiIDAttributeNames := []string{"sys_vendor"}
 	dmiAttrs := make(map[string]string)
 	for _, name := range dmiIDAttributeNames {
 		val, err := getDmiIDAttribute(name)


Test patch:
diff --git a/pkg/nfd-gc/nfd-gc_test.go b/pkg/nfd-gc/nfd-gc_test.go
index 7c912f495..d93c48748 100644
--- a/pkg/nfd-gc/nfd-gc_test.go
+++ b/pkg/nfd-gc/nfd-gc_test.go
@@ -29,6 +29,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/sets"
 	metadataclient "k8s.io/client-go/metadata"
 	"k8s.io/client-go/metadata/fake"
+	fakemetadataclient "k8s.io/client-go/metadata/fake"
 	"k8s.io/client-go/metadata/metadatainformer"
 
 	. "github.com/smartystreets/goconvey/convey"
@@ -99,7 +100,7 @@ func newMockGC(nodes, nrts []string) *mockGC {
 
 	scheme := fake.NewTestScheme()
 	_ = metav1.AddMetaToScheme(scheme)
-	cli := fake.NewSimpleMetadataClient(scheme, objs...)
+	cli := fakemetadataclient.NewSimpleMetadataClient(scheme, objs...)
 	return &mockGC{
 		nfdGarbageCollector: nfdGarbageCollector{
 			factory:  metadatainformer.NewSharedInformerFactory(cli, 0),
diff --git a/pkg/nfd-master/nfd-master-internal_test.go b/pkg/nfd-master/nfd-master-internal_test.go
index c41172875..498070a86 100644
--- a/pkg/nfd-master/nfd-master-internal_test.go
+++ b/pkg/nfd-master/nfd-master-internal_test.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"maps"
 	"os"
+	"path/filepath"
 	"sort"
 	"strings"
 	"testing"
@@ -664,6 +665,96 @@ leaderElection:
 	})
 }
 
+func TestDynamicConfig(t *testing.T) {
+	Convey("When running nfd-master", t, func() {
+		// Add feature gates as running nfd-master depends on that
+		err := features.NFDMutableFeatureGate.Add(features.DefaultNFDFeatureGates)
+		So(err, ShouldBeNil)
+
+		tmpDir, err := os.MkdirTemp("", "*.nfd-test")
+		So(err, ShouldBeNil)
+		defer os.RemoveAll(tmpDir)
+
+		// Create (temporary) dir for config
+		configDir := filepath.Join(tmpDir, "subdir-1", "subdir-2", "master.conf")
+		err = os.MkdirAll(configDir, 0755)
+		So(err, ShouldBeNil)
+
+		// Create config file
+		configFile := filepath.Clean(filepath.Join(configDir, "master.conf"))
+
+		writeConfig := func(data string) {
+			f, err := os.Create(configFile)
+			So(err, ShouldBeNil)
+			_, err = f.WriteString(data)
+			So(err, ShouldBeNil)
+			err = f.Close()
+			So(err, ShouldBeNil)
+		}
+		writeConfig(`
+klog:
+  v: "4"
+extraLabelNs: ["added.ns.io"]
+`)
+
+		master := newFakeMaster(
+			WithArgs(&Args{ConfigFile: configFile}),
+			WithKubernetesClient(fakeclient.NewSimpleClientset(newTestNode())))
+
+		Convey("config file updates should take effect", func() {
+			go func() {
+				Convey("nfd-master should exit gracefully", t, func() {
+					err = master.Run()
+					So(err, ShouldBeNil)
+				})
+			}()
+			defer master.Stop()
+			// Check initial config
+			time.Sleep(10 * time.Second)
+			So(func() interface{} { return master.config.ExtraLabelNs },
+				withTimeout, 2*time.Second, ShouldResemble, utils.StringSetVal{"added.ns.io": struct{}{}})
+
+			// Update config and verify the effect
+			writeConfig(`
+extraLabelNs: ["override.ns.io"]
+resyncPeriod: '2h'
+nfdApiParallelism: 300
+`)
+			So(func() interface{} { return master.config.ExtraLabelNs },
+				withTimeout, 2*time.Second, ShouldResemble, utils.StringSetVal{"override.ns.io": struct{}{}})
+			So(func() interface{} { return master.config.ResyncPeriod.Duration },
+				withTimeout, 2*time.Second, ShouldResemble, time.Duration(2)*time.Hour)
+			So(func() interface{} { return master.config.NfdApiParallelism },
+				withTimeout, 2*time.Second, ShouldResemble, 300)
+
+			// Removing config file should get back our defaults
+			err = os.RemoveAll(tmpDir)
+			So(err, ShouldBeNil)
+			So(func() interface{} { return master.config.ExtraLabelNs },
+				withTimeout, 2*time.Second, ShouldResemble, utils.StringSetVal{})
+			So(func() interface{} { return master.config.ResyncPeriod.Duration },
+				withTimeout, 2*time.Second, ShouldResemble, time.Duration(1)*time.Hour)
+			So(func() interface{} { return master.config.NfdApiParallelism },
+				withTimeout, 2*time.Second, ShouldResemble, 10)
+
+			// Re-creating config dir and file should change the config
+			err = os.MkdirAll(configDir, 0755)
+			So(err, ShouldBeNil)
+			writeConfig(`
+extraLabelNs: ["another.override.ns"]
+resyncPeriod: '3m'
+nfdApiParallelism: 100
+`)
+			So(func() interface{} { return master.config.ExtraLabelNs },
+				withTimeout, 2*time.Second, ShouldResemble, utils.StringSetVal{"another.override.ns": struct{}{}})
+			So(func() interface{} { return master.config.ResyncPeriod.Duration },
+				withTimeout, 2*time.Second, ShouldResemble, time.Duration(3)*time.Minute)
+			So(func() interface{} { return master.config.NfdApiParallelism },
+				withTimeout, 2*time.Second, ShouldResemble, 100)
+		})
+	})
+}
+
 func newTestNodeList() *corev1.NodeList {
 	l := corev1.NodeList{}
 
diff --git a/pkg/nfd-master/updater-pool_test.go b/pkg/nfd-master/updater-pool_test.go
index 7430d5a1d..efeab4043 100644
--- a/pkg/nfd-master/updater-pool_test.go
+++ b/pkg/nfd-master/updater-pool_test.go
@@ -37,15 +37,8 @@ func TestUpdaterStart(t *testing.T) {
 	fakeMaster := newFakeMaster()
 	updaterPool := newFakeupdaterPool(fakeMaster)
 
-	Convey("New node updater pool should report running=false", t, func() {
-		So(updaterPool.running(), ShouldBeFalse)
-	})
-
 	Convey("When starting the node updater pool", t, func() {
 		updaterPool.start(10)
-		Convey("Running node updater pool should report running=true", func() {
-			So(updaterPool.running(), ShouldBeTrue)
-		})
 		q := updaterPool.queue
 		Convey("Node updater pool queue properties should change", func() {
 			So(q, ShouldNotBeNil)
@@ -64,15 +57,9 @@ func TestNodeUpdaterStop(t *testing.T) {
 	updaterPool := newFakeupdaterPool(fakeMaster)
 
 	updaterPool.start(10)
-	Convey("Running node updater pool should report running=true", t, func() {
-		So(updaterPool.running(), ShouldBeTrue)
-	})
 
 	Convey("When stoping the node updater pool", t, func() {
 		updaterPool.stop()
-		Convey("Stopped node updater pool should report running=false", func() {
-			So(updaterPool.running(), ShouldBeFalse)
-		})
 		Convey("Node updater pool queue should be removed", func() {
 			// Wait for the wg.Done()
 			So(func() interface{} {
diff --git a/pkg/nfd-worker/nfd-worker-internal_test.go b/pkg/nfd-worker/nfd-worker-internal_test.go
index 2ff380571..067653d64 100644
--- a/pkg/nfd-worker/nfd-worker-internal_test.go
+++ b/pkg/nfd-worker/nfd-worker-internal_test.go
@@ -18,6 +18,7 @@ package nfdworker
 
 import (
 	"os"
+	"path/filepath"
 	"regexp"
 	"strings"
 	"testing"
@@ -194,6 +195,113 @@ sources:
 	})
 }
 
+func TestDynamicConfig(t *testing.T) {
+	Convey("When running nfd-worker", t, func() {
+		tmpDir, err := os.MkdirTemp("", "*.nfd-test")
+		So(err, ShouldBeNil)
+		defer os.RemoveAll(tmpDir)
+
+		// Create (temporary) dir for config
+		configDir := filepath.Join(tmpDir, "subdir-1", "subdir-2", "worker.conf")
+		err = os.MkdirAll(configDir, 0755)
+		So(err, ShouldBeNil)
+
+		// Create config file
+		configFile := filepath.Join(configDir, "worker.conf")
+
+		writeConfig := func(data string) {
+			f, err := os.Create(configFile)
+			So(err, ShouldBeNil)
+			_, err = f.WriteString(data)
+			So(err, ShouldBeNil)
+			err = f.Close()
+			So(err, ShouldBeNil)
+
+		}
+		writeConfig(`
+core:
+  labelWhiteList: "fake"
+`)
+
+		noPublish := true
+		w, err := NewNfdWorker(WithArgs(&Args{
+			ConfigFile: configFile,
+			Overrides: ConfigOverrideArgs{
+				FeatureSources: &utils.StringSliceVal{"fake"},
+				LabelSources:   &utils.StringSliceVal{"fake"},
+				NoPublish:      &noPublish},
+		}), WithKubernetesClient(fakeclient.NewSimpleClientset()))
+		So(err, ShouldBeNil)
+		worker := w.(*nfdWorker)
+
+		Convey("config file updates should take effect", func() {
+			go func() { _ = w.Run() }()
+			defer w.Stop()
+
+			// Check initial config
+			So(func() interface{} { return worker.config.Core.LabelWhiteList.String() },
+				withTimeout, 2*time.Second, ShouldEqual, "fake")
+
+			// Update config and verify the effect
+			writeConfig(`
+core:
+  labelWhiteList: "foo"
+`)
+			So(func() interface{} { return worker.config.Core.LabelWhiteList.String() },
+				withTimeout, 2*time.Second, ShouldEqual, "foo")
+
+			// Removing config file should get back our defaults
+			err = os.RemoveAll(tmpDir)
+			So(err, ShouldBeNil)
+			So(func() interface{} { return worker.config.Core.LabelWhiteList.String() },
+				withTimeout, 2*time.Second, ShouldEqual, "")
+
+			// Re-creating config dir and file should change the config
+			err = os.MkdirAll(configDir, 0755)
+			So(err, ShouldBeNil)
+			writeConfig(`
+core:
+  labelWhiteList: "bar"
+`)
+			So(func() interface{} { return worker.config.Core.LabelWhiteList.String() },
+				withTimeout, 2*time.Second, ShouldEqual, "bar")
+		})
+	})
+}
+
+// withTimeout is a custom assertion for polling a value asynchronously
+// actual is a function for getting the actual value
+// expected[0] is a time.Duration value specifying the timeout
+// expected[1] is  the "real" assertion function to be called
+// expected[2:] are the arguments for the "real" assertion function
+func withTimeout(actual interface{}, expected ...interface{}) string {
+	getter, ok := actual.(func() interface{})
+	if !ok {
+		return "not getterFunc"
+	}
+	t, ok := expected[0].(time.Duration)
+	if !ok {
+		return "not time.Duration"
+	}
+	f, ok := expected[1].(func(interface{}, ...interface{}) string)
+	if !ok {
+		return "not an assert func"
+	}
+
+	timeout := time.After(t)
+	for {
+		result := f(getter(), expected[2:]...)
+		if result == "" {
+			return ""
+		}
+		select {
+		case <-timeout:
+			return result
+		case <-time.After(10 * time.Millisecond):
+		}
+	}
+}
+
 func TestNewNfdWorker(t *testing.T) {
 	Convey("When creating new NfdWorker instance", t, func() {
 
diff --git a/scripts/test-infra/verify.sh b/scripts/test-infra/verify.sh
index bd041d0f4..a0bc89766 100755
--- a/scripts/test-infra/verify.sh
+++ b/scripts/test-infra/verify.sh
@@ -4,7 +4,7 @@ this_dir=`dirname $0`
 
 # Install deps
 gobinpath="$(go env GOPATH)/bin"
-curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b "$gobinpath" v1.60.3
+curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b "$gobinpath" v1.59.1
 export PATH=$PATH:$(go env GOPATH)/bin
 
 curl -sfL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash -s -- --version v3.15.3
diff --git a/test/e2e/node_feature_discovery_test.go b/test/e2e/node_feature_discovery_test.go
index 7edebee6d..38e0a49f0 100644
--- a/test/e2e/node_feature_discovery_test.go
+++ b/test/e2e/node_feature_discovery_test.go
@@ -200,8 +200,6 @@ func cleanupCRs(ctx context.Context, cli *nfdclient.Clientset, namespace string)
 // Actual test suite
 var _ = NFDDescribe(Label("nfd-master"), func() {
 	f := framework.NewDefaultFramework("node-feature-discovery")
-	// nfd-worker needs host mounts
-	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
 
 	Context("when deploying a single nfd-master pod", Ordered, func() {
 		var (
@@ -236,6 +234,14 @@ var _ = NFDDescribe(Label("nfd-master"), func() {
 		})
 
 		JustBeforeEach(func(ctx context.Context) {
+			// Drop the pod security admission label as nfd-worker needs host mounts
+			if _, ok := f.Namespace.Labels[admissionapi.EnforceLevelLabel]; ok {
+				framework.Logf("Deleting %s label from the test namespace", admissionapi.EnforceLevelLabel)
+				delete(f.Namespace.Labels, admissionapi.EnforceLevelLabel)
+				_, err := f.ClientSet.CoreV1().Namespaces().Update(ctx, f.Namespace, metav1.UpdateOptions{})
+				Expect(err).NotTo(HaveOccurred())
+			}
+
 			err := testutils.ConfigureRBAC(ctx, f.ClientSet, f.Namespace.Name)
 			Expect(err).NotTo(HaveOccurred())
 
@@ -934,6 +940,24 @@ denyLabelNs: ["*.denied.ns","random.unwanted.ns"]
 				By("Deleting NodeFeature object")
 				err = nfdClient.NfdV1alpha1().NodeFeatures(f.Namespace.Name).Delete(ctx, nodeFeatures[0], metav1.DeleteOptions{})
 				Expect(err).NotTo(HaveOccurred())
+
+				// TODO: Find a better way to handle the timeout that happens to reflect the configmap changes
+				Skip("Testing the master dynamic configuration")
+				// Verify that config changes were applied
+				By("Updating the master config")
+				Expect(testutils.UpdateConfigMap(ctx, f.ClientSet, "nfd-master-conf", f.Namespace.Name, "nfd-master.conf", `
+denyLabelNs: []
+`))
+				By("Verifying that denied labels were removed")
+				expectedLabels = map[string]k8sLabels{
+					targetNodeName: {
+						nfdv1alpha1.FeatureLabelNs + "/e2e-nodefeature-test-4": "obj-4",
+						"custom.vendor.io/e2e-nodefeature-test-3":              "vendor-ns",
+						"random.denied.ns/e2e-nodefeature-test-1":              "denied-ns",
+						"random.unwanted.ns/e2e-nodefeature-test-2":            "unwanted-ns",
+					},
+				}
+				eventuallyNonControlPlaneNodes(ctx, f.ClientSet).Should(MatchLabels(expectedLabels, nodes))
 			})
 		})
 
diff --git a/test/e2e/utils/noderesourcetopology.go b/test/e2e/utils/noderesourcetopology.go
index ff15a236c..b58d29289 100644
--- a/test/e2e/utils/noderesourcetopology.go
+++ b/test/e2e/utils/noderesourcetopology.go
@@ -228,7 +228,7 @@ func IsValidNodeTopology(nodeTopology *v1alpha2.NodeResourceTopology, kubeletCon
 		return false
 	}
 
-	if len(nodeTopology.Zones) == 0 {
+	if nodeTopology.Zones == nil || len(nodeTopology.Zones) == 0 {
 		framework.Logf("failed to get topology zones from the node topology resource")
 		return false
 	}
diff --git a/test/e2e/utils/pod/pod.go b/test/e2e/utils/pod/pod.go
index 56b421ad3..435405266 100644
--- a/test/e2e/utils/pod/pod.go
+++ b/test/e2e/utils/pod/pod.go
@@ -120,6 +120,8 @@ type SpecOption func(spec *corev1.PodSpec)
 
 // NFDMaster provide NFD master pod definition
 func NFDMaster(opts ...SpecOption) *corev1.Pod {
+	yes := true
+	no := false
 	p := &corev1.Pod{
 		ObjectMeta: metav1.ObjectMeta{
 			GenerateName: "nfd-master-",
@@ -145,10 +147,10 @@ func NFDMaster(opts ...SpecOption) *corev1.Pod {
 						Capabilities: &corev1.Capabilities{
 							Drop: []corev1.Capability{"ALL"},
 						},
-						Privileged:               ptr.To[bool](false),
-						RunAsNonRoot:             ptr.To[bool](true),
-						ReadOnlyRootFilesystem:   ptr.To[bool](true),
-						AllowPrivilegeEscalation: ptr.To[bool](false),
+						Privileged:               &no,
+						RunAsNonRoot:             &yes,
+						ReadOnlyRootFilesystem:   &yes,
+						AllowPrivilegeEscalation: &no,
 						SeccompProfile: &corev1.SeccompProfile{
 							Type: corev1.SeccompProfileTypeRuntimeDefault,
 						},
@@ -250,6 +252,8 @@ func SpecWithConfigMap(name, mountPath string) SpecOption {
 }
 
 func nfdWorkerSpec(opts ...SpecOption) *corev1.PodSpec {
+	yes := true
+	no := false
 	p := &corev1.PodSpec{
 		Containers: []corev1.Container{
 			{
@@ -287,10 +291,10 @@ func nfdWorkerSpec(opts ...SpecOption) *corev1.PodSpec {
 					Capabilities: &corev1.Capabilities{
 						Drop: []corev1.Capability{"ALL"},
 					},
-					Privileged:               ptr.To[bool](false),
-					RunAsNonRoot:             ptr.To[bool](true),
-					ReadOnlyRootFilesystem:   ptr.To[bool](true),
-					AllowPrivilegeEscalation: ptr.To[bool](false),
+					Privileged:               &no,
+					RunAsNonRoot:             &yes,
+					ReadOnlyRootFilesystem:   &yes,
+					AllowPrivilegeEscalation: &no,
 					SeccompProfile: &corev1.SeccompProfile{
 						Type: corev1.SeccompProfileTypeRuntimeDefault,
 					},
@@ -368,6 +372,8 @@ func nfdWorkerSpec(opts ...SpecOption) *corev1.PodSpec {
 }
 
 func NFDGCSpec(opts ...SpecOption) *corev1.PodSpec {
+	yes := true
+	no := false
 	p := &corev1.PodSpec{
 		Containers: []corev1.Container{
 			{
@@ -378,10 +384,10 @@ func NFDGCSpec(opts ...SpecOption) *corev1.PodSpec {
 					Capabilities: &corev1.Capabilities{
 						Drop: []corev1.Capability{"ALL"},
 					},
-					Privileged:               ptr.To[bool](false),
-					RunAsNonRoot:             ptr.To[bool](true),
-					ReadOnlyRootFilesystem:   ptr.To[bool](true),
-					AllowPrivilegeEscalation: ptr.To[bool](false),
+					Privileged:               &no,
+					RunAsNonRoot:             &yes,
+					ReadOnlyRootFilesystem:   &yes,
+					AllowPrivilegeEscalation: &no,
 					SeccompProfile: &corev1.SeccompProfile{
 						Type: corev1.SeccompProfileTypeRuntimeDefault,
 					},