mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-01-26 21:31:18 +01:00
The new context.Context can be later used for passing a contextualized logger. Or, other initialization steps that require the context.
184 lines
6.7 KiB
Go
184 lines
6.7 KiB
Go
/*
|
|
Copyright 2025 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 example
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"time"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/klog/v2"
|
|
|
|
fwtypes "sigs.k8s.io/descheduler/pkg/framework/types"
|
|
|
|
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
|
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
|
)
|
|
|
|
// PluginName is used when registering the plugin. You need to choose a unique
|
|
// name across all plugins. This name is used to identify the plugin config in
|
|
// the descheduler policy.
|
|
const PluginName = "Example"
|
|
|
|
// We need to ensure that the plugin struct complies with the DeschedulePlugin
|
|
// interface. This prevent unexpected changes that may render this type
|
|
// incompatible.
|
|
var _ fwtypes.DeschedulePlugin = &Example{}
|
|
|
|
// Example is our plugin (implementing the DeschedulePlugin interface). This
|
|
// plugin will evict pods that match a regex and are older than a certain age.
|
|
type Example struct {
|
|
handle fwtypes.Handle
|
|
args *ExampleArgs
|
|
podFilter podutil.FilterFunc
|
|
}
|
|
|
|
// New builds a plugin instance from its arguments. Arguments are passed in as
|
|
// a runtime.Object. Handle is used by plugins to retrieve a kubernetes client
|
|
// set, evictor interface, shared informer factory and other instruments shared
|
|
// across different plugins.
|
|
func New(ctx context.Context, args runtime.Object, handle fwtypes.Handle) (fwtypes.Plugin, error) {
|
|
// make sure we are receiving the right argument type.
|
|
exampleArgs, ok := args.(*ExampleArgs)
|
|
if !ok {
|
|
return nil, fmt.Errorf("args must be of type ExampleArgs, got %T", args)
|
|
}
|
|
|
|
// we can use the included and excluded namespaces to filter the pods we want
|
|
// to evict.
|
|
var includedNamespaces, excludedNamespaces sets.Set[string]
|
|
if exampleArgs.Namespaces != nil {
|
|
includedNamespaces = sets.New(exampleArgs.Namespaces.Include...)
|
|
excludedNamespaces = sets.New(exampleArgs.Namespaces.Exclude...)
|
|
}
|
|
|
|
// here we create a pod filter that will return only pods that can be
|
|
// evicted (according to the evictor and inside the namespaces we want).
|
|
// NOTE: here we could also add a function to filter out by the regex and
|
|
// age but for sake of the example we are keeping it simple and filtering
|
|
// those out in the Deschedule() function.
|
|
podFilter, err := podutil.NewOptions().
|
|
WithNamespaces(includedNamespaces).
|
|
WithoutNamespaces(excludedNamespaces).
|
|
WithFilter(
|
|
podutil.WrapFilterFuncs(
|
|
handle.Evictor().Filter,
|
|
handle.Evictor().PreEvictionFilter,
|
|
),
|
|
).
|
|
BuildFilterFunc()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error initializing pod filter function: %v", err)
|
|
}
|
|
|
|
return &Example{
|
|
handle: handle,
|
|
podFilter: podFilter,
|
|
args: exampleArgs,
|
|
}, nil
|
|
}
|
|
|
|
// Name returns the plugin name.
|
|
func (d *Example) Name() string {
|
|
return PluginName
|
|
}
|
|
|
|
// Deschedule is the function where most of the logic around eviction is laid
|
|
// down. Here we go through all pods in all nodes and evict the ones that match
|
|
// the regex and are older than the maximum age. This function receives a list
|
|
// of nodes we need to process.
|
|
func (d *Example) Deschedule(ctx context.Context, nodes []*v1.Node) *fwtypes.Status {
|
|
var podsToEvict []*v1.Pod
|
|
logger := klog.FromContext(ctx)
|
|
logger.Info("Example plugin starting descheduling")
|
|
|
|
re, err := regexp.Compile(d.args.Regex)
|
|
if err != nil {
|
|
err = fmt.Errorf("fail to compile regex: %w", err)
|
|
return &fwtypes.Status{Err: err}
|
|
}
|
|
|
|
duration, err := time.ParseDuration(d.args.MaxAge)
|
|
if err != nil {
|
|
err = fmt.Errorf("fail to parse max age: %w", err)
|
|
return &fwtypes.Status{Err: err}
|
|
}
|
|
|
|
// here we create an auxiliar filter to remove all pods that don't
|
|
// match the provided regex or are still too young to be evicted.
|
|
// This filter will be used when we list all pods on a node. This
|
|
// filter here could have been part of the podFilter but we are
|
|
// keeping it separate for the sake of the example.
|
|
filter := func(pod *v1.Pod) bool {
|
|
if !re.MatchString(pod.Name) {
|
|
return false
|
|
}
|
|
deadline := pod.CreationTimestamp.Add(duration)
|
|
return time.Now().After(deadline)
|
|
}
|
|
|
|
// go node by node getting all pods that we can evict.
|
|
for _, node := range nodes {
|
|
// ListAllPodsOnANode is a helper function that retrieves all pods filtering out the ones we can't evict.
|
|
// ListPodsOnANode is a helper function that retrieves all pods(excluding Succeeded or Failed phases) filtering out the ones we can't evict.
|
|
// We merge the default filters with the one we created above.
|
|
//
|
|
// The difference between ListPodsOnANode and ListAllPodsOnANode lies in their handling of Pods based on their phase:
|
|
// - ListPodsOnANode excludes Pods that are in Succeeded or Failed phases because they do not occupy any resources.
|
|
// - ListAllPodsOnANode does not exclude Pods based on their phase, listing all Pods regardless of their state.
|
|
//
|
|
// In this context, we prefer using ListPodsOnANode because:
|
|
// 1. It ensures that only active Pods (not in Succeeded or Failed states) are considered for eviction.
|
|
// 2. This helps avoid unnecessary processing of Pods that no longer consume resources.
|
|
// 3. By applying an additional filter (d.podFilter and filter), we can further refine which Pods are eligible for eviction,
|
|
// ensuring that only Pods meeting specific criteria are selected.
|
|
//
|
|
// However, if you need to consider all Pods including those in Succeeded or Failed states for other purposes,
|
|
// you should use ListAllPodsOnANode instead.
|
|
pods, err := podutil.ListPodsOnANode(
|
|
node.Name,
|
|
d.handle.GetPodsAssignedToNodeFunc(),
|
|
podutil.WrapFilterFuncs(d.podFilter, filter),
|
|
)
|
|
if err != nil {
|
|
err = fmt.Errorf("fail to list pods: %w", err)
|
|
return &fwtypes.Status{Err: err}
|
|
}
|
|
|
|
// as we have already filtered out pods that don't match the
|
|
// regex or are too young we can simply add them all to the
|
|
// eviction list.
|
|
podsToEvict = append(podsToEvict, pods...)
|
|
}
|
|
|
|
// evict all the pods.
|
|
for _, pod := range podsToEvict {
|
|
logger.Info("Example plugin evicting pod", "pod", klog.KObj(pod))
|
|
opts := evictions.EvictOptions{StrategyName: PluginName}
|
|
if err := d.handle.Evictor().Evict(ctx, pod, opts); err != nil {
|
|
logger.Error(err, "unable to evict pod", "pod", klog.KObj(pod))
|
|
}
|
|
}
|
|
|
|
logger.Info("Example plugin finished descheduling")
|
|
return nil
|
|
}
|