diff --git a/README.md b/README.md index 1bc44479c2..bcd5c96619 100644 --- a/README.md +++ b/README.md @@ -261,18 +261,36 @@ strategies: ### PodLifeTime -This strategy evicts pods that are older than `.strategies.PodLifeTime.params.maxPodLifeTimeSeconds` The policy -file should look like: +This strategy evicts pods that are older than `maxPodLifeTimeSeconds`. The policy file should look like: -```` +``` apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: "PodLifeTime": enabled: true params: - maxPodLifeTimeSeconds: 86400 -```` + podLifeTime: + maxPodLifeTimeSeconds: 86400 +``` + +You can specify `podStatusPhases` to `only` evict pods with specific `StatusPhases`, currently this parameter is limited +to `Running` and `Pending`. E.g. + +``` +apiVersion: "descheduler/v1alpha1" +kind: "DeschedulerPolicy" +strategies: + "PodLifeTime": + enabled: true + params: + podLifeTime: + maxPodLifeTimeSeconds: 86400 + podStatusPhases: + - "Pending" +``` + +Only `Pending` pods will get evicted in this example. ## Filter Pods @@ -290,7 +308,8 @@ strategies: "PodLifeTime": enabled: true params: - maxPodLifeTimeSeconds: 86400 + podLifeTime: + maxPodLifeTimeSeconds: 86400 namespaces: include: - "namespace1" @@ -307,7 +326,8 @@ strategies: "PodLifeTime": enabled: true params: - maxPodLifeTimeSeconds: 86400 + podLifeTime: + maxPodLifeTimeSeconds: 86400 namespaces: exclude: - "namespace1" @@ -334,7 +354,8 @@ strategies: "PodLifeTime": enabled: true params: - maxPodLifeTimeSeconds: 86400 + podLifeTime: + maxPodLifeTimeSeconds: 86400 thresholdPriority: 10000 ``` @@ -346,7 +367,8 @@ strategies: "PodLifeTime": enabled: true params: - maxPodLifeTimeSeconds: 86400 + podLifeTime: + maxPodLifeTimeSeconds: 86400 thresholdPriorityClassName: "priorityclass1" ``` diff --git a/examples/pod-life-time.yml b/examples/pod-life-time.yml index ad0eb3e085..1bce942893 100644 --- a/examples/pod-life-time.yml +++ b/examples/pod-life-time.yml @@ -17,4 +17,5 @@ strategies: "PodLifeTime": enabled: true params: - maxPodLifeTimeSeconds: 604800 # 7 days + podLifeTime: + maxPodLifeTimeSeconds: 604800 # 7 days diff --git a/pkg/api/types.go b/pkg/api/types.go index 2044e384a0..be899ebb5f 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -67,7 +67,7 @@ type StrategyParameters struct { NodeResourceUtilizationThresholds *NodeResourceUtilizationThresholds NodeAffinityType []string PodsHavingTooManyRestarts *PodsHavingTooManyRestarts - MaxPodLifeTimeSeconds *uint + PodLifeTime *PodLifeTime RemoveDuplicates *RemoveDuplicates Namespaces *Namespaces ThresholdPriority *int32 @@ -91,3 +91,8 @@ type PodsHavingTooManyRestarts struct { type RemoveDuplicates struct { ExcludeOwnerKinds []string } + +type PodLifeTime struct { + MaxPodLifeTimeSeconds *uint + PodStatusPhases []string +} diff --git a/pkg/api/v1alpha1/types.go b/pkg/api/v1alpha1/types.go index eca22681cd..f5717b33d4 100644 --- a/pkg/api/v1alpha1/types.go +++ b/pkg/api/v1alpha1/types.go @@ -65,7 +65,7 @@ type StrategyParameters struct { NodeResourceUtilizationThresholds *NodeResourceUtilizationThresholds `json:"nodeResourceUtilizationThresholds,omitempty"` NodeAffinityType []string `json:"nodeAffinityType,omitempty"` PodsHavingTooManyRestarts *PodsHavingTooManyRestarts `json:"podsHavingTooManyRestarts,omitempty"` - MaxPodLifeTimeSeconds *uint `json:"maxPodLifeTimeSeconds,omitempty"` + PodLifeTime *PodLifeTime `json:"podLifeTime,omitempty"` RemoveDuplicates *RemoveDuplicates `json:"removeDuplicates,omitempty"` Namespaces *Namespaces `json:"namespaces"` ThresholdPriority *int32 `json:"thresholdPriority"` @@ -89,3 +89,8 @@ type PodsHavingTooManyRestarts struct { type RemoveDuplicates struct { ExcludeOwnerKinds []string `json:"excludeOwnerKinds,omitempty"` } + +type PodLifeTime struct { + MaxPodLifeTimeSeconds *uint `json:"maxPodLifeTimeSeconds,omitempty"` + PodStatusPhases []string `json:"podStatusPhases,omitempty"` +} diff --git a/pkg/api/v1alpha1/zz_generated.conversion.go b/pkg/api/v1alpha1/zz_generated.conversion.go index 7c1d245307..8d6bbb94bc 100644 --- a/pkg/api/v1alpha1/zz_generated.conversion.go +++ b/pkg/api/v1alpha1/zz_generated.conversion.go @@ -75,6 +75,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*PodLifeTime)(nil), (*api.PodLifeTime)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_PodLifeTime_To_api_PodLifeTime(a.(*PodLifeTime), b.(*api.PodLifeTime), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*api.PodLifeTime)(nil), (*PodLifeTime)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_api_PodLifeTime_To_v1alpha1_PodLifeTime(a.(*api.PodLifeTime), b.(*PodLifeTime), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*PodsHavingTooManyRestarts)(nil), (*api.PodsHavingTooManyRestarts)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts(a.(*PodsHavingTooManyRestarts), b.(*api.PodsHavingTooManyRestarts), scope) }); err != nil { @@ -204,6 +214,28 @@ func Convert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtili return autoConvert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtilizationThresholds(in, out, s) } +func autoConvert_v1alpha1_PodLifeTime_To_api_PodLifeTime(in *PodLifeTime, out *api.PodLifeTime, s conversion.Scope) error { + out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds)) + out.PodStatusPhases = *(*[]string)(unsafe.Pointer(&in.PodStatusPhases)) + return nil +} + +// Convert_v1alpha1_PodLifeTime_To_api_PodLifeTime is an autogenerated conversion function. +func Convert_v1alpha1_PodLifeTime_To_api_PodLifeTime(in *PodLifeTime, out *api.PodLifeTime, s conversion.Scope) error { + return autoConvert_v1alpha1_PodLifeTime_To_api_PodLifeTime(in, out, s) +} + +func autoConvert_api_PodLifeTime_To_v1alpha1_PodLifeTime(in *api.PodLifeTime, out *PodLifeTime, s conversion.Scope) error { + out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds)) + out.PodStatusPhases = *(*[]string)(unsafe.Pointer(&in.PodStatusPhases)) + return nil +} + +// Convert_api_PodLifeTime_To_v1alpha1_PodLifeTime is an autogenerated conversion function. +func Convert_api_PodLifeTime_To_v1alpha1_PodLifeTime(in *api.PodLifeTime, out *PodLifeTime, s conversion.Scope) error { + return autoConvert_api_PodLifeTime_To_v1alpha1_PodLifeTime(in, out, s) +} + func autoConvert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts(in *PodsHavingTooManyRestarts, out *api.PodsHavingTooManyRestarts, s conversion.Scope) error { out.PodRestartThreshold = in.PodRestartThreshold out.IncludingInitContainers = in.IncludingInitContainers @@ -250,7 +282,7 @@ func autoConvert_v1alpha1_StrategyParameters_To_api_StrategyParameters(in *Strat out.NodeResourceUtilizationThresholds = (*api.NodeResourceUtilizationThresholds)(unsafe.Pointer(in.NodeResourceUtilizationThresholds)) out.NodeAffinityType = *(*[]string)(unsafe.Pointer(&in.NodeAffinityType)) out.PodsHavingTooManyRestarts = (*api.PodsHavingTooManyRestarts)(unsafe.Pointer(in.PodsHavingTooManyRestarts)) - out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds)) + out.PodLifeTime = (*api.PodLifeTime)(unsafe.Pointer(in.PodLifeTime)) out.RemoveDuplicates = (*api.RemoveDuplicates)(unsafe.Pointer(in.RemoveDuplicates)) out.Namespaces = (*api.Namespaces)(unsafe.Pointer(in.Namespaces)) out.ThresholdPriority = (*int32)(unsafe.Pointer(in.ThresholdPriority)) @@ -267,7 +299,7 @@ func autoConvert_api_StrategyParameters_To_v1alpha1_StrategyParameters(in *api.S out.NodeResourceUtilizationThresholds = (*NodeResourceUtilizationThresholds)(unsafe.Pointer(in.NodeResourceUtilizationThresholds)) out.NodeAffinityType = *(*[]string)(unsafe.Pointer(&in.NodeAffinityType)) out.PodsHavingTooManyRestarts = (*PodsHavingTooManyRestarts)(unsafe.Pointer(in.PodsHavingTooManyRestarts)) - out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds)) + out.PodLifeTime = (*PodLifeTime)(unsafe.Pointer(in.PodLifeTime)) out.RemoveDuplicates = (*RemoveDuplicates)(unsafe.Pointer(in.RemoveDuplicates)) out.Namespaces = (*Namespaces)(unsafe.Pointer(in.Namespaces)) out.ThresholdPriority = (*int32)(unsafe.Pointer(in.ThresholdPriority)) diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index 79ad51ade5..bdfd49f50c 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -148,6 +148,32 @@ func (in *NodeResourceUtilizationThresholds) DeepCopy() *NodeResourceUtilization return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodLifeTime) DeepCopyInto(out *PodLifeTime) { + *out = *in + if in.MaxPodLifeTimeSeconds != nil { + in, out := &in.MaxPodLifeTimeSeconds, &out.MaxPodLifeTimeSeconds + *out = new(uint) + **out = **in + } + if in.PodStatusPhases != nil { + in, out := &in.PodStatusPhases, &out.PodStatusPhases + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodLifeTime. +func (in *PodLifeTime) DeepCopy() *PodLifeTime { + if in == nil { + return nil + } + out := new(PodLifeTime) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodsHavingTooManyRestarts) DeepCopyInto(out *PodsHavingTooManyRestarts) { *out = *in @@ -247,10 +273,10 @@ func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) { *out = new(PodsHavingTooManyRestarts) **out = **in } - if in.MaxPodLifeTimeSeconds != nil { - in, out := &in.MaxPodLifeTimeSeconds, &out.MaxPodLifeTimeSeconds - *out = new(uint) - **out = **in + if in.PodLifeTime != nil { + in, out := &in.PodLifeTime, &out.PodLifeTime + *out = new(PodLifeTime) + (*in).DeepCopyInto(*out) } if in.RemoveDuplicates != nil { in, out := &in.RemoveDuplicates, &out.RemoveDuplicates diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go index 17603cac07..ac9e9c5d1d 100644 --- a/pkg/api/zz_generated.deepcopy.go +++ b/pkg/api/zz_generated.deepcopy.go @@ -148,6 +148,32 @@ func (in *NodeResourceUtilizationThresholds) DeepCopy() *NodeResourceUtilization return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodLifeTime) DeepCopyInto(out *PodLifeTime) { + *out = *in + if in.MaxPodLifeTimeSeconds != nil { + in, out := &in.MaxPodLifeTimeSeconds, &out.MaxPodLifeTimeSeconds + *out = new(uint) + **out = **in + } + if in.PodStatusPhases != nil { + in, out := &in.PodStatusPhases, &out.PodStatusPhases + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodLifeTime. +func (in *PodLifeTime) DeepCopy() *PodLifeTime { + if in == nil { + return nil + } + out := new(PodLifeTime) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodsHavingTooManyRestarts) DeepCopyInto(out *PodsHavingTooManyRestarts) { *out = *in @@ -247,10 +273,10 @@ func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) { *out = new(PodsHavingTooManyRestarts) **out = **in } - if in.MaxPodLifeTimeSeconds != nil { - in, out := &in.MaxPodLifeTimeSeconds, &out.MaxPodLifeTimeSeconds - *out = new(uint) - **out = **in + if in.PodLifeTime != nil { + in, out := &in.PodLifeTime, &out.PodLifeTime + *out = new(PodLifeTime) + (*in).DeepCopyInto(*out) } if in.RemoveDuplicates != nil { in, out := &in.RemoveDuplicates, &out.RemoveDuplicates diff --git a/pkg/descheduler/strategies/pod_lifetime.go b/pkg/descheduler/strategies/pod_lifetime.go index 9214970d3e..eeb2a96081 100644 --- a/pkg/descheduler/strategies/pod_lifetime.go +++ b/pkg/descheduler/strategies/pod_lifetime.go @@ -32,10 +32,18 @@ import ( ) func validatePodLifeTimeParams(params *api.StrategyParameters) error { - if params == nil || params.MaxPodLifeTimeSeconds == nil { + if params == nil || params.PodLifeTime == nil || params.PodLifeTime.MaxPodLifeTimeSeconds == nil { return fmt.Errorf("MaxPodLifeTimeSeconds not set") } + if params.PodLifeTime.PodStatusPhases != nil { + for _, phase := range params.PodLifeTime.PodStatusPhases { + if phase != string(v1.PodPending) && phase != string(v1.PodRunning) { + return fmt.Errorf("only Pending and Running phases are supported in PodLifeTime") + } + } + } + // At most one of include/exclude can be set if params.Namespaces != nil && len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { return fmt.Errorf("only one of Include/Exclude namespaces can be set") @@ -68,14 +76,26 @@ func PodLifeTime(ctx context.Context, client clientset.Interface, strategy api.D evictable := podEvictor.Evictable(evictions.WithPriorityThreshold(thresholdPriority)) + filter := evictable.IsEvictable + if strategy.Params.PodLifeTime.PodStatusPhases != nil { + filter = func(pod *v1.Pod) bool { + for _, phase := range strategy.Params.PodLifeTime.PodStatusPhases { + if string(pod.Status.Phase) == phase { + return evictable.IsEvictable(pod) + } + } + return false + } + } + for _, node := range nodes { klog.V(1).InfoS("Processing node", "node", klog.KObj(node)) - pods := listOldPodsOnNode(ctx, client, node, includedNamespaces, excludedNamespaces, *strategy.Params.MaxPodLifeTimeSeconds, evictable.IsEvictable) + pods := listOldPodsOnNode(ctx, client, node, includedNamespaces, excludedNamespaces, *strategy.Params.PodLifeTime.MaxPodLifeTimeSeconds, filter) for _, pod := range pods { success, err := podEvictor.EvictPod(ctx, pod, node, "PodLifeTime") if success { - klog.V(1).InfoS("Evicted pod because it exceeded its lifetime", "pod", klog.KObj(pod), "maxPodLifeTime", *strategy.Params.MaxPodLifeTimeSeconds) + klog.V(1).InfoS("Evicted pod because it exceeded its lifetime", "pod", klog.KObj(pod), "maxPodLifeTime", *strategy.Params.PodLifeTime.MaxPodLifeTimeSeconds) } if err != nil { diff --git a/pkg/descheduler/strategies/pod_lifetime_test.go b/pkg/descheduler/strategies/pod_lifetime_test.go index 93323a7f8f..41e361b2c1 100644 --- a/pkg/descheduler/strategies/pod_lifetime_test.go +++ b/pkg/descheduler/strategies/pod_lifetime_test.go @@ -85,6 +85,19 @@ func TestPodLifeTime(t *testing.T) { p5.ObjectMeta.OwnerReferences = ownerRef4 p6.ObjectMeta.OwnerReferences = ownerRef4 + // Setup two old pods with different status phases + p9 := test.BuildTestPod("p9", 100, 0, node.Name, nil) + p9.Namespace = "dev" + p9.ObjectMeta.CreationTimestamp = olderPodCreationTime + p10 := test.BuildTestPod("p10", 100, 0, node.Name, nil) + p10.Namespace = "dev" + p10.ObjectMeta.CreationTimestamp = olderPodCreationTime + + p9.Status.Phase = "Pending" + p10.Status.Phase = "Running" + p9.ObjectMeta.OwnerReferences = ownerRef1 + p10.ObjectMeta.OwnerReferences = ownerRef1 + var maxLifeTime uint = 600 testCases := []struct { description string @@ -98,7 +111,7 @@ func TestPodLifeTime(t *testing.T) { strategy: api.DeschedulerStrategy{ Enabled: true, Params: &api.StrategyParameters{ - MaxPodLifeTimeSeconds: &maxLifeTime, + PodLifeTime: &api.PodLifeTime{MaxPodLifeTimeSeconds: &maxLifeTime}, }, }, maxPodsToEvictPerNode: 5, @@ -110,7 +123,7 @@ func TestPodLifeTime(t *testing.T) { strategy: api.DeschedulerStrategy{ Enabled: true, Params: &api.StrategyParameters{ - MaxPodLifeTimeSeconds: &maxLifeTime, + PodLifeTime: &api.PodLifeTime{MaxPodLifeTimeSeconds: &maxLifeTime}, }, }, maxPodsToEvictPerNode: 5, @@ -122,7 +135,7 @@ func TestPodLifeTime(t *testing.T) { strategy: api.DeschedulerStrategy{ Enabled: true, Params: &api.StrategyParameters{ - MaxPodLifeTimeSeconds: &maxLifeTime, + PodLifeTime: &api.PodLifeTime{MaxPodLifeTimeSeconds: &maxLifeTime}, }, }, maxPodsToEvictPerNode: 5, @@ -134,13 +147,28 @@ func TestPodLifeTime(t *testing.T) { strategy: api.DeschedulerStrategy{ Enabled: true, Params: &api.StrategyParameters{ - MaxPodLifeTimeSeconds: &maxLifeTime, + PodLifeTime: &api.PodLifeTime{MaxPodLifeTimeSeconds: &maxLifeTime}, }, }, maxPodsToEvictPerNode: 5, pods: []v1.Pod{*p7, *p8}, expectedEvictedPodCount: 0, }, + { + description: "Two old pods with different status phases. 1 should be evicted.", + strategy: api.DeschedulerStrategy{ + Enabled: true, + Params: &api.StrategyParameters{ + PodLifeTime: &api.PodLifeTime{ + MaxPodLifeTimeSeconds: &maxLifeTime, + PodStatusPhases: []string{"Pending"}, + }, + }, + }, + maxPodsToEvictPerNode: 5, + pods: []v1.Pod{*p9, *p10}, + expectedEvictedPodCount: 1, + }, } for _, tc := range testCases { diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 5d6b979240..05bafa0e40 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -206,7 +206,7 @@ func runPodLifetimeStrategy(ctx context.Context, clientset clientset.Interface, deschedulerapi.DeschedulerStrategy{ Enabled: true, Params: &deschedulerapi.StrategyParameters{ - MaxPodLifeTimeSeconds: &maxPodLifeTimeSeconds, + PodLifeTime: &deschedulerapi.PodLifeTime{MaxPodLifeTimeSeconds: &maxPodLifeTimeSeconds}, Namespaces: namespaces, ThresholdPriority: priority, ThresholdPriorityClassName: priorityClass,