From 11f1333af7298577ae4fb61d11e5736ab7de281f Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Sat, 11 Jul 2020 20:21:28 +0200 Subject: [PATCH] ListPodsOnANode: allow to include/exclude namespaces Info: field selector is still not properly mocked so it's not possible to unit test it --- pkg/api/types.go | 12 +++- pkg/api/v1alpha1/types.go | 10 +++- pkg/api/v1alpha1/zz_generated.conversion.go | 38 +++++++++++++ pkg/api/v1alpha1/zz_generated.deepcopy.go | 27 +++++++++ pkg/api/zz_generated.deepcopy.go | 27 +++++++++ pkg/descheduler/pod/pods.go | 56 ++++++++++++++++++- pkg/descheduler/strategies/node_affinity.go | 34 ++++++++--- pkg/descheduler/strategies/node_taint.go | 32 ++++++++++- .../strategies/pod_antiaffinity.go | 28 +++++++++- pkg/descheduler/strategies/pod_lifetime.go | 35 ++++++++++-- pkg/descheduler/strategies/toomanyrestarts.go | 27 ++++++++- 11 files changed, 303 insertions(+), 23 deletions(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index e80c780b5..fb3becbea 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -44,13 +44,23 @@ type DeschedulerStrategy struct { Params *StrategyParameters } -// Only one of its members may be specified +// Namespaces carries a list of included/excluded namespaces +// for which a given strategy is applicable +type Namespaces struct { + Include []string + Exclude []string +} + +// Besides Namespaces only one of its members may be specified +// TODO(jchaloup): move Namespaces to individual strategies once the policy +// version is bumped to v1alpha2 type StrategyParameters struct { NodeResourceUtilizationThresholds *NodeResourceUtilizationThresholds NodeAffinityType []string PodsHavingTooManyRestarts *PodsHavingTooManyRestarts MaxPodLifeTimeSeconds *uint RemoveDuplicates *RemoveDuplicates + Namespaces Namespaces } type Percentage float64 diff --git a/pkg/api/v1alpha1/types.go b/pkg/api/v1alpha1/types.go index df52ed1c2..aa1f60c8d 100644 --- a/pkg/api/v1alpha1/types.go +++ b/pkg/api/v1alpha1/types.go @@ -44,13 +44,21 @@ type DeschedulerStrategy struct { Params *StrategyParameters `json:"params,omitempty"` } -// Only one of its members may be specified +// Namespaces carries a list of included/excluded namespaces +// for which a given strategy is applicable. +type Namespaces struct { + Include []string `json:"include"` + Exclude []string `json:"exclude"` +} + +// Besides Namespaces only one of its members may be specified type StrategyParameters struct { NodeResourceUtilizationThresholds *NodeResourceUtilizationThresholds `json:"nodeResourceUtilizationThresholds,omitempty"` NodeAffinityType []string `json:"nodeAffinityType,omitempty"` PodsHavingTooManyRestarts *PodsHavingTooManyRestarts `json:"podsHavingTooManyRestarts,omitempty"` MaxPodLifeTimeSeconds *uint `json:"maxPodLifeTimeSeconds,omitempty"` RemoveDuplicates *RemoveDuplicates `json:"removeDuplicates,omitempty"` + Namespaces Namespaces `json:"namespaces"` } type Percentage float64 diff --git a/pkg/api/v1alpha1/zz_generated.conversion.go b/pkg/api/v1alpha1/zz_generated.conversion.go index 3fa76f749..d2a58a2eb 100644 --- a/pkg/api/v1alpha1/zz_generated.conversion.go +++ b/pkg/api/v1alpha1/zz_generated.conversion.go @@ -55,6 +55,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*Namespaces)(nil), (*api.Namespaces)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_Namespaces_To_api_Namespaces(a.(*Namespaces), b.(*api.Namespaces), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*api.Namespaces)(nil), (*Namespaces)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_api_Namespaces_To_v1alpha1_Namespaces(a.(*api.Namespaces), b.(*Namespaces), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*NodeResourceUtilizationThresholds)(nil), (*api.NodeResourceUtilizationThresholds)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds(a.(*NodeResourceUtilizationThresholds), b.(*api.NodeResourceUtilizationThresholds), scope) }); err != nil { @@ -142,6 +152,28 @@ func Convert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy(in *api.Des return autoConvert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy(in, out, s) } +func autoConvert_v1alpha1_Namespaces_To_api_Namespaces(in *Namespaces, out *api.Namespaces, s conversion.Scope) error { + out.Include = *(*[]string)(unsafe.Pointer(&in.Include)) + out.Exclude = *(*[]string)(unsafe.Pointer(&in.Exclude)) + return nil +} + +// Convert_v1alpha1_Namespaces_To_api_Namespaces is an autogenerated conversion function. +func Convert_v1alpha1_Namespaces_To_api_Namespaces(in *Namespaces, out *api.Namespaces, s conversion.Scope) error { + return autoConvert_v1alpha1_Namespaces_To_api_Namespaces(in, out, s) +} + +func autoConvert_api_Namespaces_To_v1alpha1_Namespaces(in *api.Namespaces, out *Namespaces, s conversion.Scope) error { + out.Include = *(*[]string)(unsafe.Pointer(&in.Include)) + out.Exclude = *(*[]string)(unsafe.Pointer(&in.Exclude)) + return nil +} + +// Convert_api_Namespaces_To_v1alpha1_Namespaces is an autogenerated conversion function. +func Convert_api_Namespaces_To_v1alpha1_Namespaces(in *api.Namespaces, out *Namespaces, s conversion.Scope) error { + return autoConvert_api_Namespaces_To_v1alpha1_Namespaces(in, out, s) +} + func autoConvert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds(in *NodeResourceUtilizationThresholds, out *api.NodeResourceUtilizationThresholds, s conversion.Scope) error { out.Thresholds = *(*api.ResourceThresholds)(unsafe.Pointer(&in.Thresholds)) out.TargetThresholds = *(*api.ResourceThresholds)(unsafe.Pointer(&in.TargetThresholds)) @@ -214,6 +246,9 @@ func autoConvert_v1alpha1_StrategyParameters_To_api_StrategyParameters(in *Strat out.PodsHavingTooManyRestarts = (*api.PodsHavingTooManyRestarts)(unsafe.Pointer(in.PodsHavingTooManyRestarts)) out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds)) out.RemoveDuplicates = (*api.RemoveDuplicates)(unsafe.Pointer(in.RemoveDuplicates)) + if err := Convert_v1alpha1_Namespaces_To_api_Namespaces(&in.Namespaces, &out.Namespaces, s); err != nil { + return err + } return nil } @@ -228,6 +263,9 @@ func autoConvert_api_StrategyParameters_To_v1alpha1_StrategyParameters(in *api.S out.PodsHavingTooManyRestarts = (*PodsHavingTooManyRestarts)(unsafe.Pointer(in.PodsHavingTooManyRestarts)) out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds)) out.RemoveDuplicates = (*RemoveDuplicates)(unsafe.Pointer(in.RemoveDuplicates)) + if err := Convert_api_Namespaces_To_v1alpha1_Namespaces(&in.Namespaces, &out.Namespaces, s); err != nil { + return err + } return nil } diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index 832418cbd..f9707bb1d 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -77,6 +77,32 @@ func (in *DeschedulerStrategy) DeepCopy() *DeschedulerStrategy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Namespaces) DeepCopyInto(out *Namespaces) { + *out = *in + if in.Include != nil { + in, out := &in.Include, &out.Include + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Exclude != nil { + in, out := &in.Exclude, &out.Exclude + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Namespaces. +func (in *Namespaces) DeepCopy() *Namespaces { + if in == nil { + return nil + } + out := new(Namespaces) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeResourceUtilizationThresholds) DeepCopyInto(out *NodeResourceUtilizationThresholds) { *out = *in @@ -216,6 +242,7 @@ func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) { *out = new(RemoveDuplicates) (*in).DeepCopyInto(*out) } + in.Namespaces.DeepCopyInto(&out.Namespaces) return } diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go index 66d7cf02a..b8d8dc574 100644 --- a/pkg/api/zz_generated.deepcopy.go +++ b/pkg/api/zz_generated.deepcopy.go @@ -77,6 +77,32 @@ func (in *DeschedulerStrategy) DeepCopy() *DeschedulerStrategy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Namespaces) DeepCopyInto(out *Namespaces) { + *out = *in + if in.Include != nil { + in, out := &in.Include, &out.Include + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Exclude != nil { + in, out := &in.Exclude, &out.Exclude + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Namespaces. +func (in *Namespaces) DeepCopy() *Namespaces { + if in == nil { + return nil + } + out := new(Namespaces) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeResourceUtilizationThresholds) DeepCopyInto(out *NodeResourceUtilizationThresholds) { *out = *in @@ -216,6 +242,7 @@ func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) { *out = new(RemoveDuplicates) (*in).DeepCopyInto(*out) } + in.Namespaces.DeepCopyInto(&out.Namespaces) return } diff --git a/pkg/descheduler/pod/pods.go b/pkg/descheduler/pod/pods.go index e2b3fa2fb..cbb4f8c19 100644 --- a/pkg/descheduler/pod/pods.go +++ b/pkg/descheduler/pod/pods.go @@ -28,7 +28,9 @@ import ( ) type Options struct { - filter func(pod *v1.Pod) bool + filter func(pod *v1.Pod) bool + includedNamespaces []string + excludedNamespaces []string } // WithFilter sets a pod filter. @@ -39,6 +41,20 @@ func WithFilter(filter func(pod *v1.Pod) bool) func(opts *Options) { } } +// WithNamespaces sets included namespaces +func WithNamespaces(namespaces []string) func(opts *Options) { + return func(opts *Options) { + opts.includedNamespaces = namespaces + } +} + +// WithoutNamespaces sets excluded namespaces +func WithoutNamespaces(namespaces []string) func(opts *Options) { + return func(opts *Options) { + opts.excludedNamespaces = namespaces + } +} + // ListPodsOnANode lists all of the pods on a node // It also accepts an optional "filter" function which can be used to further limit the pods that are returned. // (Usually this is podEvictor.IsEvictable, in order to only list the evictable pods on a node, but can @@ -54,18 +70,52 @@ func ListPodsOnANode( opt(options) } - fieldSelector, err := fields.ParseSelector("spec.nodeName=" + node.Name + ",status.phase!=" + string(v1.PodSucceeded) + ",status.phase!=" + string(v1.PodFailed)) + pods := make([]*v1.Pod, 0) + + fieldSelectorString := "spec.nodeName=" + node.Name + ",status.phase!=" + string(v1.PodSucceeded) + ",status.phase!=" + string(v1.PodFailed) + + if len(options.includedNamespaces) > 0 { + fieldSelector, err := fields.ParseSelector(fieldSelectorString) + if err != nil { + return []*v1.Pod{}, err + } + + for _, namespace := range options.includedNamespaces { + podList, err := client.CoreV1().Pods(namespace).List(ctx, + metav1.ListOptions{FieldSelector: fieldSelector.String()}) + if err != nil { + return []*v1.Pod{}, err + } + for i := range podList.Items { + if options.filter != nil && !options.filter(&podList.Items[i]) { + continue + } + pods = append(pods, &podList.Items[i]) + } + } + return pods, nil + } + + if len(options.excludedNamespaces) > 0 { + for _, namespace := range options.excludedNamespaces { + fieldSelectorString += ",metadata.namespace!=" + namespace + } + } + + fieldSelector, err := fields.ParseSelector(fieldSelectorString) if err != nil { return []*v1.Pod{}, err } + // INFO(jchaloup): field selectors do not work properly with listers + // Once the descheduler switcheds to pod listers (through informers), + // We need to flip to client-side filtering. podList, err := client.CoreV1().Pods(v1.NamespaceAll).List(ctx, metav1.ListOptions{FieldSelector: fieldSelector.String()}) if err != nil { return []*v1.Pod{}, err } - pods := make([]*v1.Pod, 0) for i := range podList.Items { if options.filter != nil && !options.filter(&podList.Items[i]) { continue diff --git a/pkg/descheduler/strategies/node_affinity.go b/pkg/descheduler/strategies/node_affinity.go index a804b5e23..5454ce947 100644 --- a/pkg/descheduler/strategies/node_affinity.go +++ b/pkg/descheduler/strategies/node_affinity.go @@ -18,6 +18,7 @@ package strategies import ( "context" + "fmt" v1 "k8s.io/api/core/v1" clientset "k8s.io/client-go/kubernetes" @@ -29,10 +30,22 @@ import ( podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" ) +func validatePodsViolatingNodeAffinityParams(params *api.StrategyParameters) error { + if params == nil || len(params.NodeAffinityType) == 0 { + return fmt.Errorf("NodeAffinityType is empty") + } + // At most one of include/exclude can be set + if len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { + return fmt.Errorf("only one of Include/Exclude namespaces can be set") + } + + return nil +} + // RemovePodsViolatingNodeAffinity evicts pods on nodes which violate node affinity func RemovePodsViolatingNodeAffinity(ctx context.Context, client clientset.Interface, strategy api.DeschedulerStrategy, nodes []*v1.Node, podEvictor *evictions.PodEvictor) { - if strategy.Params == nil { - klog.V(1).Infof("NodeAffinityType not set") + if err := validatePodsViolatingNodeAffinityParams(strategy.Params); err != nil { + klog.V(1).Info(err) return } for _, nodeAffinity := range strategy.Params.NodeAffinityType { @@ -43,11 +56,18 @@ func RemovePodsViolatingNodeAffinity(ctx context.Context, client clientset.Inter for _, node := range nodes { klog.V(1).Infof("Processing node: %#v\n", node.Name) - pods, err := podutil.ListPodsOnANode(ctx, client, node, podutil.WithFilter(func(pod *v1.Pod) bool { - return podEvictor.IsEvictable(pod) && - !nodeutil.PodFitsCurrentNode(pod, node) && - nodeutil.PodFitsAnyNode(pod, nodes) - })) + pods, err := podutil.ListPodsOnANode( + ctx, + client, + node, + podutil.WithFilter(func(pod *v1.Pod) bool { + return podEvictor.IsEvictable(pod) && + !nodeutil.PodFitsCurrentNode(pod, node) && + nodeutil.PodFitsAnyNode(pod, nodes) + }), + podutil.WithNamespaces(strategy.Params.Namespaces.Include), + podutil.WithoutNamespaces(strategy.Params.Namespaces.Exclude), + ) if err != nil { klog.Errorf("failed to get pods from %v: %v", node.Name, err) } diff --git a/pkg/descheduler/strategies/node_taint.go b/pkg/descheduler/strategies/node_taint.go index bc612890d..529b6b6b3 100644 --- a/pkg/descheduler/strategies/node_taint.go +++ b/pkg/descheduler/strategies/node_taint.go @@ -18,6 +18,7 @@ package strategies import ( "context" + "fmt" "sigs.k8s.io/descheduler/pkg/api" "sigs.k8s.io/descheduler/pkg/descheduler/evictions" @@ -29,11 +30,40 @@ import ( "k8s.io/klog/v2" ) +func validateRemovePodsViolatingNodeTaintsParams(params *api.StrategyParameters) error { + if params == nil { + return nil + } + + // At most one of include/exclude can be set + if len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { + return fmt.Errorf("only one of Include/Exclude namespaces can be set") + } + + return nil +} + // RemovePodsViolatingNodeTaints evicts pods on the node which violate NoSchedule Taints on nodes func RemovePodsViolatingNodeTaints(ctx context.Context, client clientset.Interface, strategy api.DeschedulerStrategy, nodes []*v1.Node, podEvictor *evictions.PodEvictor) { + if err := validateRemovePodsViolatingNodeTaintsParams(strategy.Params); err != nil { + klog.V(1).Info(err) + return + } + var namespaces api.Namespaces + if strategy.Params != nil { + namespaces = strategy.Params.Namespaces + } + for _, node := range nodes { klog.V(1).Infof("Processing node: %#v\n", node.Name) - pods, err := podutil.ListPodsOnANode(ctx, client, node, podutil.WithFilter(podEvictor.IsEvictable)) + pods, err := podutil.ListPodsOnANode( + ctx, + client, + node, + podutil.WithFilter(podEvictor.IsEvictable), + podutil.WithNamespaces(namespaces.Include), + podutil.WithoutNamespaces(namespaces.Exclude), + ) if err != nil { //no pods evicted as error encountered retrieving evictable Pods return diff --git a/pkg/descheduler/strategies/pod_antiaffinity.go b/pkg/descheduler/strategies/pod_antiaffinity.go index 943aa8fd8..66667e3fc 100644 --- a/pkg/descheduler/strategies/pod_antiaffinity.go +++ b/pkg/descheduler/strategies/pod_antiaffinity.go @@ -18,6 +18,7 @@ package strategies import ( "context" + "fmt" "sigs.k8s.io/descheduler/pkg/api" "sigs.k8s.io/descheduler/pkg/descheduler/evictions" @@ -30,11 +31,36 @@ import ( "k8s.io/klog/v2" ) +func validateRemovePodsViolatingInterPodAntiAffinityParams(params *api.StrategyParameters) error { + if params == nil { + return nil + } + + // At most one of include/exclude can be set + if len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { + return fmt.Errorf("only one of Include/Exclude namespaces can be set") + } + + return nil +} + // RemovePodsViolatingInterPodAntiAffinity evicts pods on the node which are having a pod affinity rules. func RemovePodsViolatingInterPodAntiAffinity(ctx context.Context, client clientset.Interface, strategy api.DeschedulerStrategy, nodes []*v1.Node, podEvictor *evictions.PodEvictor) { + var namespaces api.Namespaces + if strategy.Params != nil { + namespaces = strategy.Params.Namespaces + } + for _, node := range nodes { klog.V(1).Infof("Processing node: %#v\n", node.Name) - pods, err := podutil.ListPodsOnANode(ctx, client, node, podutil.WithFilter(podEvictor.IsEvictable)) + pods, err := podutil.ListPodsOnANode( + ctx, + client, + node, + podutil.WithFilter(podEvictor.IsEvictable), + podutil.WithNamespaces(namespaces.Include), + podutil.WithoutNamespaces(namespaces.Exclude), + ) if err != nil { return } diff --git a/pkg/descheduler/strategies/pod_lifetime.go b/pkg/descheduler/strategies/pod_lifetime.go index ae6c0208c..a214dc3cf 100644 --- a/pkg/descheduler/strategies/pod_lifetime.go +++ b/pkg/descheduler/strategies/pod_lifetime.go @@ -18,6 +18,7 @@ package strategies import ( "context" + "fmt" v1 "k8s.io/api/core/v1" v1meta "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,16 +30,30 @@ import ( podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" ) +func validatePodLifeTimeParams(params *api.StrategyParameters) error { + if params == nil || params.MaxPodLifeTimeSeconds == nil { + return fmt.Errorf("MaxPodLifeTimeSeconds not set") + } + + // At most one of include/exclude can be set + if len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { + return fmt.Errorf("only one of Include/Exclude namespaces can be set") + } + + return nil +} + // PodLifeTime evicts pods on nodes that were created more than strategy.Params.MaxPodLifeTimeSeconds seconds ago. func PodLifeTime(ctx context.Context, client clientset.Interface, strategy api.DeschedulerStrategy, nodes []*v1.Node, podEvictor *evictions.PodEvictor) { - if strategy.Params == nil || strategy.Params.MaxPodLifeTimeSeconds == nil { - klog.V(1).Infof("MaxPodLifeTimeSeconds not set") + if err := validatePodLifeTimeParams(strategy.Params); err != nil { + klog.V(1).Info(err) return } for _, node := range nodes { klog.V(1).Infof("Processing node: %#v", node.Name) - pods := listOldPodsOnNode(ctx, client, node, *strategy.Params.MaxPodLifeTimeSeconds, podEvictor) + + pods := listOldPodsOnNode(ctx, client, node, strategy.Params, podEvictor) for _, pod := range pods { success, err := podEvictor.EvictPod(ctx, pod, node, "PodLifeTime") if success { @@ -50,11 +65,19 @@ func PodLifeTime(ctx context.Context, client clientset.Interface, strategy api.D break } } + } } -func listOldPodsOnNode(ctx context.Context, client clientset.Interface, node *v1.Node, maxAge uint, evictor *evictions.PodEvictor) []*v1.Pod { - pods, err := podutil.ListPodsOnANode(ctx, client, node, podutil.WithFilter(evictor.IsEvictable)) +func listOldPodsOnNode(ctx context.Context, client clientset.Interface, node *v1.Node, params *api.StrategyParameters, evictor *evictions.PodEvictor) []*v1.Pod { + pods, err := podutil.ListPodsOnANode( + ctx, + client, + node, + podutil.WithFilter(evictor.IsEvictable), + podutil.WithNamespaces(params.Namespaces.Include), + podutil.WithoutNamespaces(params.Namespaces.Exclude), + ) if err != nil { return nil } @@ -62,7 +85,7 @@ func listOldPodsOnNode(ctx context.Context, client clientset.Interface, node *v1 var oldPods []*v1.Pod for _, pod := range pods { podAgeSeconds := uint(v1meta.Now().Sub(pod.GetCreationTimestamp().Local()).Seconds()) - if podAgeSeconds > maxAge { + if podAgeSeconds > *params.MaxPodLifeTimeSeconds { oldPods = append(oldPods, pod) } } diff --git a/pkg/descheduler/strategies/toomanyrestarts.go b/pkg/descheduler/strategies/toomanyrestarts.go index e0750c14f..ae745b45f 100644 --- a/pkg/descheduler/strategies/toomanyrestarts.go +++ b/pkg/descheduler/strategies/toomanyrestarts.go @@ -18,6 +18,7 @@ package strategies import ( "context" + "fmt" v1 "k8s.io/api/core/v1" clientset "k8s.io/client-go/kubernetes" @@ -28,17 +29,37 @@ import ( podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" ) +func validateRemovePodsHavingTooManyRestartsParams(params *api.StrategyParameters) error { + if params == nil || params.PodsHavingTooManyRestarts == nil || params.PodsHavingTooManyRestarts.PodRestartThreshold < 1 { + return fmt.Errorf("PodsHavingTooManyRestarts threshold not set") + } + + // At most one of include/exclude can be set + if len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { + return fmt.Errorf("only one of Include/Exclude namespaces can be set") + } + + return nil +} + // RemovePodsHavingTooManyRestarts removes the pods that have too many restarts on node. // There are too many cases leading this issue: Volume mount failed, app error due to nodes' different settings. // As of now, this strategy won't evict daemonsets, mirror pods, critical pods and pods with local storages. func RemovePodsHavingTooManyRestarts(ctx context.Context, client clientset.Interface, strategy api.DeschedulerStrategy, nodes []*v1.Node, podEvictor *evictions.PodEvictor) { - if strategy.Params == nil || strategy.Params.PodsHavingTooManyRestarts == nil || strategy.Params.PodsHavingTooManyRestarts.PodRestartThreshold < 1 { - klog.V(1).Infof("PodsHavingTooManyRestarts thresholds not set") + if err := validateRemovePodsHavingTooManyRestartsParams(strategy.Params); err != nil { + klog.V(1).Info(err) return } for _, node := range nodes { klog.V(1).Infof("Processing node: %s", node.Name) - pods, err := podutil.ListPodsOnANode(ctx, client, node, podutil.WithFilter(podEvictor.IsEvictable)) + pods, err := podutil.ListPodsOnANode( + ctx, + client, + node, + podutil.WithFilter(podEvictor.IsEvictable), + podutil.WithNamespaces(strategy.Params.Namespaces.Include), + podutil.WithoutNamespaces(strategy.Params.Namespaces.Exclude), + ) if err != nil { klog.Errorf("Error when list pods at node %s", node.Name) continue