Skip to content

Commit dbdb572

Browse files
authored
feat(selectors): Extract selectors from daemonsets if not provided through config (#33)
1 parent 24c95a5 commit dbdb572

File tree

7 files changed

+226
-80
lines changed

7 files changed

+226
-80
lines changed

charts/nidhogg/templates/rbac.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ rules:
3131
- get
3232
- list
3333
- watch
34+
- apiGroups:
35+
- "apps"
36+
resources:
37+
- daemonsets
38+
verbs:
39+
- get
40+
- list
41+
- watch
3442
- apiGroups:
3543
- ""
3644
resources:

cmd/manager/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package main
1818
import (
1919
"flag"
2020
"os"
21+
"strings"
22+
2123
"sigs.k8s.io/controller-runtime/pkg/log/zap"
2224

2325
"github.com/uswitch/nidhogg/pkg/apis"
@@ -57,7 +59,11 @@ func main() {
5759
os.Exit(1)
5860
}
5961

60-
log.Info("looking for nodes that match selector", "selector", handlerConf.Selector.String())
62+
if handlerConf.NodeSelector == nil {
63+
log.Info("looking for nodes that will match daemonsets selectors")
64+
} else {
65+
log.Info("looking for nodes that match provided node selector", "selector", strings.Join(handlerConf.NodeSelector, ","))
66+
}
6167

6268
// Get a config to talk to the apiserver
6369
log.Info("setting up client for manager")

docs/README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ Nidhogg requires a yaml/json config file to tell it what Daemonsets to watch and
1818

1919
| Attribute name | Required/Optional | Description |
2020
| :--- | :--- | :--- |
21-
| `nodeSelector` | Required | Map of keys/values corresponding to node labels |
2221
| `daemonsets` | Required | Array of Daemonsets to watch, each containing two fields `name` and `namespace` |
22+
| `nodeSelector` | Optional | Map of keys/values corresponding to node labels, will default to get selectors from daemonsets directly if not provided |
2323
| `taintNamePrefix` | Optional | Prefix of the taint name, defaults to `nidhogg.uswitch.com` if not specified |
2424
| `taintRemovalDelayInSeconds` | Optional | Delay to apply before removing taint on the node when ready, defaults to 0 if not specified |
2525

@@ -29,30 +29,30 @@ Example:
2929

3030
YAML:
3131
```yaml
32+
daemonsets:
33+
- name: kiam
34+
namespace: kube-system
3235
nodeSelector:
3336
- "node-role.kubernetes.io/node"
3437
- "!node-role.kubernetes.io/master"
3538
- "aws.amazon.com/ec2.asg.name in (standard, special)"
36-
daemonsets:
37-
- name: kiam
38-
namespace: kube-system
3939
taintNamePrefix: "nidhogg.uswitch.com"
4040
taintRemovalDelayInSeconds: 10
4141
```
4242
JSON:
4343
```json
4444
{
45-
"nodeSelector": [
46-
"node-role.kubernetes.io/node",
47-
"!node-role.kubernetes.io/master",
48-
"aws.amazon.com/ec2.asg.name in (standard, special)"
49-
],
5045
"daemonsets": [
5146
{
5247
"name": "kiam",
5348
"namespace": "kube-system"
5449
}
5550
],
51+
"nodeSelector": [
52+
"node-role.kubernetes.io/node",
53+
"!node-role.kubernetes.io/master",
54+
"aws.amazon.com/ec2.asg.name in (standard, special)"
55+
],
5656
"taintNamePrefix": "nidhogg.uswitch.com",
5757
"taintRemovalDelayInSeconds": 10
5858
}

pkg/controller/node/node_controller_suite_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ package node
1717

1818
import (
1919
"context"
20-
"github.com/onsi/gomega"
2120
stdlog "log"
2221
"os"
2322
"path/filepath"
24-
"sigs.k8s.io/controller-runtime/pkg/manager"
2523
"sync"
2624
"testing"
2725

26+
"github.com/onsi/gomega"
27+
"sigs.k8s.io/controller-runtime/pkg/manager"
28+
2829
"github.com/uswitch/nidhogg/pkg/apis"
2930
"k8s.io/client-go/kubernetes/scheme"
3031
"k8s.io/client-go/rest"

pkg/controller/node/node_controller_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ package node
1717

1818
import (
1919
"context"
20+
"testing"
21+
"time"
22+
2023
"github.com/onsi/gomega"
2124
"github.com/uswitch/nidhogg/pkg/nidhogg"
2225
corev1 "k8s.io/api/core/v1"
2326
apierrors "k8s.io/apimachinery/pkg/api/errors"
2427
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2528
"sigs.k8s.io/controller-runtime/pkg/manager"
26-
"testing"
27-
"time"
2829

2930
"k8s.io/apimachinery/pkg/types"
3031
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -47,10 +48,10 @@ func TestReconcile(t *testing.T) {
4748
g.Expect(err).NotTo(gomega.HaveOccurred())
4849
c = mgr.GetClient()
4950

50-
handler := nidhogg.HandlerConfig{}
51-
_ = handler.BuildSelectors()
51+
handlerConfig := nidhogg.HandlerConfig{}
52+
_ = handlerConfig.BuildSelectors()
5253

53-
recFn, requests := SetupTestReconcile(newReconciler(mgr, handler))
54+
recFn, requests := SetupTestReconcile(newReconciler(mgr, handlerConfig))
5455
g.Expect(add(mgr, recFn)).NotTo(gomega.HaveOccurred())
5556

5657
_, cancel, mgrStopped := StartTestManager(mgr, g)

pkg/nidhogg/handler.go

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package nidhogg
33
import (
44
"context"
55
"fmt"
6-
"github.com/uswitch/nidhogg/pkg/utils"
7-
"k8s.io/apimachinery/pkg/api/errors"
86
"reflect"
97
"strings"
108
"time"
119

10+
"github.com/uswitch/nidhogg/pkg/utils"
11+
"k8s.io/apimachinery/pkg/api/errors"
12+
1213
"github.com/prometheus/client_golang/prometheus"
14+
appsv1 "k8s.io/api/apps/v1"
1315
corev1 "k8s.io/api/core/v1"
1416
"k8s.io/apimachinery/pkg/labels"
1517
"k8s.io/apimachinery/pkg/types"
@@ -66,20 +68,25 @@ type HandlerConfig struct {
6668
TaintNamePrefix string `json:"taintNamePrefix,omitempty" yaml:"taintNamePrefix,omitempty"`
6769
TaintRemovalDelayInSeconds int `json:"taintRemovalDelayInSeconds,omitempty" yaml:"taintRemovalDelayInSeconds,omitempty"`
6870
Daemonsets []Daemonset `json:"daemonsets" yaml:"daemonsets"`
69-
NodeSelector []string `json:"nodeSelector" yaml:"nodeSelector"`
70-
Selector labels.Selector
71+
NodeSelector []string `json:"nodeSelector,omitempty" yaml:"nodeSelector,omitempty"`
72+
DaemonsetSelectors map[Daemonset]labels.Selector
7173
}
7274

7375
func (hc *HandlerConfig) BuildSelectors() error {
74-
hc.Selector = labels.Everything()
76+
hc.DaemonsetSelectors = make(map[Daemonset]labels.Selector)
77+
globalSelector := labels.Nothing()
7578
for _, rawSelector := range hc.NodeSelector {
7679
if selector, err := labels.Parse(rawSelector); err != nil {
7780
return fmt.Errorf("error parsing selector: %v", err)
7881
} else {
7982
requirements, _ := selector.Requirements()
80-
hc.Selector = hc.Selector.Add(requirements...)
83+
globalSelector = labels.NewSelector().Add(requirements...)
8184
}
8285
}
86+
//Will initialize all daemonsets with the same selector, either representing the NodeSelector config or labels.Nothing if no config was provided for NodeSelector
87+
for _, daemonset := range hc.Daemonsets {
88+
hc.DaemonsetSelectors[daemonset] = globalSelector
89+
}
8390
return nil
8491
}
8592

@@ -116,15 +123,10 @@ func (h *Handler) HandleNode(ctx context.Context, request reconcile.Request) (re
116123
return reconcile.Result{}, err
117124
}
118125

119-
//check whether nodeName matches the nodeSelector
120-
if !h.config.Selector.Matches(labels.Set(latestNode.Labels)) {
121-
return reconcile.Result{}, nil
122-
}
123-
124126
updatedNode, taintChanges, err := h.calculateTaints(ctx, latestNode)
125127
if err != nil {
126128
taintOperationErrors.WithLabelValues("calculateTaints").Inc()
127-
return reconcile.Result{}, fmt.Errorf("error caluclating taints for nodeName: %v", err)
129+
return reconcile.Result{}, fmt.Errorf("error calculating taints for nodeName: %v", err)
128130
}
129131

130132
taintLess := true
@@ -179,6 +181,18 @@ func (h *Handler) HandleNode(ctx context.Context, request reconcile.Request) (re
179181
return reconcile.Result{}, nil
180182
}
181183

184+
func (h *Handler) getSelectorFromDaemonSet(ctx context.Context, daemonset Daemonset) (labels.Selector, error) {
185+
ds := &appsv1.DaemonSet{}
186+
err := h.Get(ctx, types.NamespacedName{Namespace: daemonset.Namespace, Name: daemonset.Name}, ds)
187+
if err != nil {
188+
logf.Log.Info(fmt.Sprintf("Could not fetch daemonset %s from namespace %s", daemonset.Name, daemonset.Namespace))
189+
return nil, err
190+
}
191+
selector := labels.SelectorFromSet(ds.Spec.Template.Spec.NodeSelector)
192+
193+
return selector, nil
194+
}
195+
182196
func (h *Handler) calculateTaints(ctx context.Context, instance *corev1.Node) (*corev1.Node, taintChanges, error) {
183197

184198
nodeCopy := instance.DeepCopy()
@@ -195,28 +209,42 @@ func (h *Handler) calculateTaints(ctx context.Context, instance *corev1.Node) (*
195209
}
196210
for _, daemonset := range h.config.Daemonsets {
197211

198-
taint := fmt.Sprintf("%s/%s.%s", h.getTaintNamePrefix(), daemonset.Namespace, daemonset.Name)
199-
// Get Pod for nodeName
200-
pods, err := h.getDaemonsetPods(ctx, instance.Name, daemonset)
201-
if err != nil {
202-
return nil, taintChanges{}, fmt.Errorf("error fetching pods: %v", err)
212+
//If NodeSelector was not provided upfront through config
213+
if h.config.NodeSelector == nil {
214+
//Will try to get selectors from daemonset directly
215+
selector, err := h.getSelectorFromDaemonSet(ctx, daemonset)
216+
if err != nil {
217+
logf.Log.Info(fmt.Sprintf("Could not fetch selector from daemonset %s in namespace %s", daemonset.Name, daemonset.Namespace))
218+
} else {
219+
//Override existing daemonset selector with the one freshly retrieved from the daemonset
220+
h.config.DaemonsetSelectors[daemonset] = selector
221+
}
203222
}
204223

205-
if len(pods) > 0 && utils.AllTrue(pods, func(pod *corev1.Pod) bool { return podReady(pod) }) {
206-
// if the taint is in the taintsToRemove map, it'll be removed
207-
continue
208-
}
209-
// pod doesn't exist or is not ready
210-
_, ok := taintsToRemove[taint]
211-
if ok {
212-
// we want to keep this already existing taint on it
213-
delete(taintsToRemove, taint)
214-
continue
224+
//make sure daemonset selector matches node selector
225+
if h.config.DaemonsetSelectors[daemonset].Matches(labels.Set(instance.Labels)) {
226+
taint := fmt.Sprintf("%s/%s.%s", h.getTaintNamePrefix(), daemonset.Namespace, daemonset.Name)
227+
// Get Pod for nodeName
228+
pods, err := h.getDaemonsetPods(ctx, instance.Name, daemonset)
229+
if err != nil {
230+
return nil, taintChanges{}, fmt.Errorf("error fetching pods: %v", err)
231+
}
232+
233+
if len(pods) == 0 || (len(pods) > 0 && !utils.AllTrue(pods, func(pod *corev1.Pod) bool { return podReady(pod) })) {
234+
// pod doesn't exist or is not ready
235+
_, ok := taintsToRemove[taint]
236+
if ok {
237+
// we want to keep this already existing taint on it
238+
delete(taintsToRemove, taint)
239+
} else {
240+
// taint is not already present, adding it
241+
changes.taintsAdded = append(changes.taintsAdded, taint)
242+
nodeCopy.Spec.Taints = addTaint(nodeCopy.Spec.Taints, taint)
243+
}
244+
}
215245
}
216-
// taint is not already present, adding it
217-
changes.taintsAdded = append(changes.taintsAdded, taint)
218-
nodeCopy.Spec.Taints = addTaint(nodeCopy.Spec.Taints, taint)
219246
}
247+
220248
for taint := range taintsToRemove {
221249
h.applyTaintRemovalDelay()
222250
nodeCopy.Spec.Taints = removeTaint(nodeCopy.Spec.Taints, taint)

0 commit comments

Comments
 (0)