Repository: kubernetes-sigs/node-feature-discovery
Pull request #1021 (2022-12-28 12:00:56)

Docs: mention tainting in the intro section


Patch:
diff --git a/.github/ISSUE_TEMPLATE/new-release.md b/.github/ISSUE_TEMPLATE/new-release.md
index 80f4c3dbb..ef10d67cd 100644
--- a/.github/ISSUE_TEMPLATE/new-release.md
+++ b/.github/ISSUE_TEMPLATE/new-release.md
@@ -17,9 +17,6 @@ Please do not remove items from the checklist
         `git branch release-0.$MAJ master`
   - [ ] An OWNER pushes the new release branch with
         `git push release-0.$MAJ`
-  - [ ] Create Prow pre-submit job configuration for the new release branch in K8s
-        [test-infra](https://github.com/kubernetes/test-infra), submit a PR
-  - [ ] Wait for the test-infra Prow config PR to be merged
 - [ ] Run `hack/prepare-release.sh $VERSION` to turn references to point to the upcoming release
       (README, deployment templates, docs configuration, test/e2e flags), submit a PR against the release branch
 - An OWNER prepares a draft release
diff --git a/Dockerfile b/Dockerfile
index 40c4af266..f1b9106a3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,7 +6,7 @@ ARG BASE_IMAGE_MINIMAL
 FROM ${BUILDER_IMAGE} as builder
 
 # Build and install the grpc-health-probe binary
-RUN GRPC_HEALTH_PROBE_VERSION=v0.4.14 && \
+RUN GRPC_HEALTH_PROBE_VERSION=v0.4.6 && \
 	go install github.com/grpc-ecosystem/grpc-health-probe@${GRPC_HEALTH_PROBE_VERSION} \
         # Rename it as it's referenced as grpc_health_probe in the deployment yamls
         # and in its own project https://github.com/grpc-ecosystem/grpc-health-probe
diff --git a/README.md b/README.md
index 44d7f23f8..bb918f3a8 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.12.0
+$ kubectl apply -k https://github.com/kubernetes-sigs/node-feature-discovery/deployment/overlays/default?ref=v0.11.3
   namespace/node-feature-discovery created
   customresourcedefinition.apiextensions.k8s.io/nodefeaturerules.nfd.k8s-sigs.io created
   serviceaccount/nfd-master created
diff --git a/cmd/nfd-topology-updater/main.go b/cmd/nfd-topology-updater/main.go
index 514c98553..e6115b3ee 100644
--- a/cmd/nfd-topology-updater/main.go
+++ b/cmd/nfd-topology-updater/main.go
@@ -26,7 +26,8 @@ import (
 	"k8s.io/klog/v2"
 	kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
 
-	topology "sigs.k8s.io/node-feature-discovery/pkg/nfd-topology-updater"
+	nfdclient "sigs.k8s.io/node-feature-discovery/pkg/nfd-client"
+	topology "sigs.k8s.io/node-feature-discovery/pkg/nfd-client/topology-updater"
 	"sigs.k8s.io/node-feature-discovery/pkg/resourcemonitor"
 	"sigs.k8s.io/node-feature-discovery/pkg/topologypolicy"
 	"sigs.k8s.io/node-feature-discovery/pkg/utils"
@@ -110,12 +111,12 @@ func parseArgs(flags *flag.FlagSet, osArgs ...string) (*topology.Args, *resource
 	}
 
 	if len(resourcemonitorArgs.KubeletConfigURI) == 0 {
-		if len(utils.NodeName()) == 0 {
+		if len(nfdclient.NodeName()) == 0 {
 			fmt.Fprintf(flags.Output(), "unable to determine the default kubelet config endpoint 'https://${NODE_NAME}:%d/configz' due to empty NODE_NAME environment, "+
 				"please either define the NODE_NAME environment variable or specify endpoint with the -kubelet-config-uri flag\n", kubeletSecurePort)
 			os.Exit(1)
 		}
-		resourcemonitorArgs.KubeletConfigURI = fmt.Sprintf("https://%s:%d/configz", utils.NodeName(), kubeletSecurePort)
+		resourcemonitorArgs.KubeletConfigURI = fmt.Sprintf("https://%s:%d/configz", nfdclient.NodeName(), kubeletSecurePort)
 	}
 
 	return args, resourcemonitorArgs
diff --git a/cmd/nfd-worker/main.go b/cmd/nfd-worker/main.go
index 2e6675e22..30e3290d1 100644
--- a/cmd/nfd-worker/main.go
+++ b/cmd/nfd-worker/main.go
@@ -24,7 +24,7 @@ import (
 
 	"k8s.io/klog/v2"
 
-	worker "sigs.k8s.io/node-feature-discovery/pkg/nfd-worker"
+	"sigs.k8s.io/node-feature-discovery/pkg/nfd-client/worker"
 	"sigs.k8s.io/node-feature-discovery/pkg/utils"
 	"sigs.k8s.io/node-feature-discovery/pkg/version"
 )
diff --git a/deployment/base/nfd-crds/nfd-api-crds.yaml b/deployment/base/nfd-crds/nfd-api-crds.yaml
index 5d9e1e16d..ab10f15a9 100644
--- a/deployment/base/nfd-crds/nfd-api-crds.yaml
+++ b/deployment/base/nfd-crds/nfd-api-crds.yaml
@@ -52,8 +52,6 @@ spec:
                       required:
                       - elements
                       type: object
-                    description: Attributes contains all the attribute-type features
-                      of the node.
                     type: object
                   flags:
                     additionalProperties:
@@ -69,8 +67,6 @@ spec:
                       required:
                       - elements
                       type: object
-                    description: Flags contains all the flag-type features of the
-                      node.
                     type: object
                   instances:
                     additionalProperties:
@@ -93,9 +89,11 @@ spec:
                       required:
                       - elements
                       type: object
-                    description: Instances contains all the instance-type features
-                      of the node.
                     type: object
+                required:
+                - attributes
+                - flags
+                - instances
                 type: object
               labels:
                 additionalProperties:
@@ -103,6 +101,8 @@ spec:
                 description: Labels is the set of node labels that are requested to
                   be created.
                 type: object
+            required:
+            - features
             type: object
         required:
         - spec
diff --git a/deployment/helm/node-feature-discovery/crds/nfd-api-crds.yaml b/deployment/helm/node-feature-discovery/crds/nfd-api-crds.yaml
index 5d9e1e16d..ab10f15a9 100644
--- a/deployment/helm/node-feature-discovery/crds/nfd-api-crds.yaml
+++ b/deployment/helm/node-feature-discovery/crds/nfd-api-crds.yaml
@@ -52,8 +52,6 @@ spec:
                       required:
                       - elements
                       type: object
-                    description: Attributes contains all the attribute-type features
-                      of the node.
                     type: object
                   flags:
                     additionalProperties:
@@ -69,8 +67,6 @@ spec:
                       required:
                       - elements
                       type: object
-                    description: Flags contains all the flag-type features of the
-                      node.
                     type: object
                   instances:
                     additionalProperties:
@@ -93,9 +89,11 @@ spec:
                       required:
                       - elements
                       type: object
-                    description: Instances contains all the instance-type features
-                      of the node.
                     type: object
+                required:
+                - attributes
+                - flags
+                - instances
                 type: object
               labels:
                 additionalProperties:
@@ -103,6 +101,8 @@ spec:
                 description: Labels is the set of node labels that are requested to
                   be created.
                 type: object
+            required:
+            - features
             type: object
         required:
         - spec
diff --git a/deployment/helm/node-feature-discovery/templates/master.yaml b/deployment/helm/node-feature-discovery/templates/master.yaml
index 220b8af9b..26a326cb8 100644
--- a/deployment/helm/node-feature-discovery/templates/master.yaml
+++ b/deployment/helm/node-feature-discovery/templates/master.yaml
@@ -76,16 +76,16 @@ spec:
             {{- toYaml .Values.master.resources | nindent 12 }}
           args:
             {{- if .Values.master.instance | empty | not }}
-            - "-instance={{ .Values.master.instance }}"
+            - "--instance={{ .Values.master.instance }}"
             {{- end }}
             {{- if .Values.enableNodeFeatureApi }}
             - "-enable-nodefeature-api"
             {{- end }}
             {{- if .Values.master.extraLabelNs | empty | not }}
-            - "-extra-label-ns={{- join "," .Values.master.extraLabelNs }}"
+            - "--extra-label-ns={{- join "," .Values.master.extraLabelNs }}"
             {{- end }}
             {{- if .Values.master.resourceLabels | empty | not }}
-            - "-resource-labels={{- join "," .Values.master.resourceLabels }}"
+            - "--resource-labels={{- join "," .Values.master.resourceLabels }}"
             {{- end }}
             {{- if .Values.master.crdController | kindIs "invalid" | not }}
             - "-crd-controller={{ .Values.master.crdController }}"
@@ -97,9 +97,9 @@ spec:
             - "-featurerules-controller={{ .Values.master.featureRulesController }}"
             {{- 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"
+            - "--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"
           volumeMounts:
             - name: nfd-master-cert
               mountPath: "/etc/kubernetes/node-feature-discovery/certs"
diff --git a/deployment/helm/node-feature-discovery/templates/topologyupdater.yaml b/deployment/helm/node-feature-discovery/templates/topologyupdater.yaml
index 8f4849ac5..2f28b969f 100644
--- a/deployment/helm/node-feature-discovery/templates/topologyupdater.yaml
+++ b/deployment/helm/node-feature-discovery/templates/topologyupdater.yaml
@@ -41,19 +41,19 @@ spec:
           - "nfd-topology-updater"
         args:
           {{- if .Values.topologyUpdater.updateInterval | empty | not }}
-          - "-sleep-interval={{ .Values.topologyUpdater.updateInterval }}"
+          - "--sleep-interval={{ .Values.topologyUpdater.updateInterval }}"
           {{- else }}
-          - "-sleep-interval=3s"
+          - "--sleep-interval=3s"
           {{- end }}
           {{- if .Values.topologyUpdater.watchNamespace | empty | not }}
-          - "-watch-namespace={{ .Values.topologyUpdater.watchNamespace }}"
+          - "--watch-namespace={{ .Values.topologyUpdater.watchNamespace }}"
           {{- else }}
-          - "-watch-namespace=*"
+          - "--watch-namespace=*"
           {{- 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"
+          - "--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 }}
         volumeMounts:
         - name: kubelet-config
diff --git a/deployment/helm/node-feature-discovery/templates/worker.yaml b/deployment/helm/node-feature-discovery/templates/worker.yaml
index 829e4e5e3..e723cc5cb 100644
--- a/deployment/helm/node-feature-discovery/templates/worker.yaml
+++ b/deployment/helm/node-feature-discovery/templates/worker.yaml
@@ -45,14 +45,14 @@ spec:
         command:
         - "nfd-worker"
         args:
-        - "-server={{ include "node-feature-discovery.fullname" . }}-master:{{ .Values.master.service.port }}"
+        - "--server={{ include "node-feature-discovery.fullname" . }}-master:{{ .Values.master.service.port }}"
         {{- if .Values.enableNodeFeatureApi }}
         - "-enable-nodefeature-api"
         {{- 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"
+        - "--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 }}
         volumeMounts:
         - name: host-boot
diff --git a/deployment/overlays/prune/master-job.yaml b/deployment/overlays/prune/master-job.yaml
index 247b59a5f..9ea1c24d3 100644
--- a/deployment/overlays/prune/master-job.yaml
+++ b/deployment/overlays/prune/master-job.yaml
@@ -20,6 +20,6 @@ spec:
           command:
             - "nfd-master"
           args:
-            - "-prune"
+            - "--prune"
       restartPolicy: Never
 
diff --git a/docs/deployment/helm.md b/docs/deployment/helm.md
index f618d7615..49ab7052c 100644
--- a/docs/deployment/helm.md
+++ b/docs/deployment/helm.md
@@ -17,10 +17,6 @@ sort: 3
 
 Node Feature Discovery Helm chart allow to easily deploy and manage NFD.
 
-> NOTE: NFD is not ideal for other Helm charts to depend on as that may result
-> in multiple parallel NFD deployments in the same cluster which is not fully
-> supported by the NFD Helm chart.
-
 ## Prerequisites
 
 [Helm package manager](https://helm.sh/) should be installed.
diff --git a/docs/deployment/index.md b/docs/deployment/index.md
index 9f4fb64cc..2b5f5c9fe 100644
--- a/docs/deployment/index.md
+++ b/docs/deployment/index.md
@@ -6,14 +6,4 @@ sort: 2
 
 # Deployment
 
-See [Image variants](image-variants) for description of the different NFD
-container images available.
-
-[Using Kustomize](kustomize) provides straightforward deployment with
-`kubectl` integration and declarative customization.
-
-[Using Helm](helm) provides easy management of NFD deployments with nice
-configuration management and easy upgrades.
-
-[Using Operator](operator) provides deployment and configuration management via
-CRDs.
+Deployment instructions.
diff --git a/docs/deployment/kustomize.md b/docs/deployment/kustomize.md
index efb6678be..b5d84a0d7 100644
--- a/docs/deployment/kustomize.md
+++ b/docs/deployment/kustomize.md
@@ -15,15 +15,9 @@ sort: 2
 
 ---
 
-[Kustomize](https://github.com/kubernetes-sigs/kustomize) provides easy
-deployment of NFD. Customization of the deployment is done by maintaining
-declarative overlays on top of the base overlays in NFD.
-
-To follow the deployment instructions here,
+This deployment method requires
 [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl) v1.21 or
-later is required.
-
-The kustomize overlays provided in the repo can be used directly:
+later. 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 }}
diff --git a/docs/deployment/operator.md b/docs/deployment/operator.md
index df1c7846b..b9d47ff09 100644
--- a/docs/deployment/operator.md
+++ b/docs/deployment/operator.md
@@ -15,11 +15,6 @@ sort: 4
 
 ---
 
-The [Node Feature Discovery Operator][nfd-operator] automates installation,
-configuration and updates of NFD using a specific NodeFeatureDiscovery custom
-resource. This also provides good support for managing NFD as a dependency of
-other operators.
-
 ## Deployment
 
 Deployment using the
diff --git a/docs/get-started/introduction.md b/docs/get-started/introduction.md
index a4719def6..21211a934 100644
--- a/docs/get-started/introduction.md
+++ b/docs/get-started/introduction.md
@@ -17,7 +17,7 @@ sort: 1
 
 This software enables node feature discovery for Kubernetes. It detects
 hardware features available on each node in a Kubernetes cluster, and
-advertises those features using node labels.
+advertises those features using node labels and optionally node taints.
 
 NFD consists of three software components:
 
diff --git a/docs/usage/custom-resources.md b/docs/usage/custom-resources.md
index 1fd64d0ea..deec64dec 100644
--- a/docs/usage/custom-resources.md
+++ b/docs/usage/custom-resources.md
@@ -20,14 +20,11 @@ NFD uses some Kubernetes [custom resources][custom-resources].
 ## NodeFeature
 
 **EXPERIMENTAL**
-NodeFeature is an NFD-specific custom resource for communicating node
-features and node labeling requests. Support for NodeFeature objects is
+NodeFeatureRule is an NFD-specific custom resource for communicating node
+features and node labeling requests. Support for NodeFeatureRule objects is
 disabled by default. If enabled, nfd-master watches for NodeFeature objects,
-labels nodes as specified and uses the listed features as input when evaluating
-[NodeFeatureRule](#nodefeaturerule)s. NodeFeature objects can be used for
-implementing 3rd party extensions (see
-[customization guide](customization-guide#nodefeature-custom-resource) for more
-details).
+labels nodes accordingly and uses the listed features as input when evaluating
+[NodeFeatureRule](#nodefeaturerule)s.
 
 ```yaml
 apiVersion: nfd.k8s-sigs.io/v1alpha1
diff --git a/pkg/apis/nfd/v1alpha1/generated.proto b/pkg/apis/nfd/v1alpha1/generated.proto
index e3d26de3d..09f408ee7 100644
--- a/pkg/apis/nfd/v1alpha1/generated.proto
+++ b/pkg/apis/nfd/v1alpha1/generated.proto
@@ -35,16 +35,10 @@ message AttributeFeatureSet {
 //
 // +protobuf=true
 message Features {
-  // Flags contains all the flag-type features of the node.
-  // +optional
   map<string, FlagFeatureSet> flags = 1;
 
-  // Attributes contains all the attribute-type features of the node.
-  // +optional
   map<string, AttributeFeatureSet> vattributes = 2;
 
-  // Instances contains all the instance-type features of the node.
-  // +optional
   map<string, InstanceFeatureSet> instances = 3;
 }
 
diff --git a/pkg/apis/nfd/v1alpha1/types.go b/pkg/apis/nfd/v1alpha1/types.go
index c916daa86..3565db1cd 100644
--- a/pkg/apis/nfd/v1alpha1/types.go
+++ b/pkg/apis/nfd/v1alpha1/types.go
@@ -46,7 +46,6 @@ type NodeFeature struct {
 // NodeFeatureSpec describes a NodeFeature object.
 type NodeFeatureSpec struct {
 	// Features is the full "raw" features data that has been discovered.
-	// +optional
 	Features Features `json:"features"`
 	// Labels is the set of node labels that are requested to be created.
 	// +optional
@@ -57,15 +56,9 @@ type NodeFeatureSpec struct {
 //
 // +protobuf=true
 type Features struct {
-	// Flags contains all the flag-type features of the node.
-	// +optional
-	Flags map[string]FlagFeatureSet `json:"flags" protobuf:"bytes,1,rep,name=flags"`
-	// Attributes contains all the attribute-type features of the node.
-	// +optional
+	Flags      map[string]FlagFeatureSet      `json:"flags" protobuf:"bytes,1,rep,name=flags"`
 	Attributes map[string]AttributeFeatureSet `json:"attributes" protobuf:"bytes,2,rep,name=vattributes"`
-	// Instances contains all the instance-type features of the node.
-	// +optional
-	Instances map[string]InstanceFeatureSet `json:"instances" protobuf:"bytes,3,rep,name=instances"`
+	Instances  map[string]InstanceFeatureSet  `json:"instances" protobuf:"bytes,3,rep,name=instances"`
 }
 
 // FlagFeatureSet is a set of simple features only containing names without values.
diff --git a/pkg/nfd-client/base.go b/pkg/nfd-client/base.go
new file mode 100644
index 000000000..c96553542
--- /dev/null
+++ b/pkg/nfd-client/base.go
@@ -0,0 +1,145 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package nfdclient
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"fmt"
+	"os"
+	"time"
+
+	"golang.org/x/net/context"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/credentials/insecure"
+	"k8s.io/klog/v2"
+
+	"sigs.k8s.io/node-feature-discovery/pkg/utils"
+)
+
+// NfdClient defines a common interface for NFD clients.
+type NfdClient interface {
+	Run() error
+	Stop()
+}
+
+// NfdBaseClient is a common base type for handling connections to nfd-master.
+type NfdBaseClient struct {
+	args       Args
+	clientConn *grpc.ClientConn
+}
+
+// Args holds the common command line arguments for all nfd clients.
+type Args struct {
+	CaFile             string
+	CertFile           string
+	KeyFile            string
+	Kubeconfig         string
+	Server             string
+	ServerNameOverride string
+
+	Klog map[string]*utils.KlogFlagVal
+}
+
+var nodeName string
+
+func init() {
+	nodeName = os.Getenv("NODE_NAME")
+}
+
+// NodeName returns the name of the k8s node we're running on.
+func NodeName() string { return nodeName }
+
+// NewNfdBaseClient creates a new NfdBaseClient instance.
+func NewNfdBaseClient(args *Args) (NfdBaseClient, error) {
+	nfd := NfdBaseClient{args: *args}
+
+	// Check TLS related args
+	if args.CertFile != "" || args.KeyFile != "" || args.CaFile != "" {
+		if args.CertFile == "" {
+			return nfd, fmt.Errorf("-cert-file needs to be specified alongside -key-file and -ca-file")
+		}
+		if args.KeyFile == "" {
+			return nfd, fmt.Errorf("-key-file needs to be specified alongside -cert-file and -ca-file")
+		}
+		if args.CaFile == "" {
+			return nfd, fmt.Errorf("-ca-file needs to be specified alongside -cert-file and -key-file")
+		}
+	}
+
+	return nfd, nil
+}
+
+// ClientConn returns the grpc ClientConn object.
+func (w *NfdBaseClient) ClientConn() *grpc.ClientConn { return w.clientConn }
+
+// Connect creates a gRPC client connection to nfd-master.
+func (w *NfdBaseClient) Connect() error {
+	// Check that if a connection already exists
+	if w.clientConn != nil {
+		return fmt.Errorf("client connection already exists")
+	}
+
+	// Dial and create a client
+	dialCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
+	defer cancel()
+	dialOpts := []grpc.DialOption{grpc.WithBlock()}
+	if w.args.CaFile != "" || w.args.CertFile != "" || w.args.KeyFile != "" {
+		// Load client cert for client authentication
+		cert, err := tls.LoadX509KeyPair(w.args.CertFile, w.args.KeyFile)
+		if err != nil {
+			return fmt.Errorf("failed to load client certificate: %v", err)
+		}
+		// Load CA cert for server cert verification
+		caCert, err := os.ReadFile(w.args.CaFile)
+		if err != nil {
+			return fmt.Errorf("failed to read root certificate file: %v", err)
+		}
+		caPool := x509.NewCertPool()
+		if ok := caPool.AppendCertsFromPEM(caCert); !ok {
+			return fmt.Errorf("failed to add certificate from '%s'", w.args.CaFile)
+		}
+		// Create TLS config
+		tlsConfig := &tls.Config{
+			Certificates: []tls.Certificate{cert},
+			RootCAs:      caPool,
+			ServerName:   w.args.ServerNameOverride,
+			MinVersion:   tls.VersionTLS13,
+		}
+		dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
+	} else {
+		dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
+	}
+	klog.Infof("connecting to nfd-master at %s ...", w.args.Server)
+	conn, err := grpc.DialContext(dialCtx, w.args.Server, dialOpts...)
+	if err != nil {
+		return err
+	}
+	w.clientConn = conn
+
+	return nil
+}
+
+// Disconnect closes the connection to NFD master
+func (w *NfdBaseClient) Disconnect() {
+	if w.clientConn != nil {
+		klog.Infof("closing connection to nfd-master ...")
+		w.clientConn.Close()
+	}
+	w.clientConn = nil
+}
diff --git a/pkg/nfd-topology-updater/nfd-topology-updater.go b/pkg/nfd-client/topology-updater/nfd-topology-updater.go
similarity index 99%
rename from pkg/nfd-topology-updater/nfd-topology-updater.go
rename to pkg/nfd-client/topology-updater/nfd-topology-updater.go
index a5b3e2639..7ada9cb3b 100644
--- a/pkg/nfd-topology-updater/nfd-topology-updater.go
+++ b/pkg/nfd-client/topology-updater/nfd-topology-updater.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package nfdtopologyupdater
+package topologyupdater
 
 import (
 	"fmt"
@@ -78,7 +78,7 @@ func NewTopologyUpdater(args Args, resourcemonitorArgs resourcemonitor.Args, pol
 		args:                args,
 		resourcemonitorArgs: resourcemonitorArgs,
 		nodeInfo: &staticNodeInfo{
-			nodeName: utils.NodeName(),
+			nodeName: os.Getenv("NODE_NAME"),
 			tmPolicy: policy,
 		},
 		stop:   make(chan struct{}, 1),
diff --git a/pkg/nfd-worker/nfd-worker.go b/pkg/nfd-client/worker/nfd-worker.go
similarity index 86%
rename from pkg/nfd-worker/nfd-worker.go
rename to pkg/nfd-client/worker/nfd-worker.go
index 304eeb698..abc0fdb4a 100644
--- a/pkg/nfd-worker/nfd-worker.go
+++ b/pkg/nfd-client/worker/nfd-worker.go
@@ -14,11 +14,9 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package nfdworker
+package worker
 
 import (
-	"crypto/tls"
-	"crypto/x509"
 	"encoding/json"
 	"fmt"
 	"os"
@@ -29,9 +27,6 @@ import (
 	"time"
 
 	"golang.org/x/net/context"
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/credentials"
-	"google.golang.org/grpc/credentials/insecure"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/util/validation"
 	"k8s.io/klog/v2"
@@ -43,6 +38,7 @@ import (
 	nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
 	nfdclient "sigs.k8s.io/node-feature-discovery/pkg/generated/clientset/versioned"
 	pb "sigs.k8s.io/node-feature-discovery/pkg/labeler"
+	clientcommon "sigs.k8s.io/node-feature-discovery/pkg/nfd-client"
 	"sigs.k8s.io/node-feature-discovery/pkg/utils"
 	"sigs.k8s.io/node-feature-discovery/pkg/version"
 	"sigs.k8s.io/node-feature-discovery/source"
@@ -61,12 +57,6 @@ import (
 	_ "sigs.k8s.io/node-feature-discovery/source/usb"
 )
 
-// NfdWorker is the interface for nfd-worker daemon
-type NfdWorker interface {
-	Run() error
-	Stop()
-}
-
 // NFDConfig contains the configuration settings of NfdWorker.
 type NFDConfig struct {
 	Core    coreConfig
@@ -90,18 +80,14 @@ type Labels map[string]string
 
 // Args are the command line arguments of NfdWorker.
 type Args struct {
-	CaFile               string
-	CertFile             string
+	clientcommon.Args
+
 	ConfigFile           string
 	EnableNodeFeatureApi bool
-	KeyFile              string
-	Klog                 map[string]*utils.KlogFlagVal
-	Kubeconfig           string
 	Oneshot              bool
 	Options              string
-	Server               string
-	ServerNameOverride   string
 
+	Klog      map[string]*utils.KlogFlagVal
 	Overrides ConfigOverrideArgs
 }
 
@@ -114,9 +100,10 @@ type ConfigOverrideArgs struct {
 }
 
 type nfdWorker struct {
+	clientcommon.NfdBaseClient
+
 	args                Args
 	certWatch           *utils.FsWatcher
-	clientConn          *grpc.ClientConn
 	configFilePath      string
 	config              *NFDConfig
 	kubernetesNamespace string
@@ -132,27 +119,21 @@ type duration struct {
 }
 
 // NewNfdWorker creates new NfdWorker instance.
-func NewNfdWorker(args *Args) (NfdWorker, error) {
+func NewNfdWorker(args *Args) (clientcommon.NfdClient, error) {
+	base, err := clientcommon.NewNfdBaseClient(&args.Args)
+	if err != nil {
+		return nil, err
+	}
+
 	nfd := &nfdWorker{
+		NfdBaseClient: base,
+
 		args:                *args,
 		config:              &NFDConfig{},
 		kubernetesNamespace: utils.GetKubernetesNamespace(),
 		stop:                make(chan struct{}, 1),
 	}
 
-	// Check TLS related args
-	if args.CertFile != "" || args.KeyFile != "" || args.CaFile != "" {
-		if args.CertFile == "" {
-			return nfd, fmt.Errorf("-cert-file needs to be specified alongside -key-file and -ca-file")
-		}
-		if args.KeyFile == "" {
-			return nfd, fmt.Errorf("-key-file needs to be specified alongside -cert-file and -ca-file")
-		}
-		if args.CaFile == "" {
-			return nfd, fmt.Errorf("-ca-file needs to be specified alongside -cert-file and -key-file")
-		}
-	}
-
 	if args.ConfigFile != "" {
 		nfd.configFilePath = filepath.Clean(args.ConfigFile)
 	}
@@ -176,7 +157,7 @@ func newDefaultConfig() *NFDConfig {
 // one request if OneShot is set to 'true' in the worker args.
 func (w *nfdWorker) Run() error {
 	klog.Infof("Node Feature Discovery Worker %s", version.Get())
-	klog.Infof("NodeName: '%s'", utils.NodeName())
+	klog.Infof("NodeName: '%s'", clientcommon.NodeName())
 	klog.Infof("Kubernetes namespace: '%s'", w.kubernetesNamespace)
 
 	// Create watcher for config file and read initial configuration
@@ -194,7 +175,7 @@ func (w *nfdWorker) Run() error {
 		return err
 	}
 
-	defer w.grpcDisconnect()
+	defer w.GrpcDisconnect()
 
 	labelTrigger := time.After(0)
 	for {
@@ -233,7 +214,7 @@ func (w *nfdWorker) Run() error {
 			}
 			// Manage connection to master
 			if w.config.Core.NoPublish || !w.args.EnableNodeFeatureApi {
-				w.grpcDisconnect()
+				w.GrpcDisconnect()
 			}
 
 			// Always re-label after a re-config event. This way the new config
@@ -242,7 +223,7 @@ func (w *nfdWorker) Run() error {
 
 		case <-w.certWatch.Events:
 			klog.Infof("TLS certificate update, renewing connection to nfd-master")
-			w.grpcDisconnect()
+			w.GrpcDisconnect()
 
 		case <-w.stop:
 			klog.Infof("shutting down nfd-worker")
@@ -268,60 +249,18 @@ func (w *nfdWorker) getGrpcClient() (pb.LabelerClient, error) {
 		return w.grpcClient, nil
 	}
 
-	// Check that if a connection already exists
-	if w.clientConn != nil {
-		return nil, fmt.Errorf("client connection already exists")
-	}
-
-	// Dial and create a client
-	dialCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
-	defer cancel()
-	dialOpts := []grpc.DialOption{grpc.WithBlock()}
-	if w.args.CaFile != "" || w.args.CertFile != "" || w.args.KeyFile != "" {
-		// Load client cert for client authentication
-		cert, err := tls.LoadX509KeyPair(w.args.CertFile, w.args.KeyFile)
-		if err != nil {
-			return nil, fmt.Errorf("failed to load client certificate: %v", err)
-		}
-		// Load CA cert for server cert verification
-		caCert, err := os.ReadFile(w.args.CaFile)
-		if err != nil {
-			return nil, fmt.Errorf("failed to read root certificate file: %v", err)
-		}
-		caPool := x509.NewCertPool()
-		if ok := caPool.AppendCertsFromPEM(caCert); !ok {
-			return nil, fmt.Errorf("failed to add certificate from '%s'", w.args.CaFile)
-		}
-		// Create TLS config
-		tlsConfig := &tls.Config{
-			Certificates: []tls.Certificate{cert},
-			RootCAs:      caPool,
-			ServerName:   w.args.ServerNameOverride,
-			MinVersion:   tls.VersionTLS13,
-		}
-		dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
-	} else {
-		dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
-	}
-	klog.Infof("connecting to nfd-master at %s ...", w.args.Server)
-	conn, err := grpc.DialContext(dialCtx, w.args.Server, dialOpts...)
-	if err != nil {
+	if err := w.NfdBaseClient.Connect(); err != nil {
 		return nil, err
 	}
-	w.clientConn = conn
 
-	w.grpcClient = pb.NewLabelerClient(w.clientConn)
+	w.grpcClient = pb.NewLabelerClient(w.ClientConn())
 
 	return w.grpcClient, nil
 }
 
-// grpcDisconnect closes the gRPC connection to NFD master
-func (w *nfdWorker) grpcDisconnect() {
-	if w.clientConn != nil {
-		klog.Infof("closing connection to nfd-master ...")
-		w.clientConn.Close()
-	}
-	w.clientConn = nil
+// GrpcDisconnect closes the gRPC connection to NFD master
+func (w *nfdWorker) GrpcDisconnect() {
+	w.NfdBaseClient.Disconnect()
 	w.grpcClient = nil
 }
 func (c *coreConfig) sanitize() {
@@ -620,7 +559,7 @@ func (w *nfdWorker) advertiseFeatureLabels(labels Labels) error {
 	labelReq := pb.SetLabelsRequest{Labels: labels,
 		Features:   source.GetAllFeatures(),
 		NfdVersion: version.Get(),
-		NodeName:   utils.NodeName()}
+		NodeName:   clientcommon.NodeName()}
 
 	cli, err := w.getGrpcClient()
 	if err != nil {
@@ -642,7 +581,7 @@ func (m *nfdWorker) updateNodeFeatureObject(labels Labels) error {
 	if err != nil {
 		return err
 	}
-	nodename := utils.NodeName()
+	nodename := clientcommon.NodeName()
 	namespace := m.kubernetesNamespace
 
 	features := source.GetAllFeatures()
diff --git a/pkg/nfd-master/nfd-master.go b/pkg/nfd-master/nfd-master.go
index 4d7c3d13c..7446471c2 100644
--- a/pkg/nfd-master/nfd-master.go
+++ b/pkg/nfd-master/nfd-master.go
@@ -21,6 +21,7 @@ import (
 	"crypto/x509"
 	"fmt"
 	"net"
+	"os"
 	"path"
 	"regexp"
 	"sort"
@@ -100,7 +101,7 @@ type nfdMaster struct {
 // NewNfdMaster creates a new NfdMaster server instance.
 func NewNfdMaster(args *Args) (NfdMaster, error) {
 	nfd := &nfdMaster{args: *args,
-		nodeName:  utils.NodeName(),
+		nodeName:  os.Getenv("NODE_NAME"),
 		namespace: utils.GetKubernetesNamespace(),
 		ready:     make(chan bool, 1),
 		stop:      make(chan struct{}, 1),
diff --git a/pkg/utils/kubernetes.go b/pkg/utils/kubernetes.go
index 7601da0fc..4dd6ba09f 100644
--- a/pkg/utils/kubernetes.go
+++ b/pkg/utils/kubernetes.go
@@ -21,16 +21,6 @@ import (
 	"strings"
 )
 
-var nodeName string
-
-// NodeName returns the name of the k8s node we're running on.
-func NodeName() string {
-	if nodeName == "" {
-		nodeName = os.Getenv("NODE_NAME")
-	}
-	return nodeName
-}
-
 // GetKubernetesNamespace returns the kubernetes namespace we're running under,
 // or an empty string if the namespace cannot be determined.
 func GetKubernetesNamespace() string {
diff --git a/source/system/system.go b/source/system/system.go
index 9399bd1d9..6945ef24d 100644
--- a/source/system/system.go
+++ b/source/system/system.go
@@ -82,7 +82,7 @@ func (s *systemSource) Discover() error {
 
 	// Get node name
 	s.features.Attributes[NameFeature] = nfdv1alpha1.NewAttributeFeatures(nil)
-	s.features.Attributes[NameFeature].Elements["nodename"] = utils.NodeName()
+	s.features.Attributes[NameFeature].Elements["nodename"] = os.Getenv("NODE_NAME")
 
 	// Get os-release information
 	release, err := parseOSRelease()


Test patch:
diff --git a/pkg/nfd-worker/nfd-worker-internal_test.go b/pkg/nfd-client/worker/nfd-worker-internal_test.go
similarity index 99%
rename from pkg/nfd-worker/nfd-worker-internal_test.go
rename to pkg/nfd-client/worker/nfd-worker-internal_test.go
index 94852ee3a..714197712 100644
--- a/pkg/nfd-worker/nfd-worker-internal_test.go
+++ b/pkg/nfd-client/worker/nfd-worker-internal_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package nfdworker
+package worker
 
 import (
 	"os"
diff --git a/pkg/nfd-worker/nfd-worker_test.go b/pkg/nfd-client/worker/nfd-worker_test.go
similarity index 78%
rename from pkg/nfd-worker/nfd-worker_test.go
rename to pkg/nfd-client/worker/nfd-worker_test.go
index b80aa27b3..adf2b84f1 100644
--- a/pkg/nfd-worker/nfd-worker_test.go
+++ b/pkg/nfd-client/worker/nfd-worker_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package nfdworker_test
+package worker_test
 
 import (
 	"fmt"
@@ -25,8 +25,9 @@ import (
 
 	. "github.com/smartystreets/goconvey/convey"
 
+	nfdclient "sigs.k8s.io/node-feature-discovery/pkg/nfd-client"
+	"sigs.k8s.io/node-feature-discovery/pkg/nfd-client/worker"
 	master "sigs.k8s.io/node-feature-discovery/pkg/nfd-master"
-	worker "sigs.k8s.io/node-feature-discovery/pkg/nfd-worker"
 	"sigs.k8s.io/node-feature-discovery/pkg/utils"
 	"sigs.k8s.io/node-feature-discovery/test/data"
 )
@@ -75,9 +76,9 @@ func teardownTest(ctx testContext) {
 func TestNewNfdWorker(t *testing.T) {
 	Convey("When initializing new NfdWorker instance", t, func() {
 		Convey("When one of -cert-file, -key-file or -ca-file is missing", func() {
-			_, err := worker.NewNfdWorker(&worker.Args{CertFile: "crt", KeyFile: "key"})
-			_, err2 := worker.NewNfdWorker(&worker.Args{KeyFile: "key", CaFile: "ca"})
-			_, err3 := worker.NewNfdWorker(&worker.Args{CertFile: "crt", CaFile: "ca"})
+			_, err := worker.NewNfdWorker(&worker.Args{Args: nfdclient.Args{CertFile: "crt", KeyFile: "key"}})
+			_, err2 := worker.NewNfdWorker(&worker.Args{Args: nfdclient.Args{KeyFile: "key", CaFile: "ca"}})
+			_, err3 := worker.NewNfdWorker(&worker.Args{Args: nfdclient.Args{CertFile: "crt", CaFile: "ca"}})
 			Convey("An error should be returned", func() {
 				So(err, ShouldNotBeNil)
 				So(err2, ShouldNotBeNil)
@@ -93,7 +94,8 @@ func TestRun(t *testing.T) {
 	Convey("When running nfd-worker against nfd-master", t, func() {
 		Convey("When publishing features from fake source", func() {
 			args := &worker.Args{
-				Server:    "localhost:8192",
+				Args: nfdclient.Args{
+					Server: "localhost:8192"},
 				Oneshot:   true,
 				Overrides: worker.ConfigOverrideArgs{LabelSources: &utils.StringSliceVal{"fake"}},
 			}
@@ -118,13 +120,15 @@ func TestRunTls(t *testing.T) {
 	Convey("When running nfd-worker against nfd-master with mutual TLS auth enabled", t, func() {
 		Convey("When publishing features from fake source", func() {
 			workerArgs := worker.Args{
-				CaFile:             data.FilePath("ca.crt"),
-				CertFile:           data.FilePath("nfd-test-worker.crt"),
-				KeyFile:            data.FilePath("nfd-test-worker.key"),
-				Server:             "localhost:8192",
-				ServerNameOverride: "nfd-test-master",
-				Oneshot:            true,
-				Overrides:          worker.ConfigOverrideArgs{LabelSources: &utils.StringSliceVal{"fake"}},
+				Args: nfdclient.Args{
+					CaFile:             data.FilePath("ca.crt"),
+					CertFile:           data.FilePath("nfd-test-worker.crt"),
+					KeyFile:            data.FilePath("nfd-test-worker.key"),
+					Server:             "localhost:8192",
+					ServerNameOverride: "nfd-test-master",
+				},
+				Oneshot:   true,
+				Overrides: worker.ConfigOverrideArgs{LabelSources: &utils.StringSliceVal{"fake"}},
 			}
 			w, _ := worker.NewNfdWorker(&workerArgs)
 			err := w.Run()
diff --git a/test/e2e/utils/pod/pod.go b/test/e2e/utils/pod/pod.go
index ec719f331..5a93b19f6 100644
--- a/test/e2e/utils/pod/pod.go
+++ b/test/e2e/utils/pod/pod.go
@@ -363,10 +363,10 @@ func NFDTopologyUpdaterSpec(kc utils.KubeletConfig, opts ...SpecOption) *corev1.
 				ImagePullPolicy: pullPolicy(),
 				Command:         []string{"nfd-topology-updater"},
 				Args: []string{
-					"-kubelet-config-uri=file:///podresources/config.yaml",
-					"-podresources-socket=unix:///podresources/kubelet.sock",
-					"-sleep-interval=3s",
-					"-watch-namespace=rte"},
+					"--kubelet-config-uri=file:///podresources/config.yaml",
+					"--podresources-socket=unix:///podresources/kubelet.sock",
+					"--sleep-interval=3s",
+					"--watch-namespace=rte"},
 				Env: []corev1.EnvVar{
 					{
 						Name: "NODE_NAME",