Skip to content

Commit e1155ac

Browse files
committed
Deprecate descheduler.alpha.kubernetes.io/evict in favor of descheduler.beta.kubernetes.io/evict with Boolean logic
In the past the descheduler was only checking the presence of descheduler.alpha.kubernetes.io/evict annotation to force a pod to be an eviction candidate. descheduler.alpha.kubernetes.io/evict=false was producing the same effect of descheduler.alpha.kubernetes.io/evict=true. If the user wants to exclude a specific pod from the descheduler actions, the suggested solution was to re-create it setting an higher priority. This could potentially work for best effort pods that can be easily recreated but it's not a viable solution for other use cases like VMs (executed in pod) that can be temporarily excluded from the descheduler action without the need to reboot them. Let's deprecate descheduler.alpha.kubernetes.io/evict introducing descheduler.beta.kubernetes.io/evict with proper Boolean logic. Signed-off-by: Simone Tiraboschi <[email protected]>
1 parent d348480 commit e1155ac

File tree

4 files changed

+168
-53
lines changed

4 files changed

+168
-53
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,10 +1013,16 @@ never evicted because these pods won't be recreated. (Standalone pods in failed
10131013
best effort pods are evicted before burstable and guaranteed pods.
10141014
* All types of pods with the annotation `descheduler.alpha.kubernetes.io/evict` are eligible for eviction. This
10151015
annotation is used to override checks which prevent eviction and users can select which pod is evicted.
1016+
This annotation is deprecated and could be ignored in future releases. Please use `descheduler.beta.kubernetes.io/evict=true|false` instead.
1017+
* `descheduler.beta.kubernetes.io/evict` annotation can be used to selectively force eviction eligibility.
1018+
All types of pods with the annotation `descheduler.beta.kubernetes.io/evict=true` are eligible for eviction.
1019+
This annotation is used to override checks which prevent eviction and users can select which pod is evicted.
10161020
Users should know how and if the pod will be recreated.
10171021
The annotation only affects internal descheduler checks.
1022+
`descheduler.beta.kubernetes.io/evict=false` will force a pod to be considered not eligible for eviction.
10181023
The anti-disruption protection provided by the [/eviction](https://kubernetes.io/docs/concepts/scheduling-eviction/api-eviction/)
10191024
subresource is still respected.
1025+
If both `descheduler.alpha.kubernetes.io/evict` and `descheduler.beta.kubernetes.io/evict` are set at the same time, the value of `descheduler.beta.kubernetes.io/evict` will be consumed.
10201026
* Pods with a non-nil DeletionTimestamp are not evicted by default.
10211027

10221028
Setting `--v=4` or greater on the Descheduler will log all reasons why any pod is not evictable.

pkg/framework/plugins/defaultevictor/defaultevictor.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"errors"
2020
"fmt"
21+
"strconv"
2122
"time"
2223

2324
v1 "k8s.io/api/core/v1"
@@ -34,8 +35,9 @@ import (
3435
)
3536

3637
const (
37-
PluginName = "DefaultEvictor"
38-
evictPodAnnotationKey = "descheduler.alpha.kubernetes.io/evict"
38+
PluginName = "DefaultEvictor"
39+
evictPodAnnotationKeyAlpha = "descheduler.alpha.kubernetes.io/evict"
40+
evictPodAnnotationKeyBeta = "descheduler.beta.kubernetes.io/evict"
3941
)
4042

4143
var _ frameworktypes.EvictorPlugin = &DefaultEvictor{}
@@ -58,10 +60,27 @@ func IsPodEvictableBasedOnPriority(pod *v1.Pod, priority int32) bool {
5860
return pod.Spec.Priority == nil || *pod.Spec.Priority < priority
5961
}
6062

61-
// HaveEvictAnnotation checks if the pod have evict annotation
62-
func HaveEvictAnnotation(pod *v1.Pod) bool {
63-
_, found := pod.ObjectMeta.Annotations[evictPodAnnotationKey]
64-
return found
63+
// HaveEvictAnnotation checks if the pod have evict annotation and its value
64+
func HaveEvictAnnotation(pod *v1.Pod) (bool, bool, string) {
65+
svalue, found := pod.ObjectMeta.Annotations[evictPodAnnotationKeyBeta]
66+
bvalue := false
67+
annotationKey := ""
68+
if found {
69+
annotationKey = evictPodAnnotationKeyBeta
70+
c, err := strconv.ParseBool(svalue)
71+
if err != nil {
72+
bvalue = false
73+
} else {
74+
bvalue = c
75+
}
76+
} else {
77+
// backward compatibility
78+
if _, found = pod.ObjectMeta.Annotations[evictPodAnnotationKeyAlpha]; found {
79+
annotationKey = evictPodAnnotationKeyAlpha
80+
bvalue = true
81+
}
82+
}
83+
return bvalue, found, annotationKey
6584
}
6685

6786
// New builds plugin from its arguments while passing a handle
@@ -236,8 +255,11 @@ func (d *DefaultEvictor) PreEvictionFilter(pod *v1.Pod) bool {
236255
func (d *DefaultEvictor) Filter(pod *v1.Pod) bool {
237256
checkErrs := []error{}
238257

239-
if HaveEvictAnnotation(pod) {
240-
return true
258+
if value, found, annotationKey := HaveEvictAnnotation(pod); found {
259+
if value {
260+
return true
261+
}
262+
checkErrs = append(checkErrs, fmt.Errorf("pod is annotated with %v=%v", annotationKey, value))
241263
}
242264

243265
if utils.IsMirrorPod(pod) {

pkg/framework/plugins/defaultevictor/defaultevictor_test.go

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,18 @@ func TestDefaultEvictorFilter(t *testing.T) {
379379
description: "Normal pod eviction with normal ownerRefs and descheduler.alpha.kubernetes.io/evict annotation",
380380
pods: []*v1.Pod{
381381
test.BuildTestPod("p2", 400, 0, n1.Name, func(pod *v1.Pod) {
382-
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
382+
pod.Annotations = map[string]string{evictPodAnnotationKeyAlpha: ""}
383+
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
384+
}),
385+
},
386+
evictLocalStoragePods: false,
387+
evictSystemCriticalPods: false,
388+
result: true,
389+
}, {
390+
description: "Normal pod eviction with normal ownerRefs and descheduler.beta.kubernetes.io/evict=true annotation",
391+
pods: []*v1.Pod{
392+
test.BuildTestPod("p2", 400, 0, n1.Name, func(pod *v1.Pod) {
393+
pod.Annotations = map[string]string{evictPodAnnotationKeyBeta: "true"}
383394
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
384395
}),
385396
},
@@ -397,10 +408,10 @@ func TestDefaultEvictorFilter(t *testing.T) {
397408
evictSystemCriticalPods: false,
398409
result: true,
399410
}, {
400-
description: "Normal pod eviction with replicaSet ownerRefs and descheduler.alpha.kubernetes.io/evict annotation",
411+
description: "Normal pod eviction with replicaSet ownerRefs and descheduler.beta.kubernetes.io/evict=true annotation",
401412
pods: []*v1.Pod{
402413
test.BuildTestPod("p4", 400, 0, n1.Name, func(pod *v1.Pod) {
403-
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
414+
pod.Annotations = map[string]string{evictPodAnnotationKeyBeta: "true"}
404415
pod.ObjectMeta.OwnerReferences = test.GetReplicaSetOwnerRefList()
405416
}),
406417
},
@@ -418,16 +429,41 @@ func TestDefaultEvictorFilter(t *testing.T) {
418429
evictSystemCriticalPods: false,
419430
result: true,
420431
}, {
421-
description: "Normal pod eviction with statefulSet ownerRefs and descheduler.alpha.kubernetes.io/evict annotation",
432+
description: "Normal pod eviction with statefulSet ownerRefs and descheduler.beta.kubernetes.io/evict=true annotation",
422433
pods: []*v1.Pod{
423434
test.BuildTestPod("p19", 400, 0, n1.Name, func(pod *v1.Pod) {
424-
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
435+
pod.Annotations = map[string]string{evictPodAnnotationKeyBeta: "true"}
425436
pod.ObjectMeta.OwnerReferences = test.GetStatefulSetOwnerRefList()
426437
}),
427438
},
428439
evictLocalStoragePods: false,
429440
evictSystemCriticalPods: false,
430441
result: true,
442+
}, {
443+
description: "Pod not evicted because it has descheduler.beta.kubernetes.io/evict=false annotation",
444+
pods: []*v1.Pod{
445+
test.BuildTestPod("p2", 400, 0, n1.Name, func(pod *v1.Pod) {
446+
pod.Annotations = map[string]string{evictPodAnnotationKeyBeta: "false"}
447+
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
448+
}),
449+
},
450+
evictLocalStoragePods: false,
451+
evictSystemCriticalPods: false,
452+
result: false,
453+
}, {
454+
description: "Pod not evicted because it has descheduler.alpha.kubernetes.io/evict but also descheduler.beta.kubernetes.io/evict=false annotation",
455+
pods: []*v1.Pod{
456+
test.BuildTestPod("p2", 400, 0, n1.Name, func(pod *v1.Pod) {
457+
pod.Annotations = map[string]string{
458+
evictPodAnnotationKeyAlpha: "",
459+
evictPodAnnotationKeyBeta: "false",
460+
}
461+
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
462+
}),
463+
},
464+
evictLocalStoragePods: false,
465+
evictSystemCriticalPods: false,
466+
result: false,
431467
}, {
432468
description: "Pod not evicted because it is bound to a PV and evictLocalStoragePods = false",
433469
pods: []*v1.Pod{
@@ -471,10 +507,32 @@ func TestDefaultEvictorFilter(t *testing.T) {
471507
evictSystemCriticalPods: false,
472508
result: true,
473509
}, {
474-
description: "Pod is evicted because it is bound to a PV and evictLocalStoragePods = false, but it has scheduler.alpha.kubernetes.io/evict annotation",
510+
description: "Pod is evicted because it is bound to a PV and evictLocalStoragePods = false, but it has descheduler.alpha.kubernetes.io/evict annotation",
511+
pods: []*v1.Pod{
512+
test.BuildTestPod("p7", 400, 0, n1.Name, func(pod *v1.Pod) {
513+
pod.Annotations = map[string]string{evictPodAnnotationKeyAlpha: ""}
514+
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
515+
pod.Spec.Volumes = []v1.Volume{
516+
{
517+
Name: "sample",
518+
VolumeSource: v1.VolumeSource{
519+
HostPath: &v1.HostPathVolumeSource{Path: "somePath"},
520+
EmptyDir: &v1.EmptyDirVolumeSource{
521+
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI),
522+
},
523+
},
524+
},
525+
}
526+
}),
527+
},
528+
evictLocalStoragePods: false,
529+
evictSystemCriticalPods: false,
530+
result: true,
531+
}, {
532+
description: "Pod is evicted because it is bound to a PV and evictLocalStoragePods = false, but it has descheduler.beta.kubernetes.io/evict=true annotation",
475533
pods: []*v1.Pod{
476534
test.BuildTestPod("p7", 400, 0, n1.Name, func(pod *v1.Pod) {
477-
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
535+
pod.Annotations = map[string]string{evictPodAnnotationKeyBeta: "true"}
478536
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
479537
pod.Spec.Volumes = []v1.Volume{
480538
{
@@ -504,10 +562,10 @@ func TestDefaultEvictorFilter(t *testing.T) {
504562
evictSystemCriticalPods: false,
505563
result: false,
506564
}, {
507-
description: "Pod is evicted because it is part of a daemonSet, but it has scheduler.alpha.kubernetes.io/evict annotation",
565+
description: "Pod is evicted because it is part of a daemonSet, but it has descheduler.beta.kubernetes.io/evict=true annotation",
508566
pods: []*v1.Pod{
509567
test.BuildTestPod("p9", 400, 0, n1.Name, func(pod *v1.Pod) {
510-
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
568+
pod.Annotations = map[string]string{evictPodAnnotationKeyBeta: "true"}
511569
pod.ObjectMeta.OwnerReferences = test.GetDaemonSetOwnerRefList()
512570
}),
513571
},
@@ -526,12 +584,12 @@ func TestDefaultEvictorFilter(t *testing.T) {
526584
evictSystemCriticalPods: false,
527585
result: false,
528586
}, {
529-
description: "Pod is evicted because it is a mirror pod, but it has scheduler.alpha.kubernetes.io/evict annotation",
587+
description: "Pod is evicted because it is a mirror pod, but it has descheduler.beta.kubernetes.io/evict=true annotation",
530588
pods: []*v1.Pod{
531589
test.BuildTestPod("p11", 400, 0, n1.Name, func(pod *v1.Pod) {
532590
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
533591
pod.Annotations = test.GetMirrorPodAnnotation()
534-
pod.Annotations["descheduler.alpha.kubernetes.io/evict"] = "true"
592+
pod.Annotations[evictPodAnnotationKeyBeta] = "true"
535593
}),
536594
},
537595
evictLocalStoragePods: false,
@@ -550,14 +608,14 @@ func TestDefaultEvictorFilter(t *testing.T) {
550608
evictSystemCriticalPods: false,
551609
result: false,
552610
}, {
553-
description: "Pod is evicted because it has system critical priority, but it has scheduler.alpha.kubernetes.io/evict annotation",
611+
description: "Pod is evicted because it has system critical priority, but it has descheduler.beta.kubernetes.io/evict=true annotation",
554612
pods: []*v1.Pod{
555613
test.BuildTestPod("p13", 400, 0, n1.Name, func(pod *v1.Pod) {
556614
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
557615
priority := utils.SystemCriticalPriority
558616
pod.Spec.Priority = &priority
559617
pod.Annotations = map[string]string{
560-
"descheduler.alpha.kubernetes.io/evict": "true",
618+
evictPodAnnotationKeyBeta: "true",
561619
}
562620
}),
563621
},
@@ -577,11 +635,11 @@ func TestDefaultEvictorFilter(t *testing.T) {
577635
priorityThreshold: &lowPriority,
578636
result: false,
579637
}, {
580-
description: "Pod is evicted because it has a priority higher than the configured priority threshold, but it has scheduler.alpha.kubernetes.io/evict annotation",
638+
description: "Pod is evicted because it has a priority higher than the configured priority threshold, but it has descheduler.beta.kubernetes.io/evict=true annotation",
581639
pods: []*v1.Pod{
582640
test.BuildTestPod("p15", 400, 0, n1.Name, func(pod *v1.Pod) {
583641
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
584-
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
642+
pod.Annotations = map[string]string{evictPodAnnotationKeyBeta: "true"}
585643
pod.Spec.Priority = &highPriority
586644
}),
587645
},
@@ -602,11 +660,11 @@ func TestDefaultEvictorFilter(t *testing.T) {
602660
evictSystemCriticalPods: true,
603661
result: true,
604662
}, {
605-
description: "Pod is evicted because it has system critical priority, but evictSystemCriticalPods = true and it has scheduler.alpha.kubernetes.io/evict annotation",
663+
description: "Pod is evicted because it has system critical priority, but evictSystemCriticalPods = true and it has descheduler.beta.kubernetes.io/evict=true annotation",
606664
pods: []*v1.Pod{
607665
test.BuildTestPod("p16", 400, 0, n1.Name, func(pod *v1.Pod) {
608666
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
609-
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
667+
pod.Annotations = map[string]string{evictPodAnnotationKeyBeta: "true"}
610668
priority := utils.SystemCriticalPriority
611669
pod.Spec.Priority = &priority
612670
}),
@@ -627,11 +685,11 @@ func TestDefaultEvictorFilter(t *testing.T) {
627685
priorityThreshold: &lowPriority,
628686
result: true,
629687
}, {
630-
description: "Pod is evicted because it has a priority higher than the configured priority threshold, but evictSystemCriticalPods = true and it has scheduler.alpha.kubernetes.io/evict annotation",
688+
description: "Pod is evicted because it has a priority higher than the configured priority threshold, but evictSystemCriticalPods = true and it has descheduler.beta.kubernetes.io/evict=true annotation",
631689
pods: []*v1.Pod{
632690
test.BuildTestPod("p17", 400, 0, n1.Name, func(pod *v1.Pod) {
633691
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
634-
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
692+
pod.Annotations = map[string]string{evictPodAnnotationKeyBeta: "true"}
635693
pod.Spec.Priority = &highPriority
636694
}),
637695
},

test/e2e/e2e_test.go

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"math"
2424
"os"
2525
"sort"
26+
"strconv"
2627
"strings"
2728
"testing"
2829
"time"
@@ -1211,8 +1212,17 @@ func TestPodLabelSelector(t *testing.T) {
12111212
}
12121213
}
12131214

1214-
func TestEvictAnnotation(t *testing.T) {
1215+
func TestEvictAnnotationFalse(t *testing.T) {
1216+
testEvictAnnotation(t, false)
1217+
}
1218+
1219+
func TestEvictAnnotationTrue(t *testing.T) {
1220+
testEvictAnnotation(t, true)
1221+
}
1222+
1223+
func testEvictAnnotation(t *testing.T, evictAnnotationValue bool) {
12151224
ctx := context.Background()
1225+
const podNumber = 5
12161226

12171227
clientSet, _, nodeLister, _ := initializeClient(ctx, t)
12181228

@@ -1222,18 +1232,26 @@ func TestEvictAnnotation(t *testing.T) {
12221232
}
12231233
defer clientSet.CoreV1().Namespaces().Delete(ctx, testNamespace.Name, metav1.DeleteOptions{})
12241234

1225-
t.Log("Create RC with pods with local storage which require descheduler.alpha.kubernetes.io/evict annotation to be set for eviction")
1226-
rc := RcByNameContainer("test-rc-evict-annotation", testNamespace.Name, int32(5), map[string]string{"test": "annotation"}, nil, "")
1227-
rc.Spec.Template.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
1228-
rc.Spec.Template.Spec.Volumes = []v1.Volume{
1229-
{
1230-
Name: "sample",
1231-
VolumeSource: v1.VolumeSource{
1232-
EmptyDir: &v1.EmptyDirVolumeSource{
1233-
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI),
1235+
if evictAnnotationValue {
1236+
t.Log("Create RC with pods with local storage which require descheduler.beta.kubernetes.io/evict=true annotation to be set for eviction")
1237+
} else {
1238+
t.Log("Create RC with pods with descheduler.beta.kubernetes.io/evict=false annotation to skip eviction")
1239+
}
1240+
1241+
rc := RcByNameContainer("test-rc-evict-annotation", testNamespace.Name, int32(podNumber), map[string]string{"test": "annotation"}, nil, "")
1242+
rc.Spec.Template.Annotations = map[string]string{"descheduler.beta.kubernetes.io/evict": strconv.FormatBool(evictAnnotationValue)}
1243+
if evictAnnotationValue {
1244+
t.Logf("Adding a local storage volume")
1245+
rc.Spec.Template.Spec.Volumes = []v1.Volume{
1246+
{
1247+
Name: "sample",
1248+
VolumeSource: v1.VolumeSource{
1249+
EmptyDir: &v1.EmptyDirVolumeSource{
1250+
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI),
1251+
},
12341252
},
12351253
},
1236-
},
1254+
}
12371255
}
12381256

12391257
if _, err := clientSet.CoreV1().ReplicationControllers(rc.Namespace).Create(ctx, rc, metav1.CreateOptions{}); err != nil {
@@ -1260,25 +1278,36 @@ func TestEvictAnnotation(t *testing.T) {
12601278
t.Log("Running PodLifetime plugin")
12611279
runPodLifetimePlugin(ctx, t, clientSet, nodeLister, nil, "", nil, false, false, nil, nil)
12621280

1263-
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, time.Minute, true, func(ctx context.Context) (bool, error) {
1264-
podList, err = clientSet.CoreV1().Pods(rc.Namespace).List(ctx, metav1.ListOptions{LabelSelector: labels.SelectorFromSet(rc.Spec.Template.Labels).String()})
1265-
if err != nil {
1266-
return false, fmt.Errorf("unable to list pods after running plugin: %v", err)
1267-
}
1281+
if evictAnnotationValue {
1282+
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, time.Minute, true, func(ctx context.Context) (bool, error) {
1283+
podList, err = clientSet.CoreV1().Pods(rc.Namespace).List(ctx, metav1.ListOptions{LabelSelector: labels.SelectorFromSet(rc.Spec.Template.Labels).String()})
1284+
if err != nil {
1285+
return false, fmt.Errorf("unable to list pods after running plugin: %v", err)
1286+
}
12681287

1269-
excludePodNames := getPodNames(podList.Items)
1270-
sort.Strings(excludePodNames)
1271-
t.Logf("Existing pods: %v", excludePodNames)
1288+
existingPodNames := getPodNames(podList.Items)
1289+
sort.Strings(existingPodNames)
1290+
t.Logf("Existing pods: %v", existingPodNames)
1291+
if len(intersectStrings(initialPodNames, existingPodNames)) > 0 {
1292+
t.Logf("Not every pods was evicted")
1293+
return false, nil
1294+
}
12721295

1273-
// validate no pods were deleted
1274-
if len(intersectStrings(initialPodNames, excludePodNames)) > 0 {
1275-
t.Logf("Not every pods was evicted")
1276-
return false, nil
1296+
return true, nil
1297+
}); err != nil {
1298+
t.Fatalf("Error waiting for pods to be deleted: %v", err)
12771299
}
1300+
} else {
1301+
t.Logf("Waiting 10s for eventual deletions")
1302+
time.Sleep(10 * time.Second)
1303+
existingPodNames := getPodNames(podList.Items)
1304+
sort.Strings(existingPodNames)
1305+
t.Logf("Existing pods: %v", existingPodNames)
12781306

1279-
return true, nil
1280-
}); err != nil {
1281-
t.Fatalf("Error waiting for pods to be deleted: %v", err)
1307+
// validate no pods were deleted
1308+
if len(intersectStrings(initialPodNames, existingPodNames)) != podNumber {
1309+
t.Fatalf("None of %v pods are expected to be deleted", initialPodNames)
1310+
}
12821311
}
12831312
}
12841313

0 commit comments

Comments
 (0)