Skip to content

Commit 2511e44

Browse files
authored
Merge pull request #8164 from MenD32/fix/hard-topology-spread-constraints-stop-scaledown
fix: Cluster Autoscaler not scaling down nodes where Pods with hard topology spread constraints are scheduled
2 parents e69b1a9 + 3a2933a commit 2511e44

File tree

2 files changed

+66
-0
lines changed

2 files changed

+66
-0
lines changed

cluster-autoscaler/simulator/cluster.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ func (r *RemovalSimulator) findPlaceFor(removedNode string, pods []*apiv1.Pod, n
226226
klog.Errorf("Simulating removal of %s/%s return error; %v", pod.Namespace, pod.Name, err)
227227
}
228228
}
229+
// Remove the node from the snapshot, so that it doesn't interfere with topology spread constraint scheduling.
230+
r.clusterSnapshot.RemoveNodeInfo(removedNode)
229231

230232
newpods := make([]*apiv1.Pod, 0, len(pods))
231233
for _, podptr := range pods {

cluster-autoscaler/simulator/cluster_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,59 @@ func TestFindNodesToRemove(t *testing.T) {
140140

141141
clusterSnapshot := testsnapshot.NewTestSnapshotOrDie(t)
142142

143+
topoNode1 := BuildTestNode("topo-n1", 1000, 2000000)
144+
topoNode2 := BuildTestNode("topo-n2", 1000, 2000000)
145+
topoNode3 := BuildTestNode("topo-n3", 1000, 2000000)
146+
topoNode1.Labels = map[string]string{"kubernetes.io/hostname": "topo-n1"}
147+
topoNode2.Labels = map[string]string{"kubernetes.io/hostname": "topo-n2"}
148+
topoNode3.Labels = map[string]string{"kubernetes.io/hostname": "topo-n3"}
149+
150+
SetNodeReadyState(topoNode1, true, time.Time{})
151+
SetNodeReadyState(topoNode2, true, time.Time{})
152+
SetNodeReadyState(topoNode3, true, time.Time{})
153+
154+
minDomains := int32(2)
155+
maxSkew := int32(1)
156+
topoConstraint := apiv1.TopologySpreadConstraint{
157+
MaxSkew: maxSkew,
158+
TopologyKey: "kubernetes.io/hostname",
159+
WhenUnsatisfiable: apiv1.DoNotSchedule,
160+
MinDomains: &minDomains,
161+
LabelSelector: &metav1.LabelSelector{
162+
MatchLabels: map[string]string{
163+
"app": "topo-app",
164+
},
165+
},
166+
}
167+
168+
pod5 := BuildTestPod("p5", 100, 100000)
169+
pod5.Labels = map[string]string{"app": "topo-app"}
170+
pod5.OwnerReferences = ownerRefs
171+
pod5.Spec.NodeName = "topo-n1"
172+
pod5.Spec.TopologySpreadConstraints = []apiv1.TopologySpreadConstraint{topoConstraint}
173+
174+
pod6 := BuildTestPod("p6", 100, 100000)
175+
pod6.Labels = map[string]string{"app": "topo-app"}
176+
pod6.OwnerReferences = ownerRefs
177+
pod6.Spec.NodeName = "topo-n2"
178+
pod6.Spec.TopologySpreadConstraints = []apiv1.TopologySpreadConstraint{topoConstraint}
179+
180+
pod7 := BuildTestPod("p7", 100, 100000)
181+
pod7.Labels = map[string]string{"app": "topo-app"}
182+
pod7.OwnerReferences = ownerRefs
183+
pod7.Spec.NodeName = "topo-n3"
184+
pod7.Spec.TopologySpreadConstraints = []apiv1.TopologySpreadConstraint{topoConstraint}
185+
186+
blocker1 := BuildTestPod("blocker1", 100, 100000)
187+
blocker1.Spec.NodeName = "topo-n2"
188+
blocker2 := BuildTestPod("blocker2", 100, 100000)
189+
blocker2.Spec.NodeName = "topo-n3"
190+
191+
topoNodeToRemove := NodeToBeRemoved{
192+
Node: topoNode1,
193+
PodsToReschedule: []*apiv1.Pod{pod5},
194+
}
195+
143196
tests := []findNodesToRemoveTestConfig{
144197
{
145198
name: "just an empty node, should be removed",
@@ -176,6 +229,17 @@ func TestFindNodesToRemove(t *testing.T) {
176229
allNodes: []*apiv1.Node{emptyNode, drainableNode, fullNode, nonDrainableNode},
177230
toRemove: []NodeToBeRemoved{emptyNodeToRemove, drainableNodeToRemove},
178231
},
232+
{
233+
name: "topology spread constraint test - one node should be removable",
234+
pods: []*apiv1.Pod{pod5, pod6, pod7, blocker1, blocker2},
235+
allNodes: []*apiv1.Node{topoNode1, topoNode2, topoNode3},
236+
candidates: []string{topoNode1.Name, topoNode2.Name, topoNode3.Name},
237+
toRemove: []NodeToBeRemoved{topoNodeToRemove},
238+
unremovable: []*UnremovableNode{
239+
{Node: topoNode2, Reason: BlockedByPod, BlockingPod: &drain.BlockingPod{Pod: blocker1, Reason: drain.NotReplicated}},
240+
{Node: topoNode3, Reason: BlockedByPod, BlockingPod: &drain.BlockingPod{Pod: blocker2, Reason: drain.NotReplicated}},
241+
},
242+
},
179243
}
180244

181245
for _, test := range tests {

0 commit comments

Comments
 (0)