Skip to content

Commit a75f334

Browse files
committed
Expose swap behavior via label
Signed-off-by: Feruzjon Muyassarov <[email protected]>
1 parent e1a7461 commit a75f334

File tree

6 files changed

+135
-25
lines changed

6 files changed

+135
-25
lines changed

cmd/nfd-worker/main.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ package main
1919
import (
2020
"flag"
2121
"fmt"
22+
"net"
2223
"os"
24+
"strings"
2325

2426
"k8s.io/klog/v2"
2527

@@ -32,7 +34,8 @@ import (
3234

3335
const (
3436
// ProgramName is the canonical name of this program
35-
ProgramName = "nfd-worker"
37+
ProgramName = "nfd-worker"
38+
kubeletSecurePort = 10250
3639
)
3740

3841
func main() {
@@ -82,6 +85,20 @@ func parseArgs(flags *flag.FlagSet, osArgs ...string) *worker.Args {
8285
os.Exit(2)
8386
}
8487

88+
if len(args.KubeletConfigURI) == 0 {
89+
nodeAddress := os.Getenv("NODE_ADDRESS")
90+
if len(nodeAddress) == 0 {
91+
_, _ = fmt.Fprintf(flags.Output(), "unable to determine the default kubelet config endpoint 'https://${NODE_ADDRESS}:%d/configz' due to empty NODE_ADDRESS environment, "+
92+
"please either define the NODE_ADDRESS environment variable or specify endpoint with the -kubelet-config-uri flag\n", kubeletSecurePort)
93+
os.Exit(1)
94+
}
95+
if isIPv6(nodeAddress) {
96+
// With IPv6 we need to wrap the IP address in brackets as we append :port below
97+
nodeAddress = "[" + nodeAddress + "]"
98+
}
99+
args.KubeletConfigURI = fmt.Sprintf("https://%s:%d/configz", nodeAddress, kubeletSecurePort)
100+
}
101+
85102
// Handle overrides
86103
flags.Visit(func(f *flag.Flag) {
87104
switch f.Name {
@@ -106,6 +123,10 @@ func initFlags(flagset *flag.FlagSet) (*worker.Args, *worker.ConfigOverrideArgs)
106123
"Config file to use.")
107124
flagset.StringVar(&args.Kubeconfig, "kubeconfig", "",
108125
"Kubeconfig to use")
126+
flagset.StringVar(&args.KubeletConfigURI, "kubelet-config-uri", "",
127+
"Kubelet config URI path. Default to kubelet configz endpoint.")
128+
flagset.StringVar(&args.APIAuthTokenFile, "api-auth-token-file", "/var/run/secrets/kubernetes.io/serviceaccount/token",
129+
"API auth token file path. It is used to request kubelet configz endpoint, only takes effect when kubelet-config-uri is https. Default to /var/run/secrets/kubernetes.io/serviceaccount/token.")
109130
flagset.BoolVar(&args.Oneshot, "oneshot", false,
110131
"Do not publish feature labels")
111132
flagset.IntVar(&args.Port, "port", 8080,
@@ -134,3 +155,8 @@ func initFlags(flagset *flag.FlagSet) (*worker.Args, *worker.ConfigOverrideArgs)
134155

135156
return args, overrides
136157
}
158+
159+
func isIPv6(addr string) bool {
160+
ip := net.ParseIP(addr)
161+
return ip != nil && strings.Count(ip.String(), ":") >= 2
162+
}

deployment/base/rbac/worker-role.yaml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
apiVersion: rbac.authorization.k8s.io/v1
2-
kind: Role
2+
kind: ClusterRole
33
metadata:
44
name: nfd-worker
55
rules:
@@ -14,7 +14,8 @@ rules:
1414
- delete
1515
- apiGroups:
1616
- ""
17-
resources:
18-
- pods
19-
verbs:
20-
- get
17+
resources: ["pods"]
18+
verbs: ["get"]
19+
- apiGroups: [""]
20+
resources: ["nodes/proxy"]
21+
verbs: ["get"]

deployment/base/rbac/worker-rolebinding.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
apiVersion: rbac.authorization.k8s.io/v1
2-
kind: RoleBinding
2+
kind: ClusterRoleBinding
33
metadata:
44
name: nfd-worker
55
roleRef:
66
apiGroup: rbac.authorization.k8s.io
7-
kind: Role
7+
kind: ClusterRole
88
name: nfd-worker
99
subjects:
1010
- kind: ServiceAccount

deployment/components/common/env.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,11 @@
1313
valueFrom:
1414
fieldRef:
1515
fieldPath: metadata.uid
16+
- name: NODE_ADDRESS
17+
valueFrom:
18+
fieldRef:
19+
fieldPath: status.hostIP
20+
- name: POD_NAMESPACE
21+
valueFrom:
22+
fieldRef:
23+
fieldPath: metadata.namespace

pkg/nfd-worker/nfd-worker.go

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"net/http"
23+
"net/url"
2324
"os"
2425
"path/filepath"
2526
"regexp"
@@ -38,8 +39,10 @@ import (
3839
"k8s.io/apimachinery/pkg/util/validation"
3940
k8sclient "k8s.io/client-go/kubernetes"
4041
"k8s.io/klog/v2"
42+
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
4143
"k8s.io/utils/ptr"
4244
klogutils "sigs.k8s.io/node-feature-discovery/pkg/utils/klog"
45+
"sigs.k8s.io/node-feature-discovery/pkg/utils/kubeconf"
4346
"sigs.k8s.io/yaml"
4447

4548
apiequality "k8s.io/apimachinery/pkg/api/equality"
@@ -56,7 +59,7 @@ import (
5659
_ "sigs.k8s.io/node-feature-discovery/source/fake"
5760
_ "sigs.k8s.io/node-feature-discovery/source/kernel"
5861
_ "sigs.k8s.io/node-feature-discovery/source/local"
59-
_ "sigs.k8s.io/node-feature-discovery/source/memory"
62+
memory "sigs.k8s.io/node-feature-discovery/source/memory"
6063
_ "sigs.k8s.io/node-feature-discovery/source/network"
6164
_ "sigs.k8s.io/node-feature-discovery/source/pci"
6265
_ "sigs.k8s.io/node-feature-discovery/source/storage"
@@ -94,13 +97,16 @@ type Labels map[string]string
9497

9598
// Args are the command line arguments of NfdWorker.
9699
type Args struct {
97-
ConfigFile string
98-
Klog map[string]*utils.KlogFlagVal
99-
Kubeconfig string
100-
Oneshot bool
101-
Options string
102-
Port int
103-
NoOwnerRefs bool
100+
ConfigFile string
101+
Klog map[string]*utils.KlogFlagVal
102+
Kubeconfig string
103+
Oneshot bool
104+
Options string
105+
Port int
106+
NoOwnerRefs bool
107+
KubeletConfigPath string
108+
KubeletConfigURI string
109+
APIAuthTokenFile string
104110

105111
Overrides ConfigOverrideArgs
106112
}
@@ -124,6 +130,7 @@ type nfdWorker struct {
124130
featureSources []source.FeatureSource
125131
labelSources []source.LabelSource
126132
ownerReference []metav1.OwnerReference
133+
kubeletConfigFunc func() (*kubeletconfigv1beta1.KubeletConfiguration, error)
127134
}
128135

129136
// This ticker can represent infinite and normal intervals.
@@ -169,12 +176,25 @@ func NewNfdWorker(opts ...NfdWorkerOption) (NfdWorker, error) {
169176
stop: make(chan struct{}),
170177
}
171178

179+
if nfd.args.ConfigFile != "" {
180+
nfd.configFilePath = filepath.Clean(nfd.args.ConfigFile)
181+
}
182+
172183
for _, o := range opts {
173184
o.apply(nfd)
174185
}
175186

176-
if nfd.args.ConfigFile != "" {
177-
nfd.configFilePath = filepath.Clean(nfd.args.ConfigFile)
187+
kubeletConfigFunc, err := getKubeletConfigFunc(nfd.args.KubeletConfigURI, nfd.args.APIAuthTokenFile)
188+
if err != nil {
189+
return nil, err
190+
}
191+
192+
nfd = &nfdWorker{
193+
kubeletConfigFunc: kubeletConfigFunc,
194+
}
195+
196+
for _, o := range opts {
197+
o.apply(nfd)
178198
}
179199

180200
// k8sClient might've been set via opts by tests
@@ -239,6 +259,8 @@ func (w *nfdWorker) runFeatureDiscovery() error {
239259
}
240260
// Get the set of feature labels.
241261
labels := createFeatureLabels(w.labelSources, w.config.Core.LabelWhiteList.Regexp)
262+
// Append a label with app=nfd
263+
labels["app"] = "nfd"
242264

243265
// Update the node with the feature labels.
244266
if !w.config.Core.NoPublish {
@@ -255,9 +277,10 @@ func (w *nfdWorker) setOwnerReference() error {
255277
if !w.config.Core.NoOwnerRefs {
256278
// Get pod owner reference
257279
podName := os.Getenv("POD_NAME")
280+
podNamespace := os.Getenv("POD_NAMESPACE")
258281
// Add pod owner reference if it exists
259282
if podName != "" {
260-
if selfPod, err := w.k8sClient.CoreV1().Pods(w.kubernetesNamespace).Get(context.TODO(), podName, metav1.GetOptions{}); err != nil {
283+
if selfPod, err := w.k8sClient.CoreV1().Pods(podNamespace).Get(context.TODO(), podName, metav1.GetOptions{}); err != nil {
261284
klog.ErrorS(err, "failed to get self pod, cannot inherit ownerReference for NodeFeature")
262285
return err
263286
} else {
@@ -312,6 +335,12 @@ func (w *nfdWorker) Run() error {
312335
httpMux.Handle("/metrics", promhttp.HandlerFor(promRegistry, promhttp.HandlerOpts{}))
313336
registerVersion(version.Get())
314337

338+
klConfig, err := w.kubeletConfigFunc()
339+
if err != nil {
340+
return err
341+
}
342+
memory.SetSwapMode(klConfig.MemorySwap.SwapBehavior)
343+
315344
err = w.runFeatureDiscovery()
316345
if err != nil {
317346
return err
@@ -624,7 +653,7 @@ func (m *nfdWorker) updateNodeFeatureObject(labels Labels) error {
624653
return err
625654
}
626655
nodename := utils.NodeName()
627-
namespace := m.kubernetesNamespace
656+
namespace := os.Getenv("POD_NAMESPACE")
628657

629658
features := source.GetAllFeatures()
630659

@@ -720,3 +749,38 @@ func (c *sourcesConfig) UnmarshalJSON(data []byte) error {
720749

721750
return nil
722751
}
752+
753+
func getKubeletConfigFunc(uri, apiAuthTokenFile string) (func() (*kubeletconfigv1beta1.KubeletConfiguration, error), error) {
754+
u, err := url.ParseRequestURI(uri)
755+
if err != nil {
756+
return nil, fmt.Errorf("failed to parse -kubelet-config-uri: %w", err)
757+
}
758+
759+
// init kubelet API client
760+
var klConfig *kubeletconfigv1beta1.KubeletConfiguration
761+
switch u.Scheme {
762+
case "file":
763+
return func() (*kubeletconfigv1beta1.KubeletConfiguration, error) {
764+
klConfig, err = kubeconf.GetKubeletConfigFromLocalFile(u.Path)
765+
if err != nil {
766+
return nil, fmt.Errorf("failed to read kubelet config: %w", err)
767+
}
768+
return klConfig, err
769+
}, nil
770+
case "https":
771+
restConfig, err := kubeconf.InsecureConfig(u.String(), apiAuthTokenFile)
772+
if err != nil {
773+
return nil, fmt.Errorf("failed to initialize rest config for kubelet config uri: %w", err)
774+
}
775+
776+
return func() (*kubeletconfigv1beta1.KubeletConfiguration, error) {
777+
klConfig, err = kubeconf.GetKubeletConfiguration(restConfig)
778+
if err != nil {
779+
return nil, fmt.Errorf("failed to get kubelet config from configz endpoint: %w", err)
780+
}
781+
return klConfig, nil
782+
}, nil
783+
}
784+
785+
return nil, fmt.Errorf("unsupported URI scheme: %v", u.Scheme)
786+
}

source/memory/memory.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,17 @@ type memorySource struct {
5656

5757
// Singleton source instance
5858
var (
59-
src memorySource
60-
_ source.FeatureSource = &src
61-
_ source.LabelSource = &src
59+
src memorySource
60+
_ source.FeatureSource = &src
61+
_ source.LabelSource = &src
62+
defaultSwapBehavior = "NoSwap"
63+
swapBehavior string
6264
)
6365

66+
func SetSwapMode(behavior string) {
67+
swapBehavior = behavior
68+
}
69+
6470
// Name returns an identifier string for this feature source.
6571
func (s *memorySource) Name() string { return Name }
6672

@@ -80,6 +86,7 @@ func (s *memorySource) GetLabels() (source.FeatureLabels, error) {
8086
// Swap
8187
if isSwap, ok := features.Attributes[SwapFeature].Elements["enabled"]; ok && isSwap == "true" {
8288
labels["swap"] = true
89+
labels["swap.behavior"] = features.Attributes[SwapFeature].Elements["behavior"]
8390
}
8491

8592
// NVDIMM
@@ -106,12 +113,16 @@ func (s *memorySource) Discover() error {
106113
} else {
107114
s.features.Attributes[NumaFeature] = nfdv1alpha1.AttributeFeatureSet{Elements: numa}
108115
}
109-
110-
// Detect Swap
116+
// Detect Swap and Swap Behavior
111117
if swap, err := detectSwap(); err != nil {
112118
klog.ErrorS(err, "failed to detect Swap nodes")
113119
} else {
114120
s.features.Attributes[SwapFeature] = nfdv1alpha1.AttributeFeatureSet{Elements: swap}
121+
if swapBehavior == "" {
122+
swap["behavior"] = defaultSwapBehavior
123+
} else {
124+
swap["behavior"] = swapBehavior
125+
}
115126
}
116127

117128
// Detect NVDIMM

0 commit comments

Comments
 (0)