Allow users to specify which schedulers to ignore

This commit is contained in:
Mahmoud Atwa 2023-11-08 10:15:22 +00:00
parent cfbfaa271a
commit 4635a6dc04
7 changed files with 58 additions and 6 deletions

View File

@ -281,4 +281,7 @@ type AutoscalingOptions struct {
//for scheduler to mark pods as unschedulable and will process both marked & non-marked pods
//it will also signal whether we enable/disable waiting for pod time buffers before triggering a scale-up.
IgnoreSchedulerProcessing bool
//IgnoredSchedulers are used to specify which schedulers to ignore their processing
//if IgnoreSchedulerProcessing is set to true
IgnoredSchedulers map[string]bool
}

View File

@ -311,7 +311,7 @@ func (a *StaticAutoscaler) RunOnce(currentTime time.Time) caerrors.AutoscalerErr
originalScheduledPods, unschedulablePods := kube_util.ScheduledPods(pods), kube_util.UnschedulablePods(pods)
schedulerUnprocessed := make([]*apiv1.Pod, 0, 0)
if a.IgnoreSchedulerProcessing {
schedulerUnprocessed = kube_util.SchedulerUnprocessedPods(pods)
schedulerUnprocessed = kube_util.SchedulerUnprocessedPods(pods, a.IgnoredSchedulers)
}
// Update cluster resource usage metrics

View File

@ -992,6 +992,9 @@ func TestStaticAutoscalerRunOnceWithFilteringOnUpcomingNodesEnabledNoScaleUp(t *
}
func TestStaticAutoscalerRunOnceWithSchedulerProcessingIgnored(t *testing.T) {
ignoredScheduler := "ignored-scheduler"
nonIgnoredScheduler := "non-ignored-scheduler"
readyNodeLister := kubernetes.NewTestNodeLister(nil)
allNodeLister := kubernetes.NewTestNodeLister(nil)
allPodListerMock := &podListerMock{}
@ -1010,7 +1013,9 @@ func TestStaticAutoscalerRunOnceWithSchedulerProcessingIgnored(t *testing.T) {
p1 := BuildTestPod("p1", 600, 100)
p1.Spec.NodeName = "n1"
p2 := BuildTestPod("p2", 600, 100, MarkUnschedulable())
p3 := BuildTestPod("p3", 600, 100) // Not yet processed by scheduler
p3 := BuildTestPod("p3", 600, 100, AddSchedulerName(apiv1.DefaultSchedulerName)) // Not yet processed by scheduler, default scheduler is ignored
p4 := BuildTestPod("p4", 600, 100, AddSchedulerName(ignoredScheduler)) // non-default scheduler & ignored, expects a scale-up
p5 := BuildTestPod("p5", 600, 100, AddSchedulerName(nonIgnoredScheduler)) // non-default scheduler & not ignored, shouldn't cause a scale-up
provider := testprovider.NewTestCloudProvider(
func(id string, delta int) error {
@ -1041,6 +1046,10 @@ func TestStaticAutoscalerRunOnceWithSchedulerProcessingIgnored(t *testing.T) {
MaxCoresTotal: 10,
MaxMemoryTotal: 100000,
IgnoreSchedulerProcessing: true,
IgnoredSchedulers: map[string]bool{
apiv1.DefaultSchedulerName: true,
ignoredScheduler: true,
},
}
processorCallbacks := newStaticAutoscalerProcessorCallbacks()
@ -1083,10 +1092,10 @@ func TestStaticAutoscalerRunOnceWithSchedulerProcessingIgnored(t *testing.T) {
// Scale up.
readyNodeLister.SetNodes([]*apiv1.Node{n1})
allNodeLister.SetNodes([]*apiv1.Node{n1})
allPodListerMock.On("List").Return([]*apiv1.Pod{p1, p2, p3}, nil).Twice()
allPodListerMock.On("List").Return([]*apiv1.Pod{p1, p2, p3, p4, p5}, nil).Twice()
daemonSetListerMock.On("List", labels.Everything()).Return([]*appsv1.DaemonSet{}, nil).Once()
podDisruptionBudgetListerMock.On("List").Return([]*policyv1.PodDisruptionBudget{}, nil).Once()
onScaleUpMock.On("ScaleUp", "ng1", 2).Return(nil).Once()
onScaleUpMock.On("ScaleUp", "ng1", 3).Return(nil).Once()
err = autoscaler.RunOnce(later.Add(time.Hour))
assert.NoError(t, err)

View File

@ -31,6 +31,7 @@ import (
"github.com/spf13/pflag"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/server/mux"
@ -244,6 +245,7 @@ var (
forceDaemonSets = flag.Bool("force-ds", false, "Blocks scale-up of node groups too small for all suitable Daemon Sets pods.")
dynamicNodeDeleteDelayAfterTaintEnabled = flag.Bool("dynamic-node-delete-delay-after-taint-enabled", false, "Enables dynamic adjustment of NodeDeleteDelayAfterTaint based of the latency between CA and api-server")
ignoreSchedulerProcessing = flag.Bool("ignore-scheduler-processing", false, "If true, cluster autoscaler will not wait for scheduler to mark pods as unschedulable and will process both marked & non-marked pods (Schedulable pods will be filtered before scaling-up) it will also disable waiting for pod time buffers before triggering a scale-up.")
ignoredSchedulers = pflag.StringSlice("ignore-schedulers", []string{apiv1.DefaultSchedulerName}, fmt.Sprintf("Names of schedulers to be ignored if '--ignore-scheduler-processing' is set to true. default value '%s' is used", apiv1.DefaultSchedulerName))
)
func isFlagPassed(name string) bool {
@ -392,6 +394,7 @@ func createAutoscalingOptions() config.AutoscalingOptions {
},
DynamicNodeDeleteDelayAfterTaintEnabled: *dynamicNodeDeleteDelayAfterTaintEnabled,
IgnoreSchedulerProcessing: *ignoreSchedulerProcessing,
IgnoredSchedulers: scheduler_util.GetIgnoredSchedulersMap(*ignoredSchedulers),
}
}

View File

@ -144,18 +144,23 @@ type PodLister interface {
List() ([]*apiv1.Pod, error)
}
// isScheduled checks whether a pod is scheduled on a node or not
func isScheduled(pod *apiv1.Pod) bool {
if pod == nil {
return false
}
return pod.Spec.NodeName != ""
}
// isDeleted checks whether a pod is deleted not
func isDeleted(pod *apiv1.Pod) bool {
if pod == nil {
return false
}
return pod.GetDeletionTimestamp() != nil
}
// isUnschedulable checks whether a pod is unschedulable or not
func isUnschedulable(pod *apiv1.Pod) bool {
if pod == nil {
return false
@ -170,6 +175,12 @@ func isUnschedulable(pod *apiv1.Pod) bool {
return true
}
// getIsDefaultSchedulerIgnored checks if the default scheduler should be ignored or not
func getIsDefaultSchedulerIgnored(ignoredSchedulers map[string]bool) bool {
ignored, ok := ignoredSchedulers[apiv1.DefaultSchedulerName]
return ignored && ok
}
// ScheduledPods is a helper method that returns all scheduled pods from given pod list.
func ScheduledPods(allPods []*apiv1.Pod) []*apiv1.Pod {
var scheduledPods []*apiv1.Pod
@ -182,10 +193,20 @@ func ScheduledPods(allPods []*apiv1.Pod) []*apiv1.Pod {
return scheduledPods
}
// SchedulerUnprocessedPods is a helper method that returns all pods which are not yet processed by the scheduler
func SchedulerUnprocessedPods(allPods []*apiv1.Pod) []*apiv1.Pod {
// SchedulerUnprocessedPods is a helper method that returns all pods which are not yet processed by the specified ignored schedulers
func SchedulerUnprocessedPods(allPods []*apiv1.Pod, ignoredSchedulers map[string]bool) []*apiv1.Pod {
var unprocessedPods []*apiv1.Pod
isDefaultSchedulerIgnored := getIsDefaultSchedulerIgnored(ignoredSchedulers)
for _, pod := range allPods {
// Don't add a pod with a scheduler that isn't specified by the user
if !isDefaultSchedulerIgnored && pod.Spec.SchedulerName == "" {
continue
}
if isIgnored, found := ignoredSchedulers[pod.Spec.SchedulerName]; !found || !isIgnored {
continue
}
// Make sure it's not scheduled or deleted
if isScheduled(pod) || isDeleted(pod) || isUnschedulable(pod) {
continue

View File

@ -147,3 +147,12 @@ func ConfigFromPath(path string) (*scheduler_config.KubeSchedulerConfiguration,
return cfgObj, nil
}
// GetIgnoredSchedulersMap returns a map of scheduler names that should be ignored as keys, and values are set to true
func GetIgnoredSchedulersMap(ignoredSchedulers []string) map[string]bool {
ignoredSchedulersMap := make(map[string]bool, len(ignoredSchedulers))
for _, scheduler := range ignoredSchedulers {
ignoredSchedulersMap[scheduler] = true
}
return ignoredSchedulersMap
}

View File

@ -82,6 +82,13 @@ func MarkUnschedulable() func(*apiv1.Pod) {
}
}
// AddSchedulerName adds scheduler name to a pod.
func AddSchedulerName(schedulerName string) func(*apiv1.Pod) {
return func(pod *apiv1.Pod) {
pod.Spec.SchedulerName = schedulerName
}
}
// BuildDSTestPod creates a DaemonSet pod with cpu and memory.
func BuildDSTestPod(name string, cpu int64, mem int64) *apiv1.Pod {