Skip to content

Commit f5adaa8

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

File tree

10 files changed

+151
-55
lines changed

10 files changed

+151
-55
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/kustomization.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ resources:
1010
- worker-serviceaccount.yaml
1111
- worker-role.yaml
1212
- worker-rolebinding.yaml
13+
- worker-clusterrole.yaml
14+
- worker-clusterrolebinding.yaml
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: rbac.authorization.k8s.io/v1
2+
kind: ClusterRole
3+
metadata:
4+
name: nfd-worker
5+
rules:
6+
- apiGroups:
7+
- ""
8+
resources: ["pods"]
9+
verbs: ["get"]
10+
- apiGroups: [""]
11+
resources: ["nodes/proxy"]
12+
verbs: ["get"]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: rbac.authorization.k8s.io/v1
2+
kind: ClusterRoleBinding
3+
metadata:
4+
name: nfd-worker
5+
roleRef:
6+
apiGroup: rbac.authorization.k8s.io
7+
kind: ClusterRole
8+
name: nfd-worker
9+
subjects:
10+
- kind: ServiceAccount
11+
name: nfd-worker
12+
namespace: default

deployment/base/rbac/worker-role.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ rules:
1717
resources:
1818
- pods
1919
verbs:
20-
- get
20+
- get

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-topology-updater/nfd-topology-updater.go

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package nfdtopologyupdater
1919
import (
2020
"fmt"
2121
"net/http"
22-
"net/url"
2322
"os"
2423
"path/filepath"
2524

@@ -99,7 +98,7 @@ func NewTopologyUpdater(args Args, resourcemonitorArgs resourcemonitor.Args) (Nf
9998
}
10099
go ntf.Run()
101100

102-
kubeletConfigFunc, err := getKubeletConfigFunc(resourcemonitorArgs.KubeletConfigURI, resourcemonitorArgs.APIAuthTokenFile)
101+
kubeletConfigFunc, err := kubeconf.GetKubeletConfigFunc(resourcemonitorArgs.KubeletConfigURI, resourcemonitorArgs.APIAuthTokenFile)
103102
if err != nil {
104103
return nil, err
105104
}
@@ -379,38 +378,3 @@ func updateAttributes(lhs *v1alpha2.AttributeList, rhs v1alpha2.AttributeList) {
379378
updateAttribute(lhs, attr)
380379
}
381380
}
382-
383-
func getKubeletConfigFunc(uri, apiAuthTokenFile string) (func() (*kubeletconfigv1beta1.KubeletConfiguration, error), error) {
384-
u, err := url.ParseRequestURI(uri)
385-
if err != nil {
386-
return nil, fmt.Errorf("failed to parse -kubelet-config-uri: %w", err)
387-
}
388-
389-
// init kubelet API client
390-
var klConfig *kubeletconfigv1beta1.KubeletConfiguration
391-
switch u.Scheme {
392-
case "file":
393-
return func() (*kubeletconfigv1beta1.KubeletConfiguration, error) {
394-
klConfig, err = kubeconf.GetKubeletConfigFromLocalFile(u.Path)
395-
if err != nil {
396-
return nil, fmt.Errorf("failed to read kubelet config: %w", err)
397-
}
398-
return klConfig, err
399-
}, nil
400-
case "https":
401-
restConfig, err := kubeconf.InsecureConfig(u.String(), apiAuthTokenFile)
402-
if err != nil {
403-
return nil, fmt.Errorf("failed to initialize rest config for kubelet config uri: %w", err)
404-
}
405-
406-
return func() (*kubeletconfigv1beta1.KubeletConfiguration, error) {
407-
klConfig, err = kubeconf.GetKubeletConfiguration(restConfig)
408-
if err != nil {
409-
return nil, fmt.Errorf("failed to get kubelet config from configz endpoint: %w", err)
410-
}
411-
return klConfig, nil
412-
}, nil
413-
}
414-
415-
return nil, fmt.Errorf("unsupported URI scheme: %v", u.Scheme)
416-
}

pkg/nfd-worker/nfd-worker.go

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ import (
3838
"k8s.io/apimachinery/pkg/util/validation"
3939
k8sclient "k8s.io/client-go/kubernetes"
4040
"k8s.io/klog/v2"
41+
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
4142
"k8s.io/utils/ptr"
4243
klogutils "sigs.k8s.io/node-feature-discovery/pkg/utils/klog"
44+
"sigs.k8s.io/node-feature-discovery/pkg/utils/kubeconf"
4345
"sigs.k8s.io/yaml"
4446

4547
apiequality "k8s.io/apimachinery/pkg/api/equality"
@@ -56,7 +58,7 @@ import (
5658
_ "sigs.k8s.io/node-feature-discovery/source/fake"
5759
_ "sigs.k8s.io/node-feature-discovery/source/kernel"
5860
_ "sigs.k8s.io/node-feature-discovery/source/local"
59-
_ "sigs.k8s.io/node-feature-discovery/source/memory"
61+
memory "sigs.k8s.io/node-feature-discovery/source/memory"
6062
_ "sigs.k8s.io/node-feature-discovery/source/network"
6163
_ "sigs.k8s.io/node-feature-discovery/source/pci"
6264
_ "sigs.k8s.io/node-feature-discovery/source/storage"
@@ -94,13 +96,16 @@ type Labels map[string]string
9496

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

105110
Overrides ConfigOverrideArgs
106111
}
@@ -124,6 +129,7 @@ type nfdWorker struct {
124129
featureSources []source.FeatureSource
125130
labelSources []source.LabelSource
126131
ownerReference []metav1.OwnerReference
132+
kubeletConfigFunc func() (*kubeletconfigv1beta1.KubeletConfiguration, error)
127133
}
128134

129135
// This ticker can represent infinite and normal intervals.
@@ -169,12 +175,25 @@ func NewNfdWorker(opts ...NfdWorkerOption) (NfdWorker, error) {
169175
stop: make(chan struct{}),
170176
}
171177

178+
if nfd.args.ConfigFile != "" {
179+
nfd.configFilePath = filepath.Clean(nfd.args.ConfigFile)
180+
}
181+
172182
for _, o := range opts {
173183
o.apply(nfd)
174184
}
175185

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

180199
// k8sClient might've been set via opts by tests
@@ -312,6 +331,12 @@ func (w *nfdWorker) Run() error {
312331
httpMux.Handle("/metrics", promhttp.HandlerFor(promRegistry, promhttp.HandlerOpts{}))
313332
registerVersion(version.Get())
314333

334+
klConfig, err := w.kubeletConfigFunc()
335+
if err != nil {
336+
return err
337+
}
338+
memory.SetSwapMode(klConfig.MemorySwap.SwapBehavior)
339+
315340
err = w.runFeatureDiscovery()
316341
if err != nil {
317342
return err
@@ -624,7 +649,7 @@ func (m *nfdWorker) updateNodeFeatureObject(labels Labels) error {
624649
return err
625650
}
626651
nodename := utils.NodeName()
627-
namespace := m.kubernetesNamespace
652+
namespace := os.Getenv("POD_NAMESPACE")
628653

629654
features := source.GetAllFeatures()
630655

pkg/utils/kubeconf/kubelet_config_file.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package kubeconf
1919
import (
2020
"context"
2121
"fmt"
22+
"net/url"
2223

2324
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
2425
kubeletconfigscheme "k8s.io/kubernetes/pkg/kubelet/apis/config/scheme"
@@ -55,3 +56,38 @@ func GetKubeletConfigFromLocalFile(kubeletConfigPath string) (*kubeletconfigv1be
5556

5657
return kubeletConfig, nil
5758
}
59+
60+
func GetKubeletConfigFunc(uri, apiAuthTokenFile string) (func() (*kubeletconfigv1beta1.KubeletConfiguration, error), error) {
61+
u, err := url.ParseRequestURI(uri)
62+
if err != nil {
63+
return nil, fmt.Errorf("failed to parse -kubelet-config-uri: %w", err)
64+
}
65+
66+
// init kubelet API client
67+
var klConfig *kubeletconfigv1beta1.KubeletConfiguration
68+
switch u.Scheme {
69+
case "file":
70+
return func() (*kubeletconfigv1beta1.KubeletConfiguration, error) {
71+
klConfig, err = GetKubeletConfigFromLocalFile(u.Path)
72+
if err != nil {
73+
return nil, fmt.Errorf("failed to read kubelet config: %w", err)
74+
}
75+
return klConfig, err
76+
}, nil
77+
case "https":
78+
restConfig, err := InsecureConfig(u.String(), apiAuthTokenFile)
79+
if err != nil {
80+
return nil, fmt.Errorf("failed to initialize rest config for kubelet config uri: %w", err)
81+
}
82+
83+
return func() (*kubeletconfigv1beta1.KubeletConfiguration, error) {
84+
klConfig, err = GetKubeletConfiguration(restConfig)
85+
if err != nil {
86+
return nil, fmt.Errorf("failed to get kubelet config from configz endpoint: %w", err)
87+
}
88+
return klConfig, nil
89+
}, nil
90+
}
91+
92+
return nil, fmt.Errorf("unsupported URI scheme: %v", u.Scheme)
93+
}

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+
swap["behavior"] = defaultSwapBehavior
122+
if swapBehavior != "" {
123+
swap["behavior"] = swapBehavior
124+
}
125+
115126
}
116127

117128
// Detect NVDIMM

0 commit comments

Comments
 (0)