From 69c5ea03ce1063b6859426f73e491a5e2f31f1a5 Mon Sep 17 00:00:00 2001 From: Maciej Pytel Date: Wed, 30 Aug 2017 16:18:31 +0200 Subject: [PATCH] Disable MatchInterPodAffinity if there are no pods using affinity --- cluster-autoscaler/core/static_autoscaler.go | 2 ++ cluster-autoscaler/core/utils.go | 24 ++++++++++++++++++++ cluster-autoscaler/simulator/predicates.go | 24 ++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/cluster-autoscaler/core/static_autoscaler.go b/cluster-autoscaler/core/static_autoscaler.go index 8512e2d4ec..1df44edaad 100644 --- a/cluster-autoscaler/core/static_autoscaler.go +++ b/cluster-autoscaler/core/static_autoscaler.go @@ -187,6 +187,8 @@ func (a *StaticAutoscaler) RunOnce(currentTime time.Time) errors.AutoscalerError return errors.ToAutoscalerError(errors.ApiCallError, err) } + ConfigurePredicateCheckerForLoop(allUnschedulablePods, allScheduled, a.PredicateChecker) + // We need to check whether pods marked as unschedulable are actually unschedulable. // It's likely we added a new node and the scheduler just haven't managed to put the // pod on in yet. In this situation we don't want to trigger another scale-up. diff --git a/cluster-autoscaler/core/utils.go b/cluster-autoscaler/core/utils.go index 84724559c1..3ac8eec23f 100644 --- a/cluster-autoscaler/core/utils.go +++ b/cluster-autoscaler/core/utils.go @@ -387,3 +387,27 @@ func getPotentiallyUnneededNodes(context *AutoscalingContext, nodes []*apiv1.Nod } return result } + +// ConfigurePredicateCheckerForLoop can be run to update predicateChecker configuration +// based on current state of the cluster. +func ConfigurePredicateCheckerForLoop(unschedulablePods []*apiv1.Pod, schedulablePods []*apiv1.Pod, predicateChecker *simulator.PredicateChecker) { + podsWithAffinityFound := false + for _, pod := range unschedulablePods { + if pod.Spec.Affinity != nil { + podsWithAffinityFound = true + break + } + } + if !podsWithAffinityFound { + for _, pod := range schedulablePods { + if pod.Spec.Affinity != nil { + podsWithAffinityFound = true + break + } + } + } + predicateChecker.SetAffinityPredicateEnabled(podsWithAffinityFound) + if !podsWithAffinityFound { + glog.V(1).Info("No pod using affinity / antiaffinity found in cluster, disabling affinity predicate for this loop") + } +} diff --git a/cluster-autoscaler/simulator/predicates.go b/cluster-autoscaler/simulator/predicates.go index 9e9f5f7a26..b3c0918b02 100644 --- a/cluster-autoscaler/simulator/predicates.go +++ b/cluster-autoscaler/simulator/predicates.go @@ -46,6 +46,10 @@ const ( // This significantly improves performance and is useful if the error message // is discarded anyway. ReturnSimpleError ErrorVerbosity = false + + // We want to disable affinity predicate for performance reasons if no ppod + // requires it + affinityPredicateName = "MatchInterPodAffinity" ) type predicateInfo struct { @@ -57,6 +61,7 @@ type predicateInfo struct { type PredicateChecker struct { predicates []predicateInfo predicateMetadataProducer algorithm.MetadataProducer + enableAffinityPredicate bool } // there are no const arrays in go, this is meant to be used as a const @@ -124,6 +129,7 @@ func NewPredicateChecker(kubeClient kube_client.Interface, stop <-chan struct{}) return &PredicateChecker{ predicates: predicateList, predicateMetadataProducer: metadataProducer, + enableAffinityPredicate: true, }, nil } @@ -149,12 +155,24 @@ func NewTestPredicateChecker() *PredicateChecker { } } +// SetAffinityPredicateEnabled can be used to enable or disable checking MatchInterPodAffinity +// predicate. This will cause incorrect CA behavior if there is at least a single pod in +// cluster using affinity/antiaffinity. However, checking affinity predicate is extremly +// costly even if no pod is using it, so it may be worth disabling it in such situation. +func (p *PredicateChecker) SetAffinityPredicateEnabled(enable bool) { + p.enableAffinityPredicate = enable +} + // GetPredicateMetadata precomputes some information useful for running predicates on a given pod in a given state // of the cluster (represented by nodeInfos map). Passing the result of this function to CheckPredicates can significantly // improve the performance of running predicates, especially MatchInterPodAffinity predicate. However, calculating // predicateMetadata is also quite expensive, so it's not always the best option to run this method. // Please refer to https://github.com/kubernetes/autoscaler/issues/257 for more details. func (p *PredicateChecker) GetPredicateMetadata(pod *apiv1.Pod, nodeInfos map[string]*schedulercache.NodeInfo) interface{} { + // skip precomputation if affinity predicate is disabled - it's not worth it performance wise + if !p.enableAffinityPredicate { + return nil + } return p.predicateMetadataProducer(pod, nodeInfos) } @@ -183,6 +201,12 @@ func (p *PredicateChecker) FitsAny(pod *apiv1.Pod, nodeInfos map[string]*schedul // Alternatively you can pass nil as predicateMetadata. func (p *PredicateChecker) CheckPredicates(pod *apiv1.Pod, predicateMetadata interface{}, nodeInfo *schedulercache.NodeInfo, verbosity ErrorVerbosity) error { for _, predInfo := range p.predicates { + + // skip affinity predicate if it has been disabled + if !p.enableAffinityPredicate && predInfo.name == affinityPredicateName { + continue + } + match, failureReason, err := predInfo.predicate(pod, predicateMetadata, nodeInfo) if verbosity == ReturnSimpleError && (err != nil || !match) {