mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-01-28 14:41:10 +01:00
removepodsviolatingtopologyspreadconstraint: topologyBalanceNodeFit to control whether to perform nodefit when balacning domains
This commit is contained in:
@@ -112,7 +112,7 @@ See the [user guide](docs/user-guide.md) in the `/docs` directory.
|
|||||||
|
|
||||||
## Policy, Default Evictor and Strategy plugins
|
## Policy, Default Evictor and Strategy plugins
|
||||||
|
|
||||||
**⚠️ v1alpha1 configuration is still suported, but deprecated (and soon will be removed). Please consider migrating to v1alpha2 (described bellow). For previous v1alpha1 documentation go to [docs/deprecated/v1alpha1.md](docs/deprecated/v1alpha1.md) ⚠️**
|
**⚠️ v1alpha1 configuration is still supported, but deprecated (and soon will be removed). Please consider migrating to v1alpha2 (described bellow). For previous v1alpha1 documentation go to [docs/deprecated/v1alpha1.md](docs/deprecated/v1alpha1.md) ⚠️**
|
||||||
|
|
||||||
The Descheduler Policy is configurable and includes default strategy plugins that can be enabled or disabled. It includes a common eviction configuration at the top level, as well as configuration from the Evictor plugin (Default Evictor, if not specified otherwise). Top-level configuration and Evictor plugin configuration are applied to all evictions.
|
The Descheduler Policy is configurable and includes default strategy plugins that can be enabled or disabled. It includes a common eviction configuration at the top level, as well as configuration from the Evictor plugin (Default Evictor, if not specified otherwise). Top-level configuration and Evictor plugin configuration are applied to all evictions.
|
||||||
|
|
||||||
@@ -525,6 +525,11 @@ This strategy requires k8s version 1.18 at a minimum.
|
|||||||
By default, this strategy only deals with hard constraints, setting parameter `includeSoftConstraints` to `true` will
|
By default, this strategy only deals with hard constraints, setting parameter `includeSoftConstraints` to `true` will
|
||||||
include soft constraints.
|
include soft constraints.
|
||||||
|
|
||||||
|
The `topologyBalanceNodeFit` arg is used when balancing topology domains while the Default Evictor's `nodeFit` is used in pre-eviction to determine if a pod can be evicted.
|
||||||
|
```yaml
|
||||||
|
topologyBalanceNodeFit: false
|
||||||
|
```
|
||||||
|
|
||||||
Strategy parameter `labelSelector` is not utilized when balancing topology domains and is only applied during eviction to determine if the pod can be evicted.
|
Strategy parameter `labelSelector` is not utilized when balancing topology domains and is only applied during eviction to determine if the pod can be evicted.
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
@@ -534,6 +539,7 @@ Strategy parameter `labelSelector` is not utilized when balancing topology domai
|
|||||||
|`includeSoftConstraints`|bool|
|
|`includeSoftConstraints`|bool|
|
||||||
|`namespaces`|(see [namespace filtering](#namespace-filtering))|
|
|`namespaces`|(see [namespace filtering](#namespace-filtering))|
|
||||||
|`labelSelector`|(see [label filtering](#label-filtering))|
|
|`labelSelector`|(see [label filtering](#label-filtering))|
|
||||||
|
|`topologyBalanceNodeFit`|bool|default `true`. [node fit filtering](#node-fit-filtering) when balancing topology domains|
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
utilpointer "k8s.io/utils/pointer"
|
||||||
"sigs.k8s.io/descheduler/pkg/api"
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
"sigs.k8s.io/descheduler/pkg/framework/plugins/nodeutilization"
|
"sigs.k8s.io/descheduler/pkg/framework/plugins/nodeutilization"
|
||||||
"sigs.k8s.io/descheduler/pkg/framework/plugins/podlifetime"
|
"sigs.k8s.io/descheduler/pkg/framework/plugins/podlifetime"
|
||||||
@@ -174,6 +175,7 @@ var StrategyParamsToPluginArgs = map[string]func(params *StrategyParameters) (*a
|
|||||||
Namespaces: v1alpha1NamespacesToInternal(params.Namespaces),
|
Namespaces: v1alpha1NamespacesToInternal(params.Namespaces),
|
||||||
LabelSelector: params.LabelSelector,
|
LabelSelector: params.LabelSelector,
|
||||||
IncludeSoftConstraints: params.IncludeSoftConstraints,
|
IncludeSoftConstraints: params.IncludeSoftConstraints,
|
||||||
|
TopologyBalanceNodeFit: utilpointer.Bool(true),
|
||||||
}
|
}
|
||||||
if err := removepodsviolatingtopologyspreadconstraint.ValidateRemovePodsViolatingTopologySpreadConstraintArgs(args); err != nil {
|
if err := removepodsviolatingtopologyspreadconstraint.ValidateRemovePodsViolatingTopologySpreadConstraintArgs(args); err != nil {
|
||||||
klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatingtopologyspreadconstraint.PluginName)
|
klog.ErrorS(err, "unable to validate plugin arguments", "pluginName", removepodsviolatingtopologyspreadconstraint.PluginName)
|
||||||
|
|||||||
@@ -568,6 +568,7 @@ func TestStrategyParamsToPluginArgsRemovePodsViolatingTopologySpreadConstraint(t
|
|||||||
Name: removepodsviolatingtopologyspreadconstraint.PluginName,
|
Name: removepodsviolatingtopologyspreadconstraint.PluginName,
|
||||||
Args: &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{
|
Args: &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{
|
||||||
IncludeSoftConstraints: true,
|
IncludeSoftConstraints: true,
|
||||||
|
TopologyBalanceNodeFit: utilpointer.Bool(true),
|
||||||
Namespaces: &api.Namespaces{
|
Namespaces: &api.Namespaces{
|
||||||
Exclude: []string{"test1"},
|
Exclude: []string{"test1"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -399,7 +399,9 @@ func TestV1alpha1ToV1alpha2(t *testing.T) {
|
|||||||
defaultEvictorPluginConfig,
|
defaultEvictorPluginConfig,
|
||||||
{
|
{
|
||||||
Name: removepodsviolatingtopologyspreadconstraint.PluginName,
|
Name: removepodsviolatingtopologyspreadconstraint.PluginName,
|
||||||
Args: &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{},
|
Args: &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{
|
||||||
|
TopologyBalanceNodeFit: utilpointer.Bool(true),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Plugins: api.Plugins{
|
Plugins: api.Plugins{
|
||||||
@@ -715,6 +717,7 @@ func TestV1alpha1ToV1alpha2(t *testing.T) {
|
|||||||
Name: removepodsviolatingtopologyspreadconstraint.PluginName,
|
Name: removepodsviolatingtopologyspreadconstraint.PluginName,
|
||||||
Args: &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{
|
Args: &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{
|
||||||
IncludeSoftConstraints: true,
|
IncludeSoftConstraints: true,
|
||||||
|
TopologyBalanceNodeFit: utilpointer.Bool(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ package removepodsviolatingtopologyspreadconstraint
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
utilpointer "k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||||
@@ -34,4 +35,7 @@ func SetDefaults_RemovePodsViolatingTopologySpreadConstraintArgs(obj runtime.Obj
|
|||||||
if !args.IncludeSoftConstraints {
|
if !args.IncludeSoftConstraints {
|
||||||
args.IncludeSoftConstraints = false
|
args.IncludeSoftConstraints = false
|
||||||
}
|
}
|
||||||
|
if args.TopologyBalanceNodeFit == nil {
|
||||||
|
args.TopologyBalanceNodeFit = utilpointer.Bool(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,20 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
utilpointer "k8s.io/utils/pointer"
|
||||||
"sigs.k8s.io/descheduler/pkg/api"
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var scheme *runtime.Scheme
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
scheme = runtime.NewScheme()
|
||||||
|
scheme.AddTypeDefaultingFunc(&RemovePodsViolatingTopologySpreadConstraintArgs{}, func(obj interface{}) {
|
||||||
|
SetDefaults_RemovePodsViolatingTopologySpreadConstraintArgs(obj.(*RemovePodsViolatingTopologySpreadConstraintArgs))
|
||||||
|
})
|
||||||
|
utilruntime.Must(AddToScheme(scheme))
|
||||||
|
}
|
||||||
|
|
||||||
func TestSetDefaults_RemovePodsViolatingTopologySpreadConstraintArgs(t *testing.T) {
|
func TestSetDefaults_RemovePodsViolatingTopologySpreadConstraintArgs(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -36,6 +47,7 @@ func TestSetDefaults_RemovePodsViolatingTopologySpreadConstraintArgs(t *testing.
|
|||||||
Namespaces: nil,
|
Namespaces: nil,
|
||||||
LabelSelector: nil,
|
LabelSelector: nil,
|
||||||
IncludeSoftConstraints: false,
|
IncludeSoftConstraints: false,
|
||||||
|
TopologyBalanceNodeFit: utilpointer.Bool(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -49,12 +61,27 @@ func TestSetDefaults_RemovePodsViolatingTopologySpreadConstraintArgs(t *testing.
|
|||||||
Namespaces: &api.Namespaces{},
|
Namespaces: &api.Namespaces{},
|
||||||
LabelSelector: &metav1.LabelSelector{},
|
LabelSelector: &metav1.LabelSelector{},
|
||||||
IncludeSoftConstraints: true,
|
IncludeSoftConstraints: true,
|
||||||
|
TopologyBalanceNodeFit: utilpointer.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RemovePodsViolatingTopologySpreadConstraintArgs without TopologyBalanceNodeFit",
|
||||||
|
in: &RemovePodsViolatingTopologySpreadConstraintArgs{},
|
||||||
|
want: &RemovePodsViolatingTopologySpreadConstraintArgs{
|
||||||
|
TopologyBalanceNodeFit: utilpointer.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RemovePodsViolatingTopologySpreadConstraintArgs with TopologyBalanceNodeFit=false",
|
||||||
|
in: &RemovePodsViolatingTopologySpreadConstraintArgs{
|
||||||
|
TopologyBalanceNodeFit: utilpointer.Bool(false),
|
||||||
|
},
|
||||||
|
want: &RemovePodsViolatingTopologySpreadConstraintArgs{
|
||||||
|
TopologyBalanceNodeFit: utilpointer.Bool(false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
scheme := runtime.NewScheme()
|
|
||||||
utilruntime.Must(AddToScheme(scheme))
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
scheme.Default(tc.in)
|
scheme.Default(tc.in)
|
||||||
if diff := cmp.Diff(tc.in, tc.want); diff != "" {
|
if diff := cmp.Diff(tc.in, tc.want); diff != "" {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
utilpointer "k8s.io/utils/pointer"
|
||||||
|
|
||||||
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
||||||
"sigs.k8s.io/descheduler/pkg/descheduler/node"
|
"sigs.k8s.io/descheduler/pkg/descheduler/node"
|
||||||
@@ -197,7 +198,7 @@ func (d *RemovePodsViolatingTopologySpreadConstraint) Balance(ctx context.Contex
|
|||||||
klog.V(2).InfoS("Skipping topology constraint because it is already balanced", "constraint", constraint)
|
klog.V(2).InfoS("Skipping topology constraint because it is already balanced", "constraint", constraint)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
balanceDomains(d.handle.GetPodsAssignedToNodeFunc(), podsForEviction, constraint, constraintTopologies, sumPods, d.handle.Evictor().Filter, nodes)
|
d.balanceDomains(podsForEviction, constraint, constraintTopologies, sumPods, nodes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,17 +271,18 @@ func topologyIsBalanced(topology map[topologyPair][]*v1.Pod, constraint v1.Topol
|
|||||||
// Following this, the above topology domains end up "sorted" as:
|
// Following this, the above topology domains end up "sorted" as:
|
||||||
// [5, 5, 5, 5, 5, 5]
|
// [5, 5, 5, 5, 5, 5]
|
||||||
// (assuming even distribution by the scheduler of the evicted pods)
|
// (assuming even distribution by the scheduler of the evicted pods)
|
||||||
func balanceDomains(
|
func (d *RemovePodsViolatingTopologySpreadConstraint) balanceDomains(
|
||||||
getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc,
|
|
||||||
podsForEviction map[*v1.Pod]struct{},
|
podsForEviction map[*v1.Pod]struct{},
|
||||||
constraint v1.TopologySpreadConstraint,
|
constraint v1.TopologySpreadConstraint,
|
||||||
constraintTopologies map[topologyPair][]*v1.Pod,
|
constraintTopologies map[topologyPair][]*v1.Pod,
|
||||||
sumPods float64,
|
sumPods float64,
|
||||||
isEvictable func(pod *v1.Pod) bool,
|
|
||||||
nodes []*v1.Node,
|
nodes []*v1.Node,
|
||||||
) {
|
) {
|
||||||
idealAvg := sumPods / float64(len(constraintTopologies))
|
idealAvg := sumPods / float64(len(constraintTopologies))
|
||||||
|
isEvictable := d.handle.Evictor().Filter
|
||||||
sortedDomains := sortDomains(constraintTopologies, isEvictable)
|
sortedDomains := sortDomains(constraintTopologies, isEvictable)
|
||||||
|
getPodsAssignedToNode := d.handle.GetPodsAssignedToNodeFunc()
|
||||||
|
topologyBalanceNodeFit := utilpointer.BoolDeref(d.args.TopologyBalanceNodeFit, true)
|
||||||
|
|
||||||
nodesBelowIdealAvg := filterNodesBelowIdealAvg(nodes, sortedDomains, constraint.TopologyKey, idealAvg)
|
nodesBelowIdealAvg := filterNodesBelowIdealAvg(nodes, sortedDomains, constraint.TopologyKey, idealAvg)
|
||||||
|
|
||||||
@@ -335,7 +337,7 @@ func balanceDomains(
|
|||||||
// This is because the chosen pods aren't sorted, but immovable pods still count as "evicted" toward the PTS algorithm.
|
// This is because the chosen pods aren't sorted, but immovable pods still count as "evicted" toward the PTS algorithm.
|
||||||
// So, a better selection heuristic could improve performance.
|
// So, a better selection heuristic could improve performance.
|
||||||
|
|
||||||
if !node.PodFitsAnyOtherNode(getPodsAssignedToNode, aboveToEvict[k], nodesBelowIdealAvg) {
|
if topologyBalanceNodeFit && !node.PodFitsAnyOtherNode(getPodsAssignedToNode, aboveToEvict[k], nodesBelowIdealAvg) {
|
||||||
klog.V(2).InfoS("ignoring pod for eviction as it does not fit on any other node", "pod", klog.KObj(aboveToEvict[k]))
|
klog.V(2).InfoS("ignoring pod for eviction as it does not fit on any other node", "pod", klog.KObj(aboveToEvict[k]))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package removepodsviolatingtopologyspreadconstraint
|
package removepodsviolatingtopologyspreadconstraint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"sigs.k8s.io/descheduler/pkg/api"
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
)
|
)
|
||||||
@@ -28,7 +29,8 @@ import (
|
|||||||
type RemovePodsViolatingTopologySpreadConstraintArgs struct {
|
type RemovePodsViolatingTopologySpreadConstraintArgs struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
Namespaces *api.Namespaces `json:"namespaces"`
|
Namespaces *api.Namespaces `json:"namespaces"`
|
||||||
LabelSelector *metav1.LabelSelector `json:"labelSelector"`
|
LabelSelector *metav1.LabelSelector `json:"labelSelector"`
|
||||||
IncludeSoftConstraints bool `json:"includeSoftConstraints"`
|
Constraints []v1.UnsatisfiableConstraintAction `json:"constraints"`
|
||||||
|
TopologyBalanceNodeFit *bool `json:"topologyBalanceNodeFit"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ func (in *RemovePodsViolatingTopologySpreadConstraintArgs) DeepCopyInto(out *Rem
|
|||||||
*out = new(v1.LabelSelector)
|
*out = new(v1.LabelSelector)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.TopologyBalanceNodeFit != nil {
|
||||||
|
in, out := &in.TopologyBalanceNodeFit, &out.TopologyBalanceNodeFit
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user