Skip to content

Commit 2dbf4ce

Browse files
committed
fix race condition when using spec.providerID
node added event is too early for newer k8s clusters so wait for node ready signal to apply settings Signed-off-by: Markus Blaschke <[email protected]>
1 parent 1e9a404 commit 2dbf4ce

File tree

5 files changed

+67
-10
lines changed

5 files changed

+67
-10
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Sets following settings on nodes if matched:
1515
- node annotations
1616
- node [configSource](https://kubernetes.io/docs/tasks/administer-cluster/reconfigure-kubelet/)
1717

18+
Node settings are applied on startup and for new nodes (delayed until they are ready) and (optional) on watch timeout.
19+
1820
Configuration
1921
-------------
2022

@@ -30,8 +32,8 @@ Application Options:
3032
--instance.namespace= Name of namespace where autopilot is running [$INSTANCE_NAMESPACE]
3133
--instance.pod= Name of pod where autopilot is running [$INSTANCE_POD]
3234
--kube.node.labelselector= Node Label selector which nodes should be checked [$KUBE_NODE_LABELSELECTOR]
33-
--kube.watch.timeout= Timeout & full resync for node watch (time.Duration) (default: 24h)
34-
[$KUBE_WATCH_TIMEOUT]
35+
--kube.watch.timeout= Timeout & full resync for node watch (time.Duration) (default: 24h) [$KUBE_WATCH_TIMEOUT]
36+
--kube.watch.reapply Reapply node settings on watch timeout [$KUBE_WATCH_REAPPLY]
3537
--lease.enable Enable lease (leader election; enabled by default in docker images) [$LEASE_ENABLE]
3638
--lease.name= Name of lease lock (default: kube-pool-manager-leader) [$LEASE_NAME]
3739
--dry-run Dry run (do not apply to nodes) [$DRY_RUN]

config/opts.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ type (
2323
}
2424

2525
K8s struct {
26-
NodeLabelSelector string `long:"kube.node.labelselector" env:"KUBE_NODE_LABELSELECTOR" description:"Node Label selector which nodes should be checked" default:""`
27-
WatchTimeout time.Duration `long:"kube.watch.timeout" env:"KUBE_WATCH_TIMEOUT" description:"Timeout & full resync for node watch (time.Duration)" default:"24h"`
26+
NodeLabelSelector string `long:"kube.node.labelselector" env:"KUBE_NODE_LABELSELECTOR" description:"Node Label selector which nodes should be checked" default:""`
27+
WatchTimeout time.Duration `long:"kube.watch.timeout" env:"KUBE_WATCH_TIMEOUT" description:"Timeout & full resync for node watch (time.Duration)" default:"24h"`
28+
ReapplyOnWatchTimeout bool `long:"kube.watch.reapply" env:"KUBE_WATCH_REAPPLY" description:"Reapply node settings on watch timeout"`
2829
}
2930

3031
// lease

example.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ pools:
1717
# sets the kubernetes node role
1818
role: [windows]
1919

20-
- pool: agents
20+
- pool: azure
2121
selector:
2222
- path: "{.spec.providerID}"
2323
# simple match
24-
match: "azure:///subscriptions/d86bcf13-ddf7-45ea-82f1-6f656767a318/resourceGroups/mc_k8s_mblaschke_westeurope/providers/Microsoft.Compute/virtualMachineScaleSets/aks-agents-35471996-vmss/virtualMachines/30"
24+
regexp: "azure://.*"
2525
node:
2626
# sets the kubernetes node role
27-
roles: [agent,foobar]
27+
roles: [azure]
2828

2929
# dynamic kubelet configuration
3030
# see https://kubernetes.io/docs/tasks/administer-cluster/reconfigure-kubelet/

manager/manager.go

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ type (
2727
ctx context.Context
2828
k8sClient *kubernetes.Clientset
2929

30+
nodePatchStatus map[string]bool
31+
3032
prometheus struct {
3133
nodePoolStatus *prometheus.GaugeVec
3234
nodeApplied *prometheus.GaugeVec
@@ -36,6 +38,7 @@ type (
3638

3739
func (m *KubePoolManager) Init() {
3840
m.ctx = context.Background()
41+
m.nodePatchStatus = map[string]bool{}
3942
m.initK8s()
4043
m.initPrometheus()
4144
}
@@ -87,10 +90,19 @@ func (r *KubePoolManager) initK8s() {
8790
func (m *KubePoolManager) Start() {
8891
go func() {
8992
m.leaderElect()
93+
94+
log.Info("initial node pool apply")
95+
m.startupApply()
96+
9097
for {
9198
log.Info("(re)starting node watch")
9299
if err := m.startNodeWatch(); err != nil {
93-
log.Errorf("node watcher stopped: %v", err)
100+
log.Warnf("node watcher stopped: %v", err)
101+
}
102+
103+
if m.Opts.K8s.ReapplyOnWatchTimeout {
104+
log.Info("reapply node pool settings")
105+
m.startupApply()
94106
}
95107
}
96108
}()
@@ -116,6 +128,20 @@ func (m *KubePoolManager) leaderElect() {
116128
}
117129
}
118130

131+
func (m *KubePoolManager) startupApply() {
132+
listOpts := metav1.ListOptions{}
133+
nodeList, err := m.k8sClient.CoreV1().Nodes().List(m.ctx, listOpts)
134+
if err != nil {
135+
log.Panic(err)
136+
}
137+
138+
m.nodePatchStatus = map[string]bool{}
139+
for _, node := range nodeList.Items {
140+
m.nodePatchStatus[node.Name] = true
141+
m.applyNode(&node)
142+
}
143+
}
144+
119145
func (m *KubePoolManager) startNodeWatch() error {
120146
timeout := int64(m.Opts.K8s.WatchTimeout.Seconds())
121147
watchOpts := metav1.ListOptions{
@@ -131,9 +157,20 @@ func (m *KubePoolManager) startNodeWatch() error {
131157

132158
for res := range nodeWatcher.ResultChan() {
133159
switch res.Type {
134-
case watch.Added:
160+
case watch.Modified:
135161
if node, ok := res.Object.(*corev1.Node); ok {
136-
m.applyNode(node)
162+
if _, exists := m.nodePatchStatus[node.Name]; !exists {
163+
m.nodePatchStatus[node.Name] = false
164+
}
165+
166+
if !m.nodePatchStatus[node.Name] && m.checkNodeCondition(node) {
167+
m.applyNode(node)
168+
m.nodePatchStatus[node.Name] = true
169+
}
170+
}
171+
case watch.Deleted:
172+
if node, ok := res.Object.(*corev1.Node); ok {
173+
delete(m.nodePatchStatus, node.Name)
137174
}
138175
case watch.Error:
139176
log.Errorf("go watch error event %v", res.Object)
@@ -143,6 +180,16 @@ func (m *KubePoolManager) startNodeWatch() error {
143180
return fmt.Errorf("terminated")
144181
}
145182

183+
func (m *KubePoolManager) checkNodeCondition(node *corev1.Node) bool {
184+
for _, condition := range node.Status.Conditions {
185+
if stringCompare(string(condition.Type), "ready") && stringCompare(condition.Reason, "kubeletready") && stringCompare(string(condition.Status), "true") {
186+
return true
187+
}
188+
}
189+
190+
return false
191+
}
192+
146193
func (m *KubePoolManager) applyNode(node *corev1.Node) {
147194
contextLogger := log.WithField("node", node.Name)
148195

manager/misc.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package manager
2+
3+
import "strings"
4+
5+
func stringCompare(a, b string) bool {
6+
return strings.EqualFold(a, b)
7+
}

0 commit comments

Comments
 (0)