Skip to content

Commit b8e3c0b

Browse files
authored
Merge pull request #1642 from ingvagabund/unconstrainedevictions
lownodeutilization: evictionLimits to limit the evictions per plugin
2 parents d883c8a + 5bf1181 commit b8e3c0b

9 files changed

+127
-3
lines changed

README.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ See `metricsCollector` field at [Top Level configuration](#top-level-configurati
300300
|`thresholds`|map(string:int)|
301301
|`targetThresholds`|map(string:int)|
302302
|`numberOfNodes`|int|
303+
|`evictionLimits`|object|
303304
|`evictableNamespaces`|(see [namespace filtering](#namespace-filtering))|
304305
|`metricsUtilization`|object|
305306
|`metricsUtilization.metricsServer`|bool|
@@ -325,6 +326,8 @@ profiles:
325326
"pods": 50
326327
metricsUtilization:
327328
metricsServer: true
329+
evictionLimits:
330+
node: 5
328331
plugins:
329332
balance:
330333
enabled:
@@ -340,10 +343,12 @@ and will not be used to compute node's usage if it's not specified in `threshold
340343
* The valid range of the resource's percentage value is \[0, 100\]
341344
* Percentage value of `thresholds` can not be greater than `targetThresholds` for the same resource.
342345

343-
There is another parameter associated with the `LowNodeUtilization` strategy, called `numberOfNodes`.
344-
This parameter can be configured to activate the strategy only when the number of under utilized nodes
346+
There are two more parameters associated with the `LowNodeUtilization` strategy, called `numberOfNodes` and `evictionLimits`.
347+
The first parameter can be configured to activate the strategy only when the number of under utilized nodes
345348
are above the configured value. This could be helpful in large clusters where a few nodes could go
346349
under utilized frequently or for a short period of time. By default, `numberOfNodes` is set to zero.
350+
The second parameter is useful when a number of evictions per the plugin per a descheduling cycle needs to be limited.
351+
The parameter currently enables to limit the number of evictions per node through `node` field.
347352

348353
### HighNodeUtilization
349354

pkg/api/types.go

+6
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ type Namespaces struct {
6464
Exclude []string `json:"exclude,omitempty"`
6565
}
6666

67+
// EvictionLimits limits the number of evictions per domain. E.g. node, namespace, total.
68+
type EvictionLimits struct {
69+
// node restricts the maximum number of evictions per node
70+
Node *uint `json:"node,omitempty"`
71+
}
72+
6773
type (
6874
Percentage float64
6975
ResourceThresholds map[v1.ResourceName]Percentage

pkg/api/zz_generated.deepcopy.go

+21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/framework/plugins/nodeutilization/highnodeutilization.go

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ func (h *HighNodeUtilization) Balance(ctx context.Context, nodes []*v1.Node) *fr
161161
h.resourceNames,
162162
continueEvictionCond,
163163
h.usageClient,
164+
nil,
164165
)
165166

166167
return nil

pkg/framework/plugins/nodeutilization/lownodeutilization.go

+6
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ func (l *LowNodeUtilization) Balance(ctx context.Context, nodes []*v1.Node) *fra
183183
// Sort the nodes by the usage in descending order
184184
sortNodesByUsage(sourceNodes, false)
185185

186+
var nodeLimit *uint
187+
if l.args.EvictionLimits != nil {
188+
nodeLimit = l.args.EvictionLimits.Node
189+
}
190+
186191
evictPodsFromSourceNodes(
187192
ctx,
188193
l.args.EvictableNamespaces,
@@ -194,6 +199,7 @@ func (l *LowNodeUtilization) Balance(ctx context.Context, nodes []*v1.Node) *fra
194199
l.resourceNames,
195200
continueEvictionCond,
196201
l.usageClient,
202+
nodeLimit,
197203
)
198204

199205
return nil

pkg/framework/plugins/nodeutilization/lownodeutilization_test.go

+69
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
core "k8s.io/client-go/testing"
3232
"k8s.io/metrics/pkg/apis/metrics/v1beta1"
3333
fakemetricsclient "k8s.io/metrics/pkg/client/clientset/versioned/fake"
34+
"k8s.io/utils/ptr"
3435

3536
"sigs.k8s.io/descheduler/pkg/api"
3637
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
@@ -63,6 +64,7 @@ func TestLowNodeUtilization(t *testing.T) {
6364
expectedPodsWithMetricsEvicted uint
6465
evictedPods []string
6566
evictableNamespaces *api.Namespaces
67+
evictionLimits *api.EvictionLimits
6668
}{
6769
{
6870
name: "no evictable pods",
@@ -1122,6 +1124,72 @@ func TestLowNodeUtilization(t *testing.T) {
11221124
expectedPodsEvicted: 3,
11231125
expectedPodsWithMetricsEvicted: 2,
11241126
},
1127+
{
1128+
name: "without priorities with node eviction limit",
1129+
thresholds: api.ResourceThresholds{
1130+
v1.ResourceCPU: 30,
1131+
v1.ResourcePods: 30,
1132+
},
1133+
targetThresholds: api.ResourceThresholds{
1134+
v1.ResourceCPU: 50,
1135+
v1.ResourcePods: 50,
1136+
},
1137+
evictionLimits: &api.EvictionLimits{
1138+
Node: ptr.To[uint](2),
1139+
},
1140+
nodes: []*v1.Node{
1141+
test.BuildTestNode(n1NodeName, 4000, 3000, 9, nil),
1142+
test.BuildTestNode(n2NodeName, 4000, 3000, 10, nil),
1143+
test.BuildTestNode(n3NodeName, 4000, 3000, 10, test.SetNodeUnschedulable),
1144+
},
1145+
pods: []*v1.Pod{
1146+
test.BuildTestPod("p1", 400, 0, n1NodeName, test.SetRSOwnerRef),
1147+
test.BuildTestPod("p2", 400, 0, n1NodeName, test.SetRSOwnerRef),
1148+
test.BuildTestPod("p3", 400, 0, n1NodeName, test.SetRSOwnerRef),
1149+
test.BuildTestPod("p4", 400, 0, n1NodeName, test.SetRSOwnerRef),
1150+
test.BuildTestPod("p5", 400, 0, n1NodeName, test.SetRSOwnerRef),
1151+
// These won't be evicted.
1152+
test.BuildTestPod("p6", 400, 0, n1NodeName, test.SetDSOwnerRef),
1153+
test.BuildTestPod("p7", 400, 0, n1NodeName, func(pod *v1.Pod) {
1154+
// A pod with local storage.
1155+
test.SetNormalOwnerRef(pod)
1156+
pod.Spec.Volumes = []v1.Volume{
1157+
{
1158+
Name: "sample",
1159+
VolumeSource: v1.VolumeSource{
1160+
HostPath: &v1.HostPathVolumeSource{Path: "somePath"},
1161+
EmptyDir: &v1.EmptyDirVolumeSource{
1162+
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI),
1163+
},
1164+
},
1165+
},
1166+
}
1167+
// A Mirror Pod.
1168+
pod.Annotations = test.GetMirrorPodAnnotation()
1169+
}),
1170+
test.BuildTestPod("p8", 400, 0, n1NodeName, func(pod *v1.Pod) {
1171+
// A Critical Pod.
1172+
pod.Namespace = "kube-system"
1173+
priority := utils.SystemCriticalPriority
1174+
pod.Spec.Priority = &priority
1175+
}),
1176+
test.BuildTestPod("p9", 400, 0, n2NodeName, test.SetRSOwnerRef),
1177+
},
1178+
nodemetricses: []*v1beta1.NodeMetrics{
1179+
test.BuildNodeMetrics(n1NodeName, 3201, 0),
1180+
test.BuildNodeMetrics(n2NodeName, 401, 0),
1181+
test.BuildNodeMetrics(n3NodeName, 11, 0),
1182+
},
1183+
podmetricses: []*v1beta1.PodMetrics{
1184+
test.BuildPodMetrics("p1", 401, 0),
1185+
test.BuildPodMetrics("p2", 401, 0),
1186+
test.BuildPodMetrics("p3", 401, 0),
1187+
test.BuildPodMetrics("p4", 401, 0),
1188+
test.BuildPodMetrics("p5", 401, 0),
1189+
},
1190+
expectedPodsEvicted: 2,
1191+
expectedPodsWithMetricsEvicted: 2,
1192+
},
11251193
}
11261194

11271195
for _, tc := range testCases {
@@ -1193,6 +1261,7 @@ func TestLowNodeUtilization(t *testing.T) {
11931261
Thresholds: tc.thresholds,
11941262
TargetThresholds: tc.targetThresholds,
11951263
UseDeviationThresholds: tc.useDeviationThresholds,
1264+
EvictionLimits: tc.evictionLimits,
11961265
EvictableNamespaces: tc.evictableNamespaces,
11971266
MetricsUtilization: MetricsUtilization{
11981267
MetricsServer: metricsEnabled,

pkg/framework/plugins/nodeutilization/nodeutilization.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ func evictPodsFromSourceNodes(
239239
resourceNames []v1.ResourceName,
240240
continueEviction continueEvictionCond,
241241
usageClient usageClient,
242+
maxNoOfPodsToEvictPerNode *uint,
242243
) {
243244
// upper bound on total number of pods/cpu/memory and optional extended resources to be moved
244245
totalAvailableUsage := api.ReferencedResourceList{}
@@ -280,7 +281,7 @@ func evictPodsFromSourceNodes(
280281
klog.V(1).InfoS("Evicting pods based on priority, if they have same priority, they'll be evicted based on QoS tiers")
281282
// 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.
282283
podutil.SortPodsBasedOnPriorityLowToHigh(removablePods)
283-
err := evictPods(ctx, evictableNamespaces, removablePods, node, totalAvailableUsage, taintsOfDestinationNodes, podEvictor, evictOptions, continueEviction, usageClient)
284+
err := evictPods(ctx, evictableNamespaces, removablePods, node, totalAvailableUsage, taintsOfDestinationNodes, podEvictor, evictOptions, continueEviction, usageClient, maxNoOfPodsToEvictPerNode)
284285
if err != nil {
285286
switch err.(type) {
286287
case *evictions.EvictionTotalLimitError:
@@ -302,14 +303,20 @@ func evictPods(
302303
evictOptions evictions.EvictOptions,
303304
continueEviction continueEvictionCond,
304305
usageClient usageClient,
306+
maxNoOfPodsToEvictPerNode *uint,
305307
) error {
306308
var excludedNamespaces sets.Set[string]
307309
if evictableNamespaces != nil {
308310
excludedNamespaces = sets.New(evictableNamespaces.Exclude...)
309311
}
310312

313+
var evictionCounter uint = 0
311314
if continueEviction(nodeInfo, totalAvailableUsage) {
312315
for _, pod := range inputPods {
316+
if maxNoOfPodsToEvictPerNode != nil && evictionCounter >= *maxNoOfPodsToEvictPerNode {
317+
klog.V(3).InfoS("Max number of evictions per node per plugin reached", "limit", *maxNoOfPodsToEvictPerNode)
318+
break
319+
}
313320
if !utils.PodToleratesTaints(pod, taintsOfLowNodes) {
314321
klog.V(3).InfoS("Skipping eviction for pod, doesn't tolerate node taint", "pod", klog.KObj(pod))
315322
continue
@@ -334,6 +341,7 @@ func evictPods(
334341
}
335342
err = podEvictor.Evict(ctx, pod, evictOptions)
336343
if err == nil {
344+
evictionCounter++
337345
klog.V(3).InfoS("Evicted pods", "pod", klog.KObj(pod))
338346

339347
for name := range totalAvailableUsage {

pkg/framework/plugins/nodeutilization/types.go

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ type LowNodeUtilizationArgs struct {
3434
// considered while considering resources used by pods
3535
// but then filtered out before eviction
3636
EvictableNamespaces *api.Namespaces `json:"evictableNamespaces,omitempty"`
37+
38+
// evictionLimits limits the number of evictions per domain. E.g. node, namespace, total.
39+
EvictionLimits *api.EvictionLimits `json:"evictionLimits,omitempty"`
3740
}
3841

3942
// +k8s:deepcopy-gen=true

pkg/framework/plugins/nodeutilization/zz_generated.deepcopy.go

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)