Skip to content

Commit 91a79d5

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

File tree

6 files changed

+135
-24
lines changed

6 files changed

+135
-24
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 & 11 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"
@@ -57,6 +60,7 @@ import (
5760
_ "sigs.k8s.io/node-feature-discovery/source/kernel"
5861
_ "sigs.k8s.io/node-feature-discovery/source/local"
5962
_ "sigs.k8s.io/node-feature-discovery/source/memory"
63+
memory "sigs.k8s.io/node-feature-discovery/source/memory"
6064
_ "sigs.k8s.io/node-feature-discovery/source/network"
6165
_ "sigs.k8s.io/node-feature-discovery/source/pci"
6266
_ "sigs.k8s.io/node-feature-discovery/source/storage"
@@ -94,13 +98,16 @@ type Labels map[string]string
9498

9599
// Args are the command line arguments of NfdWorker.
96100
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
101+
ConfigFile string
102+
Klog map[string]*utils.KlogFlagVal
103+
Kubeconfig string
104+
Oneshot bool
105+
Options string
106+
Port int
107+
NoOwnerRefs bool
108+
KubeletConfigPath string
109+
KubeletConfigURI string
110+
APIAuthTokenFile string
104111

105112
Overrides ConfigOverrideArgs
106113
}
@@ -124,6 +131,7 @@ type nfdWorker struct {
124131
featureSources []source.FeatureSource
125132
labelSources []source.LabelSource
126133
ownerReference []metav1.OwnerReference
134+
kubeletConfigFunc func() (*kubeletconfigv1beta1.KubeletConfiguration, error)
127135
}
128136

129137
// This ticker can represent infinite and normal intervals.
@@ -169,12 +177,25 @@ func NewNfdWorker(opts ...NfdWorkerOption) (NfdWorker, error) {
169177
stop: make(chan struct{}),
170178
}
171179

180+
if nfd.args.ConfigFile != "" {
181+
nfd.configFilePath = filepath.Clean(nfd.args.ConfigFile)
182+
}
183+
172184
for _, o := range opts {
173185
o.apply(nfd)
174186
}
175187

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

180201
// k8sClient might've been set via opts by tests
@@ -239,6 +260,8 @@ func (w *nfdWorker) runFeatureDiscovery() error {
239260
}
240261
// Get the set of feature labels.
241262
labels := createFeatureLabels(w.labelSources, w.config.Core.LabelWhiteList.Regexp)
263+
// Append a label with app=nfd
264+
labels["app"] = "nfd"
242265

243266
// Update the node with the feature labels.
244267
if !w.config.Core.NoPublish {
@@ -255,9 +278,10 @@ func (w *nfdWorker) setOwnerReference() error {
255278
if !w.config.Core.NoOwnerRefs {
256279
// Get pod owner reference
257280
podName := os.Getenv("POD_NAME")
281+
podNamespace := os.Getenv("POD_NAMESPACE")
258282
// Add pod owner reference if it exists
259283
if podName != "" {
260-
if selfPod, err := w.k8sClient.CoreV1().Pods(w.kubernetesNamespace).Get(context.TODO(), podName, metav1.GetOptions{}); err != nil {
284+
if selfPod, err := w.k8sClient.CoreV1().Pods(podNamespace).Get(context.TODO(), podName, metav1.GetOptions{}); err != nil {
261285
klog.ErrorS(err, "failed to get self pod, cannot inherit ownerReference for NodeFeature")
262286
return err
263287
} else {
@@ -312,6 +336,12 @@ func (w *nfdWorker) Run() error {
312336
httpMux.Handle("/metrics", promhttp.HandlerFor(promRegistry, promhttp.HandlerOpts{}))
313337
registerVersion(version.Get())
314338

339+
klConfig, err := w.kubeletConfigFunc()
340+
if err != nil {
341+
return err
342+
}
343+
memory.SetSwapMode(klConfig.MemorySwap.SwapBehavior)
344+
315345
err = w.runFeatureDiscovery()
316346
if err != nil {
317347
return err
@@ -624,7 +654,7 @@ func (m *nfdWorker) updateNodeFeatureObject(labels Labels) error {
624654
return err
625655
}
626656
nodename := utils.NodeName()
627-
namespace := m.kubernetesNamespace
657+
namespace := os.Getenv("POD_NAMESPACE")
628658

629659
features := source.GetAllFeatures()
630660

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

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

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)