Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions examples/max-pods-per-node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
profiles:
- name: ProfileName
pluginConfig:
- name: "MaxPodsPerNode"
args:
maxPods: 30
namespaces:
include:
- "default"
- name: "DefaultEvictor"
args:
evictSystemCriticalPods: false
evictLocalStoragePods: true
plugins:
deschedule:
enabled:
- "MaxPodsPerNode"
filter:
enabled:
- "DefaultEvictor"
2 changes: 2 additions & 0 deletions pkg/descheduler/setupplugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package descheduler
import (
"sigs.k8s.io/descheduler/pkg/framework/pluginregistry"
"sigs.k8s.io/descheduler/pkg/framework/plugins/defaultevictor"
"sigs.k8s.io/descheduler/pkg/framework/plugins/maxpodspernode"
"sigs.k8s.io/descheduler/pkg/framework/plugins/nodeutilization"
"sigs.k8s.io/descheduler/pkg/framework/plugins/podlifetime"
"sigs.k8s.io/descheduler/pkg/framework/plugins/removeduplicates"
Expand Down Expand Up @@ -47,4 +48,5 @@ func RegisterDefaultPlugins(registry pluginregistry.Registry) {
pluginregistry.Register(removepodsviolatingnodeaffinity.PluginName, removepodsviolatingnodeaffinity.New, &removepodsviolatingnodeaffinity.RemovePodsViolatingNodeAffinity{}, &removepodsviolatingnodeaffinity.RemovePodsViolatingNodeAffinityArgs{}, removepodsviolatingnodeaffinity.ValidateRemovePodsViolatingNodeAffinityArgs, removepodsviolatingnodeaffinity.SetDefaults_RemovePodsViolatingNodeAffinityArgs, registry)
pluginregistry.Register(removepodsviolatingnodetaints.PluginName, removepodsviolatingnodetaints.New, &removepodsviolatingnodetaints.RemovePodsViolatingNodeTaints{}, &removepodsviolatingnodetaints.RemovePodsViolatingNodeTaintsArgs{}, removepodsviolatingnodetaints.ValidateRemovePodsViolatingNodeTaintsArgs, removepodsviolatingnodetaints.SetDefaults_RemovePodsViolatingNodeTaintsArgs, registry)
pluginregistry.Register(removepodsviolatingtopologyspreadconstraint.PluginName, removepodsviolatingtopologyspreadconstraint.New, &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraint{}, &removepodsviolatingtopologyspreadconstraint.RemovePodsViolatingTopologySpreadConstraintArgs{}, removepodsviolatingtopologyspreadconstraint.ValidateRemovePodsViolatingTopologySpreadConstraintArgs, removepodsviolatingtopologyspreadconstraint.SetDefaults_RemovePodsViolatingTopologySpreadConstraintArgs, registry)
pluginregistry.Register(maxpodspernode.PluginName, maxpodspernode.New, &maxpodspernode.MaxPodsPerNode{}, &maxpodspernode.MaxPodsPerNodeArgs{}, maxpodspernode.ValidateMaxPodsPerNodeArgs, maxpodspernode.SetDefaults_MaxPodsPerNodeArgs, registry)
}
36 changes: 36 additions & 0 deletions pkg/framework/plugins/maxpodspernode/defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package maxpodspernode

import (
"k8s.io/apimachinery/pkg/runtime"
)

func addDefaultingFuncs(scheme *runtime.Scheme) error {
return RegisterDefaults(scheme)
}

// SetDefaults_MaxPodsPerNodeArgs sets default values for MaxPodsPerNodeArgs
func SetDefaults_MaxPodsPerNodeArgs(obj runtime.Object) {
args := obj.(*MaxPodsPerNodeArgs)
if args.Namespaces == nil {
args.Namespaces = nil
}
if args.LabelSelector == nil {
args.LabelSelector = nil
}
if args.MaxPods == 0 {
args.MaxPods = 110 // default Kubernetes max pods per node
}
}
16 changes: 16 additions & 0 deletions pkg/framework/plugins/maxpodspernode/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// +k8s:defaulter-gen=TypeMeta

package maxpodspernode
129 changes: 129 additions & 0 deletions pkg/framework/plugins/maxpodspernode/maxpodspernode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package maxpodspernode

import (
"context"
"fmt"
"sort"

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog/v2"

"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
frameworktypes "sigs.k8s.io/descheduler/pkg/framework/types"
)

const PluginName = "MaxPodsPerNode"

// MaxPodsPerNode evicts pods from nodes that exceed the configured maximum pod count
type MaxPodsPerNode struct {
logger klog.Logger
handle frameworktypes.Handle
args *MaxPodsPerNodeArgs
podFilter podutil.FilterFunc
}

var _ frameworktypes.DeschedulePlugin = &MaxPodsPerNode{}

// New builds plugin from its arguments while passing a handle
func New(ctx context.Context, args runtime.Object, handle frameworktypes.Handle) (frameworktypes.Plugin, error) {
maxPodsArgs, ok := args.(*MaxPodsPerNodeArgs)
if !ok {
return nil, fmt.Errorf("want args to be of type MaxPodsPerNodeArgs, got %T", args)
}
logger := klog.FromContext(ctx).WithValues("plugin", PluginName)

var includedNamespaces, excludedNamespaces sets.Set[string]
if maxPodsArgs.Namespaces != nil {
includedNamespaces = sets.New(maxPodsArgs.Namespaces.Include...)
excludedNamespaces = sets.New(maxPodsArgs.Namespaces.Exclude...)
}

// We can combine Filter and PreEvictionFilter since for this strategy it does not matter where we run PreEvictionFilter
podFilter, err := podutil.NewOptions().
WithNamespaces(includedNamespaces).
WithoutNamespaces(excludedNamespaces).
WithLabelSelector(maxPodsArgs.LabelSelector).
BuildFilterFunc()
if err != nil {
return nil, fmt.Errorf("error initializing pod filter function: %v", err)
}

return &MaxPodsPerNode{
logger: logger,
handle: handle,
podFilter: podFilter,
args: maxPodsArgs,
}, nil
}

// Name retrieves the plugin name
func (d *MaxPodsPerNode) Name() string {
return PluginName
}

// Deschedule extension point implementation for the plugin
func (d *MaxPodsPerNode) Deschedule(ctx context.Context, nodes []*v1.Node) *frameworktypes.Status {
logger := klog.FromContext(klog.NewContext(ctx, d.logger)).WithValues("ExtensionPoint", frameworktypes.DescheduleExtensionPoint)

for _, node := range nodes {
logger.V(2).Info("Processing node", "node", klog.KObj(node))

pods, err := podutil.ListAllPodsOnANode(node.Name, d.handle.GetPodsAssignedToNodeFunc(), d.podFilter)
if err != nil {
return &frameworktypes.Status{
Err: fmt.Errorf("error listing pods on a node: %v", err),
}
}

podCount := len(pods)
if podCount <= d.args.MaxPods {
logger.V(2).Info("Node pod count within limit", "node", klog.KObj(node), "podCount", podCount, "maxPods", d.args.MaxPods)
continue
}

excessPods := podCount - d.args.MaxPods
logger.V(1).Info("Node exceeds max pods limit", "node", klog.KObj(node), "podCount", podCount, "maxPods", d.args.MaxPods, "excessPods", excessPods)

// Sort pods by creation timestamp (newest first) so we evict the most recently created pods
sort.Slice(pods, func(i, j int) bool {
return pods[j].CreationTimestamp.Before(&pods[i].CreationTimestamp)
})

evicted := 0
loop:
for i := 0; i < len(pods) && evicted < excessPods; i++ {
err := d.handle.Evictor().Evict(ctx, pods[i], evictions.EvictOptions{StrategyName: PluginName})
if err == nil {
evicted++
continue
}
switch err.(type) {
case *evictions.EvictionNodeLimitError:
break loop
case *evictions.EvictionTotalLimitError:
return nil
default:
logger.Error(err, "eviction failed", "pod", klog.KObj(pods[i]))
}
}

logger.V(1).Info("Evicted pods from node", "node", klog.KObj(node), "evictedCount", evicted)
}
return nil
}
31 changes: 31 additions & 0 deletions pkg/framework/plugins/maxpodspernode/register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package maxpodspernode

import (
"k8s.io/apimachinery/pkg/runtime"
)

var (
SchemeBuilder = runtime.NewSchemeBuilder()
localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme
)

func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addDefaultingFuncs)
}
33 changes: 33 additions & 0 deletions pkg/framework/plugins/maxpodspernode/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package maxpodspernode

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/descheduler/pkg/api"
)

// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// MaxPodsPerNodeArgs holds arguments used to configure the MaxPodsPerNode plugin.
type MaxPodsPerNodeArgs struct {
metav1.TypeMeta `json:",inline"`

Namespaces *api.Namespaces `json:"namespaces,omitempty"`
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
// MaxPods is the maximum number of pods allowed on a node.
// Pods will be evicted from nodes exceeding this count.
MaxPods int `json:"maxPods"`
}
45 changes: 45 additions & 0 deletions pkg/framework/plugins/maxpodspernode/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package maxpodspernode

import (
"fmt"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
)

// ValidateMaxPodsPerNodeArgs validates MaxPodsPerNode arguments
func ValidateMaxPodsPerNodeArgs(obj runtime.Object) error {
args := obj.(*MaxPodsPerNodeArgs)
var allErrs []error

if args.MaxPods <= 0 {
allErrs = append(allErrs, fmt.Errorf("maxPods must be a positive integer"))
}

// At most one of include/exclude can be set
if args.Namespaces != nil && len(args.Namespaces.Include) > 0 && len(args.Namespaces.Exclude) > 0 {
allErrs = append(allErrs, fmt.Errorf("only one of Include/Exclude namespaces can be set"))
}

if args.LabelSelector != nil {
if _, err := metav1.LabelSelectorAsSelector(args.LabelSelector); err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to get label selectors from strategy's params: %+v", err))
}
}

return utilerrors.NewAggregate(allErrs)
}
63 changes: 63 additions & 0 deletions pkg/framework/plugins/maxpodspernode/zz_generated.deepcopy.go

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

Loading