Skip to content

Commit 0383e62

Browse files
committed
node monitoring: correctly recognize NotReady Nodes as unscheduable (#345)
1 parent abcfaf8 commit 0383e62

File tree

2 files changed

+77
-2
lines changed

2 files changed

+77
-2
lines changed

Diff for: internal/controller/appwrapper/node_health_monitor.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ var (
5656
// noScheduleNodes is a mapping from Node names to ResourceLists of unschedulable resources.
5757
// A resource may be unschedulable either because:
5858
// (a) the Node is cordoned (node.Spec.Unschedulable is true) or
59-
// (b) Autopilot has labeled the Node with a NoExecute or NoSchedule taint for the resource.
59+
// (b) the Node has been marked as NotReady by Kubernetes or
60+
// (c) Autopilot has labeled the Node with a NoExecute or NoSchedule taint for the resource.
6061
noScheduleNodes = make(map[string]v1.ResourceList)
6162
// noScheduleNodesMutex synchronizes access to noScheduleNodes
6263
noScheduleNodesMutex sync.RWMutex
@@ -153,7 +154,7 @@ func (r *NodeHealthMonitor) updateNoExecuteNodes(ctx context.Context, node *v1.N
153154
// update noScheduleNodes entry for node
154155
func (r *NodeHealthMonitor) updateNoScheduleNodes(ctx context.Context, node *v1.Node) {
155156
var noScheduleResources v1.ResourceList
156-
if node.Spec.Unschedulable {
157+
if r.nodeIsUnscheduable(node) {
157158
noScheduleResources = node.Status.Capacity.DeepCopy()
158159
delete(noScheduleResources, v1.ResourcePods)
159160
} else {
@@ -196,6 +197,18 @@ func (r *NodeHealthMonitor) updateNoScheduleNodes(ctx context.Context, node *v1.
196197
}
197198
}
198199

200+
func (r *NodeHealthMonitor) nodeIsUnscheduable(node *v1.Node) bool {
201+
if node.Spec.Unschedulable {
202+
return true
203+
}
204+
for _, taint := range node.Spec.Taints {
205+
if taint.Key == "node.kubernetes.io/unreachable" || taint.Key == "node.kubernetes.io/not-ready" {
206+
return true
207+
}
208+
}
209+
return false
210+
}
211+
199212
// SetupWithManager sets up the controller with the Manager.
200213
func (r *NodeHealthMonitor) SetupWithManager(mgr ctrl.Manager) error {
201214
return ctrl.NewControllerManagedBy(mgr).

Diff for: internal/controller/appwrapper/node_health_monitor_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,13 @@ var _ = Describe("NodeMonitor Controller", func() {
4545
Expect(k8sClient.Create(ctx, node)).To(Succeed())
4646
node = getNode(nodeName)
4747
node.Status.Capacity = nodeGPUs
48+
node.Status.Conditions = append(node.Status.Conditions, v1.NodeCondition{
49+
Type: v1.NodeReady,
50+
Status: v1.ConditionTrue,
51+
})
4852
Expect(k8sClient.Status().Update(ctx, node)).To(Succeed())
53+
node.Spec.Taints = []v1.Taint{}
54+
Expect(k8sClient.Update(ctx, node)).To(Succeed())
4955
}
5056

5157
deleteNode := func(nodeName string) {
@@ -117,6 +123,62 @@ var _ = Describe("NodeMonitor Controller", func() {
117123
Expect(err).NotTo(HaveOccurred())
118124
Expect(len(noExecuteNodes)).Should(Equal(0))
119125

126+
By("A Node tainted as unreachable is detected as unscheduable")
127+
node = getNode(node1Name.Name)
128+
node.Spec.Taints = append(node.Spec.Taints, v1.Taint{Key: "node.kubernetes.io/unreachable", Effect: v1.TaintEffectNoExecute})
129+
Expect(k8sClient.Update(ctx, node)).Should(Succeed())
130+
_, err = nodeMonitor.Reconcile(ctx, reconcile.Request{NamespacedName: node1Name})
131+
Expect(err).NotTo(HaveOccurred())
132+
_, err = nodeMonitor.Reconcile(ctx, reconcile.Request{NamespacedName: node2Name})
133+
Expect(err).NotTo(HaveOccurred())
134+
Expect(noScheduleNodes).Should(HaveLen(1))
135+
Expect(noScheduleNodes).Should(HaveKey(node1Name.Name))
136+
Expect(noScheduleNodes[node1Name.Name]).Should(HaveKey(v1.ResourceName("nvidia.com/gpu")))
137+
138+
By("Repeated reconcile does not change map")
139+
_, err = nodeMonitor.Reconcile(ctx, reconcile.Request{NamespacedName: node1Name})
140+
Expect(err).NotTo(HaveOccurred())
141+
_, err = nodeMonitor.Reconcile(ctx, reconcile.Request{NamespacedName: node2Name})
142+
Expect(err).NotTo(HaveOccurred())
143+
Expect(noScheduleNodes).Should(HaveLen(1))
144+
Expect(noScheduleNodes).Should(HaveKey(node1Name.Name))
145+
Expect(noScheduleNodes[node1Name.Name]).Should(HaveKey(v1.ResourceName("nvidia.com/gpu")))
146+
147+
By("Removing the taint updates unhealthyNodes")
148+
node.Spec.Taints = []v1.Taint{}
149+
Expect(k8sClient.Update(ctx, node)).Should(Succeed())
150+
_, err = nodeMonitor.Reconcile(ctx, reconcile.Request{NamespacedName: node1Name})
151+
Expect(err).NotTo(HaveOccurred())
152+
Expect(noScheduleNodes).Should(BeEmpty())
153+
154+
By("A Node tainted as not-read is detected as unscheduable")
155+
node = getNode(node1Name.Name)
156+
node.Spec.Taints = append(node.Spec.Taints, v1.Taint{Key: "node.kubernetes.io/not-ready", Effect: v1.TaintEffectNoExecute})
157+
Expect(k8sClient.Update(ctx, node)).Should(Succeed())
158+
_, err = nodeMonitor.Reconcile(ctx, reconcile.Request{NamespacedName: node1Name})
159+
Expect(err).NotTo(HaveOccurred())
160+
_, err = nodeMonitor.Reconcile(ctx, reconcile.Request{NamespacedName: node2Name})
161+
Expect(err).NotTo(HaveOccurred())
162+
Expect(noScheduleNodes).Should(HaveLen(1))
163+
Expect(noScheduleNodes).Should(HaveKey(node1Name.Name))
164+
Expect(noScheduleNodes[node1Name.Name]).Should(HaveKey(v1.ResourceName("nvidia.com/gpu")))
165+
166+
By("Repeated reconcile does not change map")
167+
_, err = nodeMonitor.Reconcile(ctx, reconcile.Request{NamespacedName: node1Name})
168+
Expect(err).NotTo(HaveOccurred())
169+
_, err = nodeMonitor.Reconcile(ctx, reconcile.Request{NamespacedName: node2Name})
170+
Expect(err).NotTo(HaveOccurred())
171+
Expect(noScheduleNodes).Should(HaveLen(1))
172+
Expect(noScheduleNodes).Should(HaveKey(node1Name.Name))
173+
Expect(noScheduleNodes[node1Name.Name]).Should(HaveKey(v1.ResourceName("nvidia.com/gpu")))
174+
175+
By("Removing the taint updates unhealthyNodes")
176+
node.Spec.Taints = []v1.Taint{}
177+
Expect(k8sClient.Update(ctx, node)).Should(Succeed())
178+
_, err = nodeMonitor.Reconcile(ctx, reconcile.Request{NamespacedName: node1Name})
179+
Expect(err).NotTo(HaveOccurred())
180+
Expect(noScheduleNodes).Should(BeEmpty())
181+
120182
deleteNode(node1Name.Name)
121183
deleteNode(node2Name.Name)
122184
})

0 commit comments

Comments
 (0)