1
0
mirror of https://github.com/kubernetes-sigs/descheduler.git synced 2026-01-26 21:31:18 +01:00

feat: support MaxNoOfPodsToEvictTotal

This commit is contained in:
zhifei92
2024-06-28 11:23:02 +08:00
parent 9d16c28f43
commit e60f525ec6
25 changed files with 184 additions and 25 deletions

View File

@@ -979,11 +979,14 @@ func TestLowNodeUtilizationWithTaints(t *testing.T) {
},
}
var uint0, uint1 uint = 0, 1
tests := []struct {
name string
nodes []*v1.Node
pods []*v1.Pod
evictionsExpected uint
name string
nodes []*v1.Node
pods []*v1.Pod
maxPodsToEvictPerNode *uint
maxPodsToEvictTotal *uint
evictionsExpected uint
}{
{
name: "No taints",
@@ -1039,6 +1042,26 @@ func TestLowNodeUtilizationWithTaints(t *testing.T) {
},
evictionsExpected: 1,
},
{
name: "Pod which tolerates node taint, set maxPodsToEvictTotal(0), should not be expelled",
nodes: []*v1.Node{n1, n3withTaints},
pods: []*v1.Pod{
// Node 1 pods
test.BuildTestPod(fmt.Sprintf("pod_1_%s", n1.Name), 200, 0, n1.Name, test.SetRSOwnerRef),
test.BuildTestPod(fmt.Sprintf("pod_2_%s", n1.Name), 200, 0, n1.Name, test.SetRSOwnerRef),
test.BuildTestPod(fmt.Sprintf("pod_3_%s", n1.Name), 200, 0, n1.Name, test.SetRSOwnerRef),
test.BuildTestPod(fmt.Sprintf("pod_4_%s", n1.Name), 200, 0, n1.Name, test.SetRSOwnerRef),
test.BuildTestPod(fmt.Sprintf("pod_5_%s", n1.Name), 200, 0, n1.Name, test.SetRSOwnerRef),
test.BuildTestPod(fmt.Sprintf("pod_6_%s", n1.Name), 200, 0, n1.Name, test.SetRSOwnerRef),
test.BuildTestPod(fmt.Sprintf("pod_7_%s", n1.Name), 200, 0, n1.Name, test.SetRSOwnerRef),
podThatToleratesTaint,
// Node 3 pods
test.BuildTestPod(fmt.Sprintf("pod_9_%s", n3withTaints.Name), 200, 0, n3withTaints.Name, test.SetRSOwnerRef),
},
maxPodsToEvictPerNode: &uint1,
maxPodsToEvictTotal: &uint0,
evictionsExpected: 0,
},
}
for _, item := range tests {

View File

@@ -274,8 +274,14 @@ func evictPodsFromSourceNodes(
klog.V(1).InfoS("Evicting pods based on priority, if they have same priority, they'll be evicted based on QoS tiers")
// sort the evictable Pods based on priority. This also sorts them based on QoS. If there are multiple pods with same priority, they are sorted based on QoS tiers.
podutil.SortPodsBasedOnPriorityLowToHigh(removablePods)
evictPods(ctx, evictableNamespaces, removablePods, node, totalAvailableUsage, taintsOfDestinationNodes, podEvictor, evictOptions, continueEviction)
err := evictPods(ctx, evictableNamespaces, removablePods, node, totalAvailableUsage, taintsOfDestinationNodes, podEvictor, evictOptions, continueEviction)
if err != nil {
switch err.(type) {
case *evictions.EvictionTotalLimitError:
return
default:
}
}
}
}
@@ -289,7 +295,7 @@ func evictPods(
podEvictor frameworktypes.Evictor,
evictOptions evictions.EvictOptions,
continueEviction continueEvictionCond,
) {
) error {
var excludedNamespaces sets.Set[string]
if evictableNamespaces != nil {
excludedNamespaces = sets.New(evictableNamespaces.Exclude...)
@@ -349,13 +355,14 @@ func evictPods(
continue
}
switch err.(type) {
case *evictions.EvictionNodeLimitError:
return
case *evictions.EvictionNodeLimitError, *evictions.EvictionTotalLimitError:
return err
default:
klog.Errorf("eviction failed: %v", err)
}
}
}
return nil
}
// sortNodesByUsage sorts nodes based on usage according to the given plugin.

View File

@@ -140,6 +140,8 @@ loop:
switch err.(type) {
case *evictions.EvictionNodeLimitError:
continue loop
case *evictions.EvictionTotalLimitError:
return nil
default:
klog.Errorf("eviction failed: %v", err)
}

View File

@@ -156,6 +156,7 @@ func TestPodLifeTime(t *testing.T) {
ignorePvcPods bool
maxPodsToEvictPerNode *uint
maxPodsToEvictPerNamespace *uint
maxPodsToEvictTotal *uint
applyPodsFunc func(pods []*v1.Pod)
}{
{
@@ -314,6 +315,17 @@ func TestPodLifeTime(t *testing.T) {
maxPodsToEvictPerNamespace: utilptr.To[uint](1),
expectedEvictedPodCount: 1,
},
{
description: "1 Oldest pod should be evicted when maxPodsToEvictTotal is set to 1",
args: &PodLifeTimeArgs{
MaxPodLifeTimeSeconds: &maxLifeTime,
},
pods: []*v1.Pod{p1, p2, p9},
nodes: []*v1.Node{node1},
maxPodsToEvictPerNamespace: utilptr.To[uint](2),
maxPodsToEvictTotal: utilptr.To[uint](1),
expectedEvictedPodCount: 1,
},
{
description: "1 Oldest pod should be evicted when maxPodsToEvictPerNode is set to 1",
args: &PodLifeTimeArgs{
@@ -560,7 +572,8 @@ func TestPodLifeTime(t *testing.T) {
eventRecorder,
evictions.NewOptions().
WithMaxPodsToEvictPerNode(tc.maxPodsToEvictPerNode).
WithMaxPodsToEvictPerNamespace(tc.maxPodsToEvictPerNamespace),
WithMaxPodsToEvictPerNamespace(tc.maxPodsToEvictPerNamespace).
WithMaxPodsToEvictTotal(tc.maxPodsToEvictTotal),
)
defaultEvictorFilterArgs := &defaultevictor.DefaultEvictorArgs{

View File

@@ -217,6 +217,8 @@ func (r *RemoveDuplicates) Balance(ctx context.Context, nodes []*v1.Node) *frame
switch err.(type) {
case *evictions.EvictionNodeLimitError:
continue loop
case *evictions.EvictionTotalLimitError:
return nil
default:
klog.Errorf("eviction failed: %v", err)
}

View File

@@ -111,6 +111,8 @@ func (d *RemoveFailedPods) Deschedule(ctx context.Context, nodes []*v1.Node) *fr
switch err.(type) {
case *evictions.EvictionNodeLimitError:
break loop
case *evictions.EvictionTotalLimitError:
return nil
default:
klog.Errorf("eviction failed: %v", err)
}

View File

@@ -131,6 +131,8 @@ func (d *RemovePodsHavingTooManyRestarts) Deschedule(ctx context.Context, nodes
switch err.(type) {
case *evictions.EvictionNodeLimitError:
break loop
case *evictions.EvictionTotalLimitError:
return nil
default:
klog.Errorf("eviction failed: %v", err)
}

View File

@@ -112,6 +112,8 @@ loop:
switch err.(type) {
case *evictions.EvictionNodeLimitError:
continue loop
case *evictions.EvictionTotalLimitError:
return nil
default:
klog.Errorf("eviction failed: %v", err)
}

View File

@@ -121,6 +121,7 @@ func TestPodAntiAffinity(t *testing.T) {
description string
maxPodsToEvictPerNode *uint
maxNoOfPodsToEvictPerNamespace *uint
maxNoOfPodsToEvictTotal *uint
pods []*v1.Pod
expectedEvictedPodCount uint
nodeFit bool
@@ -146,6 +147,14 @@ func TestPodAntiAffinity(t *testing.T) {
nodes: []*v1.Node{node1},
expectedEvictedPodCount: 3,
},
{
description: "Maximum pods to evict (maxNoOfPodsToEvictTotal)",
maxNoOfPodsToEvictPerNamespace: &uint3,
maxNoOfPodsToEvictTotal: &uint1,
pods: []*v1.Pod{p1, p2, p3, p4},
nodes: []*v1.Node{node1},
expectedEvictedPodCount: 1,
},
{
description: "Evict only 1 pod after sorting",
pods: []*v1.Pod{p5, p6, p7},
@@ -237,7 +246,8 @@ func TestPodAntiAffinity(t *testing.T) {
eventRecorder,
evictions.NewOptions().
WithMaxPodsToEvictPerNode(test.maxPodsToEvictPerNode).
WithMaxPodsToEvictPerNamespace(test.maxNoOfPodsToEvictPerNamespace),
WithMaxPodsToEvictPerNamespace(test.maxNoOfPodsToEvictPerNamespace).
WithMaxPodsToEvictTotal(test.maxNoOfPodsToEvictTotal),
)
defaultevictorArgs := &defaultevictor.DefaultEvictorArgs{

View File

@@ -144,6 +144,8 @@ func (d *RemovePodsViolatingNodeAffinity) processNodes(ctx context.Context, node
switch err.(type) {
case *evictions.EvictionNodeLimitError:
break loop
case *evictions.EvictionTotalLimitError:
return nil
default:
klog.Errorf("eviction failed: %v", err)
}

View File

@@ -119,6 +119,7 @@ func TestRemovePodsViolatingNodeAffinity(t *testing.T) {
expectedEvictedPodCount uint
maxPodsToEvictPerNode *uint
maxNoOfPodsToEvictPerNamespace *uint
maxNoOfPodsToEvictTotal *uint
args RemovePodsViolatingNodeAffinityArgs
nodefit bool
}{
@@ -237,6 +238,17 @@ func TestRemovePodsViolatingNodeAffinity(t *testing.T) {
nodes: []*v1.Node{nodeWithoutLabels, nodeWithLabels},
maxNoOfPodsToEvictPerNamespace: &uint1,
},
{
description: "Pod is scheduled on node without matching labels, another schedulable node available, maxNoOfPodsToEvictTotal set to 0, should not be evicted [required affinity]",
expectedEvictedPodCount: 0,
args: RemovePodsViolatingNodeAffinityArgs{
NodeAffinityType: []string{"requiredDuringSchedulingIgnoredDuringExecution"},
},
pods: addPodsToNode(nodeWithoutLabels, nil, "requiredDuringSchedulingIgnoredDuringExecution"),
nodes: []*v1.Node{nodeWithoutLabels, nodeWithLabels},
maxNoOfPodsToEvictPerNamespace: &uint1,
maxNoOfPodsToEvictTotal: &uint0,
},
{
description: "Pod is scheduled on node without matching labels, another schedulable node available, maxNoOfPodsToEvictPerNamespace set to 1, should be evicted [preferred affinity]",
expectedEvictedPodCount: 1,
@@ -363,7 +375,8 @@ func TestRemovePodsViolatingNodeAffinity(t *testing.T) {
eventRecorder,
evictions.NewOptions().
WithMaxPodsToEvictPerNode(tc.maxPodsToEvictPerNode).
WithMaxPodsToEvictPerNamespace(tc.maxNoOfPodsToEvictPerNamespace),
WithMaxPodsToEvictPerNamespace(tc.maxNoOfPodsToEvictPerNamespace).
WithMaxPodsToEvictTotal(tc.maxNoOfPodsToEvictTotal),
)
defaultevictorArgs := &defaultevictor.DefaultEvictorArgs{

View File

@@ -129,6 +129,8 @@ func (d *RemovePodsViolatingNodeTaints) Deschedule(ctx context.Context, nodes []
switch err.(type) {
case *evictions.EvictionNodeLimitError:
break loop
case *evictions.EvictionTotalLimitError:
return nil
default:
klog.Errorf("eviction failed: %v", err)
}

View File

@@ -177,7 +177,7 @@ func TestDeletePodsViolatingNodeTaints(t *testing.T) {
p15 = addTolerationToPod(p15, "testTaint", "test", 1, v1.TaintEffectNoSchedule)
p15 = addTolerationToPod(p15, "testingTaint", "testing", 1, v1.TaintEffectNoSchedule)
var uint1 uint = 1
var uint1, uint2 uint = 1, 2
tests := []struct {
description string
@@ -187,6 +187,7 @@ func TestDeletePodsViolatingNodeTaints(t *testing.T) {
evictSystemCriticalPods bool
maxPodsToEvictPerNode *uint
maxNoOfPodsToEvictPerNamespace *uint
maxNoOfPodsToEvictTotal *uint
expectedEvictedPodCount uint
nodeFit bool
includePreferNoSchedule bool
@@ -209,6 +210,16 @@ func TestDeletePodsViolatingNodeTaints(t *testing.T) {
evictSystemCriticalPods: false,
expectedEvictedPodCount: 1, // p4 gets evicted
},
{
description: "Only <maxNoOfPodsToEvictTotal> number of Pods not tolerating node taint should be evicted",
pods: []*v1.Pod{p1, p5, p6},
nodes: []*v1.Node{node1},
evictLocalStoragePods: false,
evictSystemCriticalPods: false,
maxPodsToEvictPerNode: &uint2,
maxNoOfPodsToEvictTotal: &uint1,
expectedEvictedPodCount: 1, // p5 or p6 gets evicted
},
{
description: "Only <maxPodsToEvictPerNode> number of Pods not tolerating node taint should be evicted",
pods: []*v1.Pod{p1, p5, p6},
@@ -227,6 +238,15 @@ func TestDeletePodsViolatingNodeTaints(t *testing.T) {
maxNoOfPodsToEvictPerNamespace: &uint1,
expectedEvictedPodCount: 1, // p5 or p6 gets evicted
},
{
description: "Only <maxNoOfPodsToEvictPerNamespace> number of Pods not tolerating node taint should be evicted",
pods: []*v1.Pod{p1, p5, p6},
nodes: []*v1.Node{node1},
evictLocalStoragePods: false,
evictSystemCriticalPods: false,
maxNoOfPodsToEvictPerNamespace: &uint1,
expectedEvictedPodCount: 1, // p5 or p6 gets evicted
},
{
description: "Critical pods not tolerating node taint should not be evicted",
pods: []*v1.Pod{p7, p8, p9, p10},
@@ -408,7 +428,8 @@ func TestDeletePodsViolatingNodeTaints(t *testing.T) {
eventRecorder,
evictions.NewOptions().
WithMaxPodsToEvictPerNode(tc.maxPodsToEvictPerNode).
WithMaxPodsToEvictPerNamespace(tc.maxNoOfPodsToEvictPerNamespace),
WithMaxPodsToEvictPerNamespace(tc.maxNoOfPodsToEvictPerNamespace).
WithMaxPodsToEvictTotal(tc.maxNoOfPodsToEvictTotal),
)
defaultevictorArgs := &defaultevictor.DefaultEvictorArgs{

View File

@@ -242,6 +242,8 @@ func (d *RemovePodsViolatingTopologySpreadConstraint) Balance(ctx context.Contex
switch err.(type) {
case *evictions.EvictionNodeLimitError:
nodeLimitExceeded[pod.Spec.NodeName] = true
case *evictions.EvictionTotalLimitError:
return nil
default:
klog.Errorf("eviction failed: %v", err)
}