diff --git a/README.md b/README.md index c23c8652a..d5ffda6a7 100644 --- a/README.md +++ b/README.md @@ -412,10 +412,17 @@ pod "podA" with a toleration to tolerate a taint ``key=value:NoSchedule`` schedu node. If the node's taint is subsequently updated/removed, taint is no longer satisfied by its pods' tolerations and will be evicted. +Node taints can be excluded from consideration by specifying a list of excludedTaints. If a node taint key **or** +key=value matches an excludedTaints entry, the taint will be ignored. + +For example, excludedTaints entry "dedicated" would match all taints with key "dedicated", regardless of value. +excludedTaints entry "dedicated=special-user" would match taints with key "dedicated" and value "special-user". + **Parameters:** |Name|Type| |---|---| +|`excludedTaints`|list(string)| |`thresholdPriority`|int (see [priority filtering](#priority-filtering))| |`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))| |`namespaces`|(see [namespace filtering](#namespace-filtering))| @@ -430,6 +437,10 @@ kind: "DeschedulerPolicy" strategies: "RemovePodsViolatingNodeTaints": enabled: true + params: + excludedTaints: + - dedicated=special-user # exclude taints with key "dedicated" and value "special-user" + - reserved # exclude all taints with key "reserved" ```` ### RemovePodsViolatingTopologySpreadConstraint diff --git a/pkg/api/types.go b/pkg/api/types.go index f67840ca3..919807c48 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -89,6 +89,7 @@ type StrategyParameters struct { LabelSelector *metav1.LabelSelector NodeFit bool IncludePreferNoSchedule bool + ExcludedTaints []string } type Percentage float64 diff --git a/pkg/api/v1alpha1/types.go b/pkg/api/v1alpha1/types.go index 569014bad..43fe680eb 100644 --- a/pkg/api/v1alpha1/types.go +++ b/pkg/api/v1alpha1/types.go @@ -87,6 +87,7 @@ type StrategyParameters struct { LabelSelector *metav1.LabelSelector `json:"labelSelector"` NodeFit bool `json:"nodeFit"` IncludePreferNoSchedule bool `json:"includePreferNoSchedule"` + ExcludedTaints []string `json:"excludedTaints,omitempty"` } type Percentage float64 diff --git a/pkg/api/v1alpha1/zz_generated.conversion.go b/pkg/api/v1alpha1/zz_generated.conversion.go index 8e965f5a2..8798136f4 100644 --- a/pkg/api/v1alpha1/zz_generated.conversion.go +++ b/pkg/api/v1alpha1/zz_generated.conversion.go @@ -362,6 +362,7 @@ func autoConvert_v1alpha1_StrategyParameters_To_api_StrategyParameters(in *Strat out.LabelSelector = (*v1.LabelSelector)(unsafe.Pointer(in.LabelSelector)) out.NodeFit = in.NodeFit out.IncludePreferNoSchedule = in.IncludePreferNoSchedule + out.ExcludedTaints = *(*[]string)(unsafe.Pointer(&in.ExcludedTaints)) return nil } @@ -384,6 +385,7 @@ func autoConvert_api_StrategyParameters_To_v1alpha1_StrategyParameters(in *api.S out.LabelSelector = (*v1.LabelSelector)(unsafe.Pointer(in.LabelSelector)) out.NodeFit = in.NodeFit out.IncludePreferNoSchedule = in.IncludePreferNoSchedule + out.ExcludedTaints = *(*[]string)(unsafe.Pointer(&in.ExcludedTaints)) return nil } diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index 506f79686..a4ffea3aa 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -356,6 +356,11 @@ func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) { *out = new(v1.LabelSelector) (*in).DeepCopyInto(*out) } + if in.ExcludedTaints != nil { + in, out := &in.ExcludedTaints, &out.ExcludedTaints + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go index 06e45c3f1..07238f415 100644 --- a/pkg/api/zz_generated.deepcopy.go +++ b/pkg/api/zz_generated.deepcopy.go @@ -356,6 +356,11 @@ func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) { *out = new(v1.LabelSelector) (*in).DeepCopyInto(*out) } + if in.ExcludedTaints != nil { + in, out := &in.ExcludedTaints, &out.ExcludedTaints + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/descheduler/strategies/node_taint.go b/pkg/descheduler/strategies/node_taint.go index 53e23f52e..64cf8d48c 100644 --- a/pkg/descheduler/strategies/node_taint.go +++ b/pkg/descheduler/strategies/node_taint.go @@ -55,13 +55,16 @@ func RemovePodsViolatingNodeTaints(ctx context.Context, client clientset.Interfa return } - var includedNamespaces, excludedNamespaces sets.String + var includedNamespaces, excludedNamespaces, excludedTaints sets.String var labelSelector *metav1.LabelSelector if strategy.Params != nil { if strategy.Params.Namespaces != nil { includedNamespaces = sets.NewString(strategy.Params.Namespaces.Include...) excludedNamespaces = sets.NewString(strategy.Params.Namespaces.Exclude...) } + if strategy.Params.ExcludedTaints != nil { + excludedTaints = sets.NewString(strategy.Params.ExcludedTaints...) + } labelSelector = strategy.Params.LabelSelector } @@ -89,10 +92,15 @@ func RemovePodsViolatingNodeTaints(ctx context.Context, client clientset.Interfa return } - taintFilterFnc := func(taint *v1.Taint) bool { return taint.Effect == v1.TaintEffectNoSchedule } + excludeTaint := func(taint *v1.Taint) bool { + // Exclude taints by key *or* key=value + return excludedTaints.Has(taint.Key) || (taint.Value != "" && excludedTaints.Has(fmt.Sprintf("%s=%s", taint.Key, taint.Value))) + } + + taintFilterFnc := func(taint *v1.Taint) bool { return (taint.Effect == v1.TaintEffectNoSchedule) && !excludeTaint(taint) } if strategy.Params != nil && strategy.Params.IncludePreferNoSchedule { taintFilterFnc = func(taint *v1.Taint) bool { - return taint.Effect == v1.TaintEffectNoSchedule || taint.Effect == v1.TaintEffectPreferNoSchedule + return (taint.Effect == v1.TaintEffectNoSchedule || taint.Effect == v1.TaintEffectPreferNoSchedule) && !excludeTaint(taint) } } diff --git a/pkg/descheduler/strategies/node_taint_test.go b/pkg/descheduler/strategies/node_taint_test.go index e21914f93..45a7cea58 100644 --- a/pkg/descheduler/strategies/node_taint_test.go +++ b/pkg/descheduler/strategies/node_taint_test.go @@ -149,6 +149,7 @@ func TestDeletePodsViolatingNodeTaints(t *testing.T) { expectedEvictedPodCount uint nodeFit bool includePreferNoSchedule bool + excludedTaints []string }{ { @@ -261,6 +262,33 @@ func TestDeletePodsViolatingNodeTaints(t *testing.T) { includePreferNoSchedule: true, expectedEvictedPodCount: 1, // p13 gets evicted }, + { + description: "Pods not tolerating excluded node taints (by key) should not be evicted", + pods: []*v1.Pod{p2}, + nodes: []*v1.Node{node1}, + evictLocalStoragePods: false, + evictSystemCriticalPods: false, + excludedTaints: []string{"excludedTaint1", "testTaint1"}, + expectedEvictedPodCount: 0, // nothing gets evicted, as one of the specified excludedTaints matches the key of node1's taint + }, + { + description: "Pods not tolerating excluded node taints (by key and value) should not be evicted", + pods: []*v1.Pod{p2}, + nodes: []*v1.Node{node1}, + evictLocalStoragePods: false, + evictSystemCriticalPods: false, + excludedTaints: []string{"testTaint1=test1"}, + expectedEvictedPodCount: 0, // nothing gets evicted, as both the key and value of the excluded taint match node1's taint + }, + { + description: "The excluded taint matches the key of node1's taint, but does not match the value", + pods: []*v1.Pod{p2}, + nodes: []*v1.Node{node1}, + evictLocalStoragePods: false, + evictSystemCriticalPods: false, + excludedTaints: []string{"testTaint1=test2"}, + expectedEvictedPodCount: 1, // pod gets evicted, as excluded taint value does not match node1's taint value + }, } for _, tc := range tests { @@ -307,6 +335,7 @@ func TestDeletePodsViolatingNodeTaints(t *testing.T) { Params: &api.StrategyParameters{ NodeFit: tc.nodeFit, IncludePreferNoSchedule: tc.includePreferNoSchedule, + ExcludedTaints: tc.excludedTaints, }, }