Skip to content

Commit

Permalink
Support of the namespaceLabelSelector in DefaultEvictor plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
RomanenkoDenys committed Sep 4, 2024
1 parent 0f1890e commit a619cfc
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 22 deletions.
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,19 @@ These are top level keys in the Descheduler Policy that you can use to configure

The Default Evictor Plugin is used by default for filtering pods before processing them in an strategy plugin, or for applying a PreEvictionFilter of pods before eviction. You can also create your own Evictor Plugin or use the Default one provided by Descheduler. Other uses for the Evictor plugin can be to sort, filter, validate or group pods by different criteria, and that's why this is handled by a plugin and not configured in the top level config.

| Name |type| Default Value | Description |
|------|----|---------------|-------------|
| `nodeSelector` |`string`| `nil` | limiting the nodes which are processed |
| `evictLocalStoragePods` |`bool`| `false` | allows eviction of pods with local storage |
| Name |type| Default Value | Description |
|---------------------------|----|---------------|-------------|
| `nodeSelector` |`string`| `nil` | limiting the nodes which are processed |
| `evictLocalStoragePods` |`bool`| `false` | allows eviction of pods with local storage |
| `evictSystemCriticalPods` |`bool`| `false` | [Warning: Will evict Kubernetes system pods] allows eviction of pods with any priority, including system pods like kube-dns |
| `ignorePvcPods` |`bool`| `false` | set whether PVC pods should be evicted or ignored |
| `evictFailedBarePods` |`bool`| `false` | allow eviction of pods without owner references and in failed phase |
|`labelSelector`|`metav1.LabelSelector`||(see [label filtering](#label-filtering))|
|`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 |
| `ignorePvcPods` |`bool`| `false` | set whether PVC pods should be evicted or ignored |
| `evictFailedBarePods` |`bool`| `false` | allow eviction of pods without owner references and in failed phase |
| `namespaceLabelSelector` |`metav1.LabelSelector`|| limiting the pods which are processed by namespace (see [label filtering](#label-filtering)) |
| `labelSelector` |`metav1.LabelSelector`||(see [label filtering](#label-filtering))|
| `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

Expand Down
57 changes: 57 additions & 0 deletions pkg/framework/plugins/defaultevictor/defaultevictor.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
utilerrors "k8s.io/apimachinery/pkg/util/errors"

"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"

nodeutil "sigs.k8s.io/descheduler/pkg/descheduler/node"
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
frameworktypes "sigs.k8s.io/descheduler/pkg/framework/types"
Expand Down Expand Up @@ -157,6 +159,25 @@ func New(args runtime.Object, handle frameworktypes.Handle) (frameworktypes.Plug
})
}

// check pod by namespace label filter
if defaultEvictorArgs.NamespaceLabelSelector != nil {
indexName := "metadata.namespace"
indexer, err := getNamespacesListByLabelSelector(indexName, defaultEvictorArgs.NamespaceLabelSelector, handle)
if err != nil {
return nil, err
}
ev.constraints = append(ev.constraints, func(pod *v1.Pod) error {
objs, err := indexer.ByIndex(indexName, pod.Namespace)
if err != nil {
return fmt.Errorf("unable to list namespaces for namespaceLabelSelector filter in the policy parameter")
}
if len(objs) == 0 {
return fmt.Errorf("pod namespace do not match the namespaceLabelSelector filter in the policy parameter")
}
return nil
})
}

if defaultEvictorArgs.MinReplicas > 1 {
indexName := "metadata.ownerReferences"
indexer, err := getPodIndexerByOwnerRefs(indexName, handle)
Expand Down Expand Up @@ -278,3 +299,39 @@ func getPodIndexerByOwnerRefs(indexName string, handle frameworktypes.Handle) (c

return indexer, nil
}

func getNamespacesListByLabelSelector(indexName string, labelSelector *metav1.LabelSelector, handle frameworktypes.Handle) (cache.Indexer, error) {
nsInformer := handle.SharedInformerFactory().Core().V1().Namespaces().Informer()
indexer := nsInformer.GetIndexer()

// do not reinitialize the indexer, if it's been defined already
for name := range indexer.GetIndexers() {
if name == indexName {
return indexer, nil
}
}

if err := nsInformer.AddIndexers(cache.Indexers{
indexName: func(obj interface{}) ([]string, error) {
ns, ok := obj.(*v1.Namespace)
if !ok {
return []string{}, errors.New("unexpected object")
}

selector, err := metav1.LabelSelectorAsSelector(labelSelector)
if err != nil {
return []string{}, errors.New("could not get selector from label selector")
}
if labelSelector != nil && !selector.Empty() {
if !selector.Matches(labels.Set(ns.Labels)) {
return []string{}, nil
}
}
return []string{ns.GetName()}, nil
},
}); err != nil {
return nil, err
}

return indexer, nil
}
70 changes: 70 additions & 0 deletions pkg/framework/plugins/defaultevictor/defaultevictor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"

"sigs.k8s.io/descheduler/pkg/api"
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
frameworkfake "sigs.k8s.io/descheduler/pkg/framework/fake"
Expand All @@ -44,11 +45,19 @@ type testCase struct {
evictSystemCriticalPods bool
priorityThreshold *int32
nodeFit bool
useNamespaceSelector bool
minReplicas uint
minPodAge *metav1.Duration
result bool
}

var namespace = "test"
var namespaceSelector = &metav1.LabelSelector{
MatchLabels: map[string]string{
"kubernetes.io/metadata.name": namespace,
},
}

func TestDefaultEvictorPreEvictionFilter(t *testing.T) {
n1 := test.BuildTestNode("node1", 1000, 2000, 13, nil)

Expand Down Expand Up @@ -305,6 +314,63 @@ func TestDefaultEvictorPreEvictionFilter(t *testing.T) {
nodeFit: false,
result: true,
},
{
description: "Pod with namespace matched namespace selector, should be evicted",
pods: []*v1.Pod{
test.BuildTestPod("p1", 400, 0, n1.Name, func(pod *v1.Pod) {
pod.ObjectMeta.Namespace = namespace
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
pod.Spec.NodeSelector = map[string]string{
nodeLabelKey: nodeLabelValue,
}
}),
},
nodes: []*v1.Node{
test.BuildTestNode("node2", 1000, 2000, 13, func(node *v1.Node) {
node.ObjectMeta.Labels = map[string]string{
nodeLabelKey: nodeLabelValue,
}
}),
test.BuildTestNode("node3", 1000, 2000, 13, func(node *v1.Node) {
node.ObjectMeta.Labels = map[string]string{
nodeLabelKey: nodeLabelValue,
}
}),
},
evictLocalStoragePods: false,
evictSystemCriticalPods: false,
nodeFit: true,
useNamespaceSelector: true,
result: true,
},
{
description: "Pod wit namespace does not matched namespace selector, should not be evicted",
pods: []*v1.Pod{
test.BuildTestPod("p1", 400, 0, n1.Name, func(pod *v1.Pod) {
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
pod.Spec.NodeSelector = map[string]string{
nodeLabelKey: "fail",
}
}),
},
nodes: []*v1.Node{
test.BuildTestNode("node2", 1000, 2000, 13, func(node *v1.Node) {
node.ObjectMeta.Labels = map[string]string{
nodeLabelKey: nodeLabelValue,
}
}),
test.BuildTestNode("node3", 1000, 2000, 13, func(node *v1.Node) {
node.ObjectMeta.Labels = map[string]string{
nodeLabelKey: nodeLabelValue,
}
}),
},
evictLocalStoragePods: false,
evictSystemCriticalPods: false,
nodeFit: true,
useNamespaceSelector: true,
result: false,
},
}

for _, test := range testCases {
Expand Down Expand Up @@ -838,6 +904,10 @@ func initializePlugin(ctx context.Context, test testCase) (frameworktypes.Plugin
MinPodAge: test.minPodAge,
}

if test.useNamespaceSelector {
defaultEvictorArgs.NamespaceLabelSelector = namespaceSelector
}

evictorPlugin, err := New(
defaultEvictorArgs,
&frameworkfake.HandleImpl{
Expand Down
3 changes: 3 additions & 0 deletions pkg/framework/plugins/defaultevictor/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ func SetDefaults_DefaultEvictorArgs(obj runtime.Object) {
if !args.EvictFailedBarePods {
args.EvictFailedBarePods = false
}
if args.NamespaceLabelSelector == nil {
args.NamespaceLabelSelector = nil
}
if args.LabelSelector == nil {
args.LabelSelector = nil
}
Expand Down
24 changes: 13 additions & 11 deletions pkg/framework/plugins/defaultevictor/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package defaultevictor

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"sigs.k8s.io/descheduler/pkg/api"
)

Expand All @@ -25,15 +26,16 @@ import (
type DefaultEvictorArgs struct {
metav1.TypeMeta `json:",inline"`

NodeSelector string `json:"nodeSelector,omitempty"`
EvictLocalStoragePods bool `json:"evictLocalStoragePods,omitempty"`
EvictDaemonSetPods bool `json:"evictDaemonSetPods,omitempty"`
EvictSystemCriticalPods bool `json:"evictSystemCriticalPods,omitempty"`
IgnorePvcPods bool `json:"ignorePvcPods,omitempty"`
EvictFailedBarePods bool `json:"evictFailedBarePods,omitempty"`
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
PriorityThreshold *api.PriorityThreshold `json:"priorityThreshold,omitempty"`
NodeFit bool `json:"nodeFit,omitempty"`
MinReplicas uint `json:"minReplicas,omitempty"`
MinPodAge *metav1.Duration `json:"minPodAge,omitempty"`
NodeSelector string `json:"nodeSelector"`
EvictLocalStoragePods bool `json:"evictLocalStoragePods"`
EvictDaemonSetPods bool `json:"evictDaemonSetPods"`
EvictSystemCriticalPods bool `json:"evictSystemCriticalPods"`
IgnorePvcPods bool `json:"ignorePvcPods"`
EvictFailedBarePods bool `json:"evictFailedBarePods"`
NamespaceLabelSelector *metav1.LabelSelector `json:"namespaceLabelSelector"`
LabelSelector *metav1.LabelSelector `json:"labelSelector"`
PriorityThreshold *api.PriorityThreshold `json:"priorityThreshold"`
NodeFit bool `json:"nodeFit"`
MinReplicas uint `json:"minReplicas"`
MinPodAge *metav1.Duration `json:"minPodAge"`
}
6 changes: 6 additions & 0 deletions pkg/framework/plugins/defaultevictor/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a619cfc

Please sign in to comment.