Skip to content

Commit 1b6910e

Browse files
Support of the namespaceLabelSelector in DefaultEvictor plugin
1 parent 0f1890e commit 1b6910e

File tree

6 files changed

+163
-22
lines changed

6 files changed

+163
-22
lines changed

README.md

+12-11
Original file line numberDiff line numberDiff line change
@@ -132,18 +132,19 @@ These are top level keys in the Descheduler Policy that you can use to configure
132132

133133
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.
134134

135-
| Name |type| Default Value | Description |
136-
|------|----|---------------|-------------|
137-
| `nodeSelector` |`string`| `nil` | limiting the nodes which are processed |
138-
| `evictLocalStoragePods` |`bool`| `false` | allows eviction of pods with local storage |
135+
| Name |type| Default Value | Description |
136+
|---------------------------|----|---------------|-------------|
137+
| `nodeSelector` |`string`| `nil` | limiting the nodes which are processed |
138+
| `evictLocalStoragePods` |`bool`| `false` | allows eviction of pods with local storage |
139139
| `evictSystemCriticalPods` |`bool`| `false` | [Warning: Will evict Kubernetes system pods] allows eviction of pods with any priority, including system pods like kube-dns |
140-
| `ignorePvcPods` |`bool`| `false` | set whether PVC pods should be evicted or ignored |
141-
| `evictFailedBarePods` |`bool`| `false` | allow eviction of pods without owner references and in failed phase |
142-
|`labelSelector`|`metav1.LabelSelector`||(see [label filtering](#label-filtering))|
143-
|`priorityThreshold`|`priorityThreshold`||(see [priority filtering](#priority-filtering))|
144-
|`nodeFit`|`bool`|`false`|(see [node fit filtering](#node-fit-filtering))|
145-
|`minReplicas`|`uint`|`0`| ignore eviction of pods where owner (e.g. `ReplicaSet`) replicas is below this threshold |
146-
|`minPodAge`|`metav1.Duration`|`0`| ignore eviction of pods with a creation time within this threshold |
140+
| `ignorePvcPods` |`bool`| `false` | set whether PVC pods should be evicted or ignored |
141+
| `evictFailedBarePods` |`bool`| `false` | allow eviction of pods without owner references and in failed phase |
142+
| `namespaceLabelSelector` |`metav1.LabelSelector`|| limiting the pods which are processed by namespace (see [label filtering](#label-filtering)) |
143+
| `labelSelector` |`metav1.LabelSelector`||(see [label filtering](#label-filtering))|
144+
| `priorityThreshold` |`priorityThreshold`||(see [priority filtering](#priority-filtering))|
145+
| `nodeFit` |`bool`|`false`|(see [node fit filtering](#node-fit-filtering))|
146+
| `minReplicas` |`uint`|`0`| ignore eviction of pods where owner (e.g. `ReplicaSet`) replicas is below this threshold |
147+
| `minPodAge` |`metav1.Duration`|`0`| ignore eviction of pods with a creation time within this threshold |
147148

148149
### Example policy
149150

pkg/framework/plugins/defaultevictor/defaultevictor.go

+59
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import (
2525
"k8s.io/apimachinery/pkg/labels"
2626
"k8s.io/apimachinery/pkg/runtime"
2727
utilerrors "k8s.io/apimachinery/pkg/util/errors"
28+
2829
"k8s.io/client-go/tools/cache"
2930
"k8s.io/klog/v2"
31+
3032
nodeutil "sigs.k8s.io/descheduler/pkg/descheduler/node"
3133
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
3234
frameworktypes "sigs.k8s.io/descheduler/pkg/framework/types"
@@ -216,6 +218,27 @@ func (d *DefaultEvictor) PreEvictionFilter(pod *v1.Pod) bool {
216218
}
217219
return true
218220
}
221+
222+
// check pod by namespace label filter
223+
if d.args.NamespaceLabelSelector != nil {
224+
indexName := "metadata.namespace"
225+
indexer, err := getNamespacesListByLabelSelector(indexName, d.args.NamespaceLabelSelector, d.handle)
226+
if err != nil {
227+
klog.ErrorS(err, "unable to list namespaces", "pod", klog.KObj(pod))
228+
return false
229+
}
230+
objs, err := indexer.ByIndex(indexName, pod.Namespace)
231+
if err != nil {
232+
klog.ErrorS(err, "unable to list namespaces for namespaceLabelSelector filter in the policy parameter", "pod", klog.KObj(pod))
233+
return false
234+
}
235+
if len(objs) == 0 {
236+
klog.InfoS("pod namespace do not match the namespaceLabelSelector filter in the policy parameter", "pod", klog.KObj(pod))
237+
return false
238+
}
239+
return true
240+
}
241+
219242
return true
220243
}
221244

@@ -278,3 +301,39 @@ func getPodIndexerByOwnerRefs(indexName string, handle frameworktypes.Handle) (c
278301

279302
return indexer, nil
280303
}
304+
305+
func getNamespacesListByLabelSelector(indexName string, labelSelector *metav1.LabelSelector, handle frameworktypes.Handle) (cache.Indexer, error) {
306+
nsInformer := handle.SharedInformerFactory().Core().V1().Namespaces().Informer()
307+
indexer := nsInformer.GetIndexer()
308+
309+
// do not reinitialize the indexer, if it's been defined already
310+
for name := range indexer.GetIndexers() {
311+
if name == indexName {
312+
return indexer, nil
313+
}
314+
}
315+
316+
if err := nsInformer.AddIndexers(cache.Indexers{
317+
indexName: func(obj interface{}) ([]string, error) {
318+
ns, ok := obj.(*v1.Namespace)
319+
if !ok {
320+
return []string{}, errors.New("unexpected object")
321+
}
322+
323+
selector, err := metav1.LabelSelectorAsSelector(labelSelector)
324+
if err != nil {
325+
return []string{}, errors.New("could not get selector from label selector")
326+
}
327+
if labelSelector != nil && !selector.Empty() {
328+
if !selector.Matches(labels.Set(ns.Labels)) {
329+
return []string{}, nil
330+
}
331+
}
332+
return []string{ns.GetName()}, nil
333+
},
334+
}); err != nil {
335+
return nil, err
336+
}
337+
338+
return indexer, nil
339+
}

pkg/framework/plugins/defaultevictor/defaultevictor_test.go

+70
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"k8s.io/apimachinery/pkg/util/uuid"
2828
"k8s.io/client-go/informers"
2929
"k8s.io/client-go/kubernetes/fake"
30+
3031
"sigs.k8s.io/descheduler/pkg/api"
3132
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
3233
frameworkfake "sigs.k8s.io/descheduler/pkg/framework/fake"
@@ -44,11 +45,19 @@ type testCase struct {
4445
evictSystemCriticalPods bool
4546
priorityThreshold *int32
4647
nodeFit bool
48+
useNamespaceSelector bool
4749
minReplicas uint
4850
minPodAge *metav1.Duration
4951
result bool
5052
}
5153

54+
var namespace = "test"
55+
var namespaceSelector = &metav1.LabelSelector{
56+
MatchLabels: map[string]string{
57+
"kubernetes.io/metadata.name": namespace,
58+
},
59+
}
60+
5261
func TestDefaultEvictorPreEvictionFilter(t *testing.T) {
5362
n1 := test.BuildTestNode("node1", 1000, 2000, 13, nil)
5463

@@ -305,6 +314,63 @@ func TestDefaultEvictorPreEvictionFilter(t *testing.T) {
305314
nodeFit: false,
306315
result: true,
307316
},
317+
{
318+
description: "Pod with namespace matched namespace selector, should be evicted",
319+
pods: []*v1.Pod{
320+
test.BuildTestPod("p1", 400, 0, n1.Name, func(pod *v1.Pod) {
321+
pod.ObjectMeta.Namespace = namespace
322+
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
323+
pod.Spec.NodeSelector = map[string]string{
324+
nodeLabelKey: nodeLabelValue,
325+
}
326+
}),
327+
},
328+
nodes: []*v1.Node{
329+
test.BuildTestNode("node2", 1000, 2000, 13, func(node *v1.Node) {
330+
node.ObjectMeta.Labels = map[string]string{
331+
nodeLabelKey: nodeLabelValue,
332+
}
333+
}),
334+
test.BuildTestNode("node3", 1000, 2000, 13, func(node *v1.Node) {
335+
node.ObjectMeta.Labels = map[string]string{
336+
nodeLabelKey: nodeLabelValue,
337+
}
338+
}),
339+
},
340+
evictLocalStoragePods: false,
341+
evictSystemCriticalPods: false,
342+
nodeFit: true,
343+
useNamespaceSelector: true,
344+
result: true,
345+
},
346+
{
347+
description: "Pod wit namespace does not matched namespace selector, should not be evicted",
348+
pods: []*v1.Pod{
349+
test.BuildTestPod("p1", 400, 0, n1.Name, func(pod *v1.Pod) {
350+
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
351+
pod.Spec.NodeSelector = map[string]string{
352+
nodeLabelKey: "fail",
353+
}
354+
}),
355+
},
356+
nodes: []*v1.Node{
357+
test.BuildTestNode("node2", 1000, 2000, 13, func(node *v1.Node) {
358+
node.ObjectMeta.Labels = map[string]string{
359+
nodeLabelKey: nodeLabelValue,
360+
}
361+
}),
362+
test.BuildTestNode("node3", 1000, 2000, 13, func(node *v1.Node) {
363+
node.ObjectMeta.Labels = map[string]string{
364+
nodeLabelKey: nodeLabelValue,
365+
}
366+
}),
367+
},
368+
evictLocalStoragePods: false,
369+
evictSystemCriticalPods: false,
370+
nodeFit: true,
371+
useNamespaceSelector: true,
372+
result: false,
373+
},
308374
}
309375

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

907+
if test.useNamespaceSelector {
908+
defaultEvictorArgs.NamespaceLabelSelector = namespaceSelector
909+
}
910+
841911
evictorPlugin, err := New(
842912
defaultEvictorArgs,
843913
&frameworkfake.HandleImpl{

pkg/framework/plugins/defaultevictor/defaults.go

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ func SetDefaults_DefaultEvictorArgs(obj runtime.Object) {
4343
if !args.EvictFailedBarePods {
4444
args.EvictFailedBarePods = false
4545
}
46+
if args.NamespaceLabelSelector == nil {
47+
args.NamespaceLabelSelector = nil
48+
}
4649
if args.LabelSelector == nil {
4750
args.LabelSelector = nil
4851
}

pkg/framework/plugins/defaultevictor/types.go

+13-11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package defaultevictor
1515

1616
import (
1717
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
1819
"sigs.k8s.io/descheduler/pkg/api"
1920
)
2021

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

28-
NodeSelector string `json:"nodeSelector,omitempty"`
29-
EvictLocalStoragePods bool `json:"evictLocalStoragePods,omitempty"`
30-
EvictDaemonSetPods bool `json:"evictDaemonSetPods,omitempty"`
31-
EvictSystemCriticalPods bool `json:"evictSystemCriticalPods,omitempty"`
32-
IgnorePvcPods bool `json:"ignorePvcPods,omitempty"`
33-
EvictFailedBarePods bool `json:"evictFailedBarePods,omitempty"`
34-
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
35-
PriorityThreshold *api.PriorityThreshold `json:"priorityThreshold,omitempty"`
36-
NodeFit bool `json:"nodeFit,omitempty"`
37-
MinReplicas uint `json:"minReplicas,omitempty"`
38-
MinPodAge *metav1.Duration `json:"minPodAge,omitempty"`
29+
NodeSelector string `json:"nodeSelector"`
30+
EvictLocalStoragePods bool `json:"evictLocalStoragePods"`
31+
EvictDaemonSetPods bool `json:"evictDaemonSetPods"`
32+
EvictSystemCriticalPods bool `json:"evictSystemCriticalPods"`
33+
IgnorePvcPods bool `json:"ignorePvcPods"`
34+
EvictFailedBarePods bool `json:"evictFailedBarePods"`
35+
NamespaceLabelSelector *metav1.LabelSelector `json:"namespaceLabelSelector"`
36+
LabelSelector *metav1.LabelSelector `json:"labelSelector"`
37+
PriorityThreshold *api.PriorityThreshold `json:"priorityThreshold"`
38+
NodeFit bool `json:"nodeFit"`
39+
MinReplicas uint `json:"minReplicas"`
40+
MinPodAge *metav1.Duration `json:"minPodAge"`
3941
}

pkg/framework/plugins/defaultevictor/zz_generated.deepcopy.go

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)