pkg/trimaran/loadvariationriskbalancing/loadvariationriskbalancing.go (104 lines of code) (raw):

/* Copyright 2021 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 loadvariationriskbalancing plugin attempts to balance the risk in load variation // across the cluster. Risk is expressed as the sum of average and standard deviation of // the measured load on a node. Typical load balancing involves only the average load. // Here, we consider the variation in load as well, hence resulting in a safer balance. package loadvariationriskbalancing import ( "context" "fmt" "math" "github.com/paypal/load-watcher/pkg/watcher" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/scheduler/framework" "sigs.k8s.io/scheduler-plugins/pkg/trimaran" ) const ( // Name : name of plugin Name = "LoadVariationRiskBalancing" ) // LoadVariationRiskBalancing : scheduler plugin type LoadVariationRiskBalancing struct { handle framework.Handle eventHandler *trimaran.PodAssignEventHandler collector *Collector } // New : create an instance of a LoadVariationRiskBalancing plugin func New(obj runtime.Object, handle framework.Handle) (framework.Plugin, error) { klog.V(4).Infof("Creating new instance of the LoadVariationRiskBalancing plugin") collector, err := newCollector(obj) if err != nil { return nil, err } podAssignEventHandler := trimaran.New() handle.SharedInformerFactory().Core().V1().Pods().Informer().AddEventHandler( cache.FilteringResourceEventHandler{ FilterFunc: func(obj interface{}) bool { switch t := obj.(type) { case *v1.Pod: return isAssigned(t) case cache.DeletedFinalStateUnknown: if pod, ok := t.Obj.(*v1.Pod); ok { return isAssigned(pod) } utilruntime.HandleError(fmt.Errorf("unable to convert object %T to *v1.Pod", obj)) return false default: utilruntime.HandleError(fmt.Errorf("unable to handle object: %T", obj)) return false } }, Handler: podAssignEventHandler, }, ) pl := &LoadVariationRiskBalancing{ handle: handle, eventHandler: podAssignEventHandler, collector: collector, } return pl, nil } // Score : evaluate score for a node func (pl *LoadVariationRiskBalancing) Score(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) { klog.V(6).Infof("Calculating score for pod %q on node %q", pod.GetName(), nodeName) score := framework.MinNodeScore nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName) if err != nil { return score, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err)) } // get node metrics metrics := pl.collector.getNodeMetrics(nodeName) if metrics == nil { klog.Warningf("failure getting metrics for node %q; using minimum score", nodeName) return score, nil } podRequest := getResourceRequested(pod) node := nodeInfo.Node() // calculate CPU score var cpuScore float64 = 0 cpuStats, cpuOK := createResourceStats(metrics, node, podRequest, v1.ResourceCPU, watcher.CPU) if cpuOK { cpuScore = cpuStats.computeScore(pl.collector.args.SafeVarianceMargin, pl.collector.args.SafeVarianceSensitivity) } klog.V(6).Infof("pod:%s; node:%s; CPUScore=%f", pod.GetName(), nodeName, cpuScore) // calculate Memory score var memoryScore float64 = 0 memoryStats, memoryOK := createResourceStats(metrics, node, podRequest, v1.ResourceMemory, watcher.Memory) if memoryOK { memoryScore = memoryStats.computeScore(pl.collector.args.SafeVarianceMargin, pl.collector.args.SafeVarianceSensitivity) } klog.V(6).Infof("pod:%s; node:%s; MemoryScore=%f", pod.GetName(), nodeName, memoryScore) // calculate total score var totalScore float64 = 0 if memoryOK && cpuOK { totalScore = math.Min(memoryScore, cpuScore) } else { totalScore = math.Max(memoryScore, cpuScore) } score = int64(math.Round(totalScore)) klog.V(6).Infof("pod:%s; node:%s; TotalScore=%d", pod.GetName(), nodeName, score) return score, framework.NewStatus(framework.Success, "") } // Name : name of plugin func (pl *LoadVariationRiskBalancing) Name() string { return Name } // ScoreExtensions : an interface for Score extended functionality func (pl *LoadVariationRiskBalancing) ScoreExtensions() framework.ScoreExtensions { return pl } // NormalizeScore : normalize scores func (pl *LoadVariationRiskBalancing) NormalizeScore(context.Context, *framework.CycleState, *v1.Pod, framework.NodeScoreList) *framework.Status { return nil } // Checks and returns true if the pod is assigned to a node func isAssigned(pod *v1.Pod) bool { return len(pod.Spec.NodeName) != 0 }