From 55a0812ae6e6235efdeb4d391135f05602a78546 Mon Sep 17 00:00:00 2001 From: Victor Gonzalez Date: Fri, 26 Jul 2024 14:59:21 +0200 Subject: [PATCH] skip eviction when pod creation time is below minPodAge threshold setting (#1475) * skip eviction when pod creation time is below minPodAge threshold setting In the default initialization phase of the descheduler, add a new constraint to not evict pods that creation time is below minPodAge threshold. Added value: - Avoid crazy pod movement when the autoscaler scales up and down. - Avoid evicting pods when they are warming up. - Decreases the overall cost of eviction as no pod will be evicted before doing significant amount of work. - Guard against scheduling. Descheduling loops in situations where the descheduler has a different node fit logic from scheduler, like not considering topology spread constraints. * Use *time.Duration instead of uint for MinPodAge type * Remove '(in minutes)' from default evictor configuration table * make fmt * Add explicit name for Duration field * Use Duration.String() --- README.md | 1 + .../plugins/defaultevictor/defaultevictor.go | 10 +++++ .../defaultevictor/defaultevictor_test.go | 39 +++++++++++++++++++ pkg/framework/plugins/defaultevictor/types.go | 1 + .../defaultevictor/zz_generated.deepcopy.go | 5 +++ 5 files changed, 56 insertions(+) diff --git a/README.md b/README.md index 2b1401994a..f769660702 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ The Default Evictor Plugin is used by default for filtering pods before processi |`priorityThreshold`|`priorityThreshold`||(see [priority filtering](#priority-filtering))| |`nodeFit`|`bool`|`false`|(see [node fit filtering](#node-fit-filtering))| |`minReplicas`|`uint`|`0`| ignore eviction of pods where owner (e.g. `ReplicaSet`) replicas is below this threshold | +|`minPodAge`|`metav1.Duration`|`0`| ignore eviction of pods with a creation time within this threshold | ### Example policy diff --git a/pkg/framework/plugins/defaultevictor/defaultevictor.go b/pkg/framework/plugins/defaultevictor/defaultevictor.go index ff25ff5c0f..7b20517dbc 100644 --- a/pkg/framework/plugins/defaultevictor/defaultevictor.go +++ b/pkg/framework/plugins/defaultevictor/defaultevictor.go @@ -18,6 +18,7 @@ import ( "context" "errors" "fmt" + "time" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -64,6 +65,7 @@ func HaveEvictAnnotation(pod *v1.Pod) bool { } // New builds plugin from its arguments while passing a handle +// nolint: gocyclo func New(args runtime.Object, handle frameworktypes.Handle) (frameworktypes.Plugin, error) { defaultEvictorArgs, ok := args.(*DefaultEvictorArgs) if !ok { @@ -185,6 +187,14 @@ func New(args runtime.Object, handle frameworktypes.Handle) (frameworktypes.Plug }) } + if defaultEvictorArgs.MinPodAge != nil { + ev.constraints = append(ev.constraints, func(pod *v1.Pod) error { + if pod.Status.StartTime == nil || time.Since(pod.Status.StartTime.Time) < defaultEvictorArgs.MinPodAge.Duration { + return fmt.Errorf("pod age is not older than MinPodAge: %s seconds", defaultEvictorArgs.MinPodAge.String()) + } + return nil + }) + } return ev, nil } diff --git a/pkg/framework/plugins/defaultevictor/defaultevictor_test.go b/pkg/framework/plugins/defaultevictor/defaultevictor_test.go index 16dcad49db..ab3f3c6469 100644 --- a/pkg/framework/plugins/defaultevictor/defaultevictor_test.go +++ b/pkg/framework/plugins/defaultevictor/defaultevictor_test.go @@ -17,6 +17,9 @@ import ( "context" "fmt" "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -42,6 +45,7 @@ type testCase struct { priorityThreshold *int32 nodeFit bool minReplicas uint + minPodAge *metav1.Duration result bool } @@ -326,6 +330,8 @@ func TestDefaultEvictorFilter(t *testing.T) { lowPriority := int32(800) highPriority := int32(900) + minPodAge := metav1.Duration{Duration: 50 * time.Minute} + nodeTaintKey := "hardware" nodeTaintValue := "gpu" @@ -701,6 +707,38 @@ func TestDefaultEvictorFilter(t *testing.T) { }, minReplicas: 2, result: true, + }, { + description: "minPodAge of 50, pod created 10 minutes ago, no eviction", + pods: []*v1.Pod{ + test.BuildTestPod("p1", 1, 1, n1.Name, func(pod *v1.Pod) { + pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList() + podStartTime := metav1.Now().Add(time.Minute * time.Duration(-10)) + pod.Status.StartTime = &metav1.Time{Time: podStartTime} + }), + }, + minPodAge: &minPodAge, + result: false, + }, { + description: "minPodAge of 50, pod created 60 minutes ago, evicts", + pods: []*v1.Pod{ + test.BuildTestPod("p1", 1, 1, n1.Name, func(pod *v1.Pod) { + pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList() + podStartTime := metav1.Now().Add(time.Minute * time.Duration(-60)) + pod.Status.StartTime = &metav1.Time{Time: podStartTime} + }), + }, + minPodAge: &minPodAge, + result: true, + }, { + description: "nil minPodAge, pod created 60 minutes ago, evicts", + pods: []*v1.Pod{ + test.BuildTestPod("p1", 1, 1, n1.Name, func(pod *v1.Pod) { + pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList() + podStartTime := metav1.Now().Add(time.Minute * time.Duration(-60)) + pod.Status.StartTime = &metav1.Time{Time: podStartTime} + }), + }, + result: true, }, } @@ -797,6 +835,7 @@ func initializePlugin(ctx context.Context, test testCase) (frameworktypes.Plugin }, NodeFit: test.nodeFit, MinReplicas: test.minReplicas, + MinPodAge: test.minPodAge, } evictorPlugin, err := New( diff --git a/pkg/framework/plugins/defaultevictor/types.go b/pkg/framework/plugins/defaultevictor/types.go index 67f9a55b98..5c016ccbc1 100644 --- a/pkg/framework/plugins/defaultevictor/types.go +++ b/pkg/framework/plugins/defaultevictor/types.go @@ -35,4 +35,5 @@ type DefaultEvictorArgs struct { PriorityThreshold *api.PriorityThreshold `json:"priorityThreshold"` NodeFit bool `json:"nodeFit"` MinReplicas uint `json:"minReplicas"` + MinPodAge *metav1.Duration `json:"minPodAge"` } diff --git a/pkg/framework/plugins/defaultevictor/zz_generated.deepcopy.go b/pkg/framework/plugins/defaultevictor/zz_generated.deepcopy.go index 5b5808afa7..9d1746e853 100644 --- a/pkg/framework/plugins/defaultevictor/zz_generated.deepcopy.go +++ b/pkg/framework/plugins/defaultevictor/zz_generated.deepcopy.go @@ -41,6 +41,11 @@ func (in *DefaultEvictorArgs) DeepCopyInto(out *DefaultEvictorArgs) { *out = new(api.PriorityThreshold) (*in).DeepCopyInto(*out) } + if in.MinPodAge != nil { + in, out := &in.MinPodAge, &out.MinPodAge + *out = new(v1.Duration) + **out = **in + } return }