2015-08-17 12:18:26 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2015 The Kubernetes Authors .
2015-08-17 12:18:26 +00:00
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 .
* /
2015-09-10 13:10:07 +00:00
package podautoscaler
2015-08-17 12:18:26 +00:00
import (
2016-01-29 11:20:19 +00:00
"encoding/json"
2015-08-17 12:18:26 +00:00
"fmt"
2015-09-07 10:25:04 +00:00
"math"
2015-08-17 12:18:26 +00:00
"time"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
2016-01-29 11:20:19 +00:00
"k8s.io/kubernetes/pkg/api/resource"
2015-09-17 22:21:55 +00:00
"k8s.io/kubernetes/pkg/api/unversioned"
2016-05-05 10:27:24 +00:00
"k8s.io/kubernetes/pkg/apis/autoscaling"
2015-10-09 22:04:41 +00:00
"k8s.io/kubernetes/pkg/apis/extensions"
2016-03-02 08:29:17 +00:00
"k8s.io/kubernetes/pkg/client/cache"
2016-10-21 22:24:05 +00:00
unversionedautoscaling "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion"
unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
unversionedextensions "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion"
2015-09-14 13:08:43 +00:00
"k8s.io/kubernetes/pkg/client/record"
2015-09-10 13:10:07 +00:00
"k8s.io/kubernetes/pkg/controller/podautoscaler/metrics"
2016-03-02 08:29:17 +00:00
"k8s.io/kubernetes/pkg/runtime"
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
"k8s.io/kubernetes/pkg/watch"
2015-08-20 12:55:28 +00:00
)
2015-12-13 08:54:43 +00:00
const (
// Usage shoud exceed the tolerance before we start downscale or upscale the pods.
// TODO: make it a flag or HPA spec element.
tolerance = 0.1
2016-01-29 11:20:19 +00:00
2016-02-29 11:02:54 +00:00
defaultTargetCPUUtilizationPercentage = 80
2016-02-05 10:31:51 +00:00
HpaCustomMetricsTargetAnnotationName = "alpha/target.custom-metrics.podautoscaler.kubernetes.io"
HpaCustomMetricsStatusAnnotationName = "alpha/status.custom-metrics.podautoscaler.kubernetes.io"
2016-10-17 15:14:15 +00:00
scaleUpLimitFactor = 2
scaleUpLimitMinimum = 4
2015-12-13 08:54:43 +00:00
)
2016-10-17 15:14:15 +00:00
func calculateScaleUpLimit ( currentReplicas int32 ) int32 {
return int32 ( math . Max ( scaleUpLimitFactor * float64 ( currentReplicas ) , scaleUpLimitMinimum ) )
}
2015-09-10 13:10:07 +00:00
type HorizontalController struct {
2016-02-16 17:54:53 +00:00
scaleNamespacer unversionedextensions . ScalesGetter
2016-05-05 10:27:24 +00:00
hpaNamespacer unversionedautoscaling . HorizontalPodAutoscalersGetter
2015-11-02 15:18:53 +00:00
2015-12-13 08:54:43 +00:00
metricsClient metrics . MetricsClient
eventRecorder record . EventRecorder
2016-03-02 08:29:17 +00:00
// A store of HPA objects, populated by the controller.
store cache . Store
// Watches changes to all HPA objects.
2016-09-14 18:35:38 +00:00
controller * cache . Controller
2015-08-25 17:16:47 +00:00
}
2015-12-13 08:54:43 +00:00
var downscaleForbiddenWindow = 5 * time . Minute
var upscaleForbiddenWindow = 3 * time . Minute
2016-09-14 18:35:38 +00:00
func newInformer ( controller * HorizontalController , resyncPeriod time . Duration ) ( cache . Store , * cache . Controller ) {
return cache . NewInformer (
2016-03-02 08:29:17 +00:00
& cache . ListWatch {
ListFunc : func ( options api . ListOptions ) ( runtime . Object , error ) {
return controller . hpaNamespacer . HorizontalPodAutoscalers ( api . NamespaceAll ) . List ( options )
} ,
WatchFunc : func ( options api . ListOptions ) ( watch . Interface , error ) {
return controller . hpaNamespacer . HorizontalPodAutoscalers ( api . NamespaceAll ) . Watch ( options )
} ,
} ,
2016-05-05 10:27:24 +00:00
& autoscaling . HorizontalPodAutoscaler { } ,
2016-03-02 08:29:17 +00:00
resyncPeriod ,
2016-09-14 18:35:38 +00:00
cache . ResourceEventHandlerFuncs {
2016-03-02 08:29:17 +00:00
AddFunc : func ( obj interface { } ) {
2016-05-05 10:27:24 +00:00
hpa := obj . ( * autoscaling . HorizontalPodAutoscaler )
hasCPUPolicy := hpa . Spec . TargetCPUUtilizationPercentage != nil
2016-03-02 09:08:17 +00:00
_ , hasCustomMetricsPolicy := hpa . Annotations [ HpaCustomMetricsTargetAnnotationName ]
if ! hasCPUPolicy && ! hasCustomMetricsPolicy {
controller . eventRecorder . Event ( hpa , api . EventTypeNormal , "DefaultPolicy" , "No scaling policy specified - will use default one. See documentation for details" )
}
2016-03-02 08:29:17 +00:00
err := controller . reconcileAutoscaler ( hpa )
if err != nil {
glog . Warningf ( "Failed to reconcile %s: %v" , hpa . Name , err )
}
} ,
UpdateFunc : func ( old , cur interface { } ) {
2016-05-05 10:27:24 +00:00
hpa := cur . ( * autoscaling . HorizontalPodAutoscaler )
2016-03-02 08:29:17 +00:00
err := controller . reconcileAutoscaler ( hpa )
if err != nil {
glog . Warningf ( "Failed to reconcile %s: %v" , hpa . Name , err )
}
} ,
// We are not interested in deletions.
} ,
)
2016-03-03 10:48:07 +00:00
}
2016-05-05 10:27:24 +00:00
func NewHorizontalController ( evtNamespacer unversionedcore . EventsGetter , scaleNamespacer unversionedextensions . ScalesGetter , hpaNamespacer unversionedautoscaling . HorizontalPodAutoscalersGetter , metricsClient metrics . MetricsClient , resyncPeriod time . Duration ) * HorizontalController {
2016-03-03 10:48:07 +00:00
broadcaster := record . NewBroadcaster ( )
2016-03-23 23:45:24 +00:00
broadcaster . StartRecordingToSink ( & unversionedcore . EventSinkImpl { Interface : evtNamespacer . Events ( "" ) } )
2016-03-03 10:48:07 +00:00
recorder := broadcaster . NewRecorder ( api . EventSource { Component : "horizontal-pod-autoscaler" } )
controller := & HorizontalController {
metricsClient : metricsClient ,
eventRecorder : recorder ,
scaleNamespacer : scaleNamespacer ,
hpaNamespacer : hpaNamespacer ,
}
store , frameworkController := newInformer ( controller , resyncPeriod )
controller . store = store
controller . controller = frameworkController
2016-03-02 08:29:17 +00:00
return controller
2015-08-17 12:18:26 +00:00
}
2016-03-02 08:29:17 +00:00
func ( a * HorizontalController ) Run ( stopCh <- chan struct { } ) {
defer utilruntime . HandleCrash ( )
glog . Infof ( "Starting HPA Controller" )
go a . controller . Run ( stopCh )
<- stopCh
glog . Infof ( "Shutting down HPA Controller" )
2015-08-17 12:18:26 +00:00
}
2016-05-05 10:27:24 +00:00
func ( a * HorizontalController ) computeReplicasForCPUUtilization ( hpa * autoscaling . HorizontalPodAutoscaler , scale * extensions . Scale ) ( int32 , * int32 , time . Time , error ) {
2016-04-27 04:35:14 +00:00
targetUtilization := int32 ( defaultTargetCPUUtilizationPercentage )
2016-05-05 10:27:24 +00:00
if hpa . Spec . TargetCPUUtilizationPercentage != nil {
targetUtilization = * hpa . Spec . TargetCPUUtilizationPercentage
2015-10-13 15:24:23 +00:00
}
currentReplicas := scale . Status . Replicas
2016-03-09 00:27:13 +00:00
if scale . Status . Selector == nil {
errMsg := "selector is required"
a . eventRecorder . Event ( hpa , api . EventTypeWarning , "SelectorRequired" , errMsg )
return 0 , nil , time . Time { } , fmt . Errorf ( errMsg )
}
selector , err := unversioned . LabelSelectorAsSelector ( scale . Status . Selector )
if err != nil {
errMsg := fmt . Sprintf ( "couldn't convert selector string to a corresponding selector object: %v" , err )
a . eventRecorder . Event ( hpa , api . EventTypeWarning , "InvalidSelector" , errMsg )
return 0 , nil , time . Time { } , fmt . Errorf ( errMsg )
}
2016-10-17 15:14:15 +00:00
currentUtilization , numRunningPods , timestamp , err := a . metricsClient . GetCPUUtilization ( hpa . Namespace , selector )
2015-10-13 15:24:23 +00:00
// TODO: what to do on partial errors (like metrics obtained for 75% of pods).
if err != nil {
2016-03-02 08:29:17 +00:00
a . eventRecorder . Event ( hpa , api . EventTypeWarning , "FailedGetMetrics" , err . Error ( ) )
2016-03-02 09:08:17 +00:00
return 0 , nil , time . Time { } , fmt . Errorf ( "failed to get CPU utilization: %v" , err )
2015-10-13 15:24:23 +00:00
}
2016-04-27 04:35:14 +00:00
utilization := int32 ( * currentUtilization )
usageRatio := float64 ( utilization ) / float64 ( targetUtilization )
2016-10-17 15:14:15 +00:00
if math . Abs ( 1.0 - usageRatio ) <= tolerance {
return currentReplicas , & utilization , timestamp , nil
2015-10-13 15:24:23 +00:00
}
2016-07-11 06:42:51 +00:00
2016-10-17 15:14:15 +00:00
desiredReplicas := math . Ceil ( usageRatio * float64 ( numRunningPods ) )
a . eventRecorder . Eventf ( hpa , api . EventTypeNormal , "DesiredReplicasComputed" ,
"Computed the desired num of replicas: %d, on a base of %d report(s) (avgCPUutil: %d, current replicas: %d)" ,
int32 ( desiredReplicas ) , numRunningPods , utilization , scale . Status . Replicas )
return int32 ( desiredReplicas ) , & utilization , timestamp , nil
2015-10-13 15:24:23 +00:00
}
2016-01-29 11:20:19 +00:00
// Computes the desired number of replicas based on the CustomMetrics passed in cmAnnotation as json-serialized
// extensions.CustomMetricsTargetList.
2016-03-02 09:08:17 +00:00
// Returns number of replicas, metric which required highest number of replicas,
// status string (also json-serialized extensions.CustomMetricsCurrentStatusList),
2016-01-29 11:20:19 +00:00
// last timestamp of the metrics involved in computations or error, if occurred.
2016-05-05 10:27:24 +00:00
func ( a * HorizontalController ) computeReplicasForCustomMetrics ( hpa * autoscaling . HorizontalPodAutoscaler , scale * extensions . Scale ,
2016-04-27 04:35:14 +00:00
cmAnnotation string ) ( replicas int32 , metric string , status string , timestamp time . Time , err error ) {
2016-01-29 11:20:19 +00:00
if cmAnnotation == "" {
2016-03-02 09:08:17 +00:00
return
2016-01-29 11:20:19 +00:00
}
2016-07-11 06:42:51 +00:00
currentReplicas := scale . Status . Replicas
2016-01-29 11:20:19 +00:00
var targetList extensions . CustomMetricTargetList
if err := json . Unmarshal ( [ ] byte ( cmAnnotation ) , & targetList ) ; err != nil {
2016-03-02 09:08:17 +00:00
return 0 , "" , "" , time . Time { } , fmt . Errorf ( "failed to parse custom metrics annotation: %v" , err )
2016-01-29 11:20:19 +00:00
}
if len ( targetList . Items ) == 0 {
2016-03-02 09:08:17 +00:00
return 0 , "" , "" , time . Time { } , fmt . Errorf ( "no custom metrics in annotation" )
2016-01-29 11:20:19 +00:00
}
statusList := extensions . CustomMetricCurrentStatusList {
Items : make ( [ ] extensions . CustomMetricCurrentStatus , 0 ) ,
}
for _ , customMetricTarget := range targetList . Items {
2016-03-09 00:27:13 +00:00
if scale . Status . Selector == nil {
errMsg := "selector is required"
a . eventRecorder . Event ( hpa , api . EventTypeWarning , "SelectorRequired" , errMsg )
return 0 , "" , "" , time . Time { } , fmt . Errorf ( "selector is required" )
}
selector , err := unversioned . LabelSelectorAsSelector ( scale . Status . Selector )
if err != nil {
errMsg := fmt . Sprintf ( "couldn't convert selector string to a corresponding selector object: %v" , err )
a . eventRecorder . Event ( hpa , api . EventTypeWarning , "InvalidSelector" , errMsg )
return 0 , "" , "" , time . Time { } , fmt . Errorf ( "couldn't convert selector string to a corresponding selector object: %v" , err )
}
value , currentTimestamp , err := a . metricsClient . GetCustomMetric ( customMetricTarget . Name , hpa . Namespace , selector )
2016-01-29 11:20:19 +00:00
// TODO: what to do on partial errors (like metrics obtained for 75% of pods).
if err != nil {
2016-03-02 08:29:17 +00:00
a . eventRecorder . Event ( hpa , api . EventTypeWarning , "FailedGetCustomMetrics" , err . Error ( ) )
2016-03-02 09:08:17 +00:00
return 0 , "" , "" , time . Time { } , fmt . Errorf ( "failed to get custom metric value: %v" , err )
2016-01-29 11:20:19 +00:00
}
floatTarget := float64 ( customMetricTarget . TargetValue . MilliValue ( ) ) / 1000.0
usageRatio := * value / floatTarget
2016-04-27 04:35:14 +00:00
replicaCountProposal := int32 ( 0 )
2016-01-29 11:20:19 +00:00
if math . Abs ( 1.0 - usageRatio ) > tolerance {
2016-04-27 04:35:14 +00:00
replicaCountProposal = int32 ( math . Ceil ( usageRatio * float64 ( currentReplicas ) ) )
2016-01-29 11:20:19 +00:00
} else {
replicaCountProposal = currentReplicas
}
if replicaCountProposal > replicas {
timestamp = currentTimestamp
replicas = replicaCountProposal
2016-03-02 09:08:17 +00:00
metric = fmt . Sprintf ( "Custom metric %s" , customMetricTarget . Name )
2016-01-29 11:20:19 +00:00
}
quantity , err := resource . ParseQuantity ( fmt . Sprintf ( "%.3f" , * value ) )
if err != nil {
2016-03-02 09:08:17 +00:00
return 0 , "" , "" , time . Time { } , fmt . Errorf ( "failed to set custom metric value: %v" , err )
2016-01-29 11:20:19 +00:00
}
statusList . Items = append ( statusList . Items , extensions . CustomMetricCurrentStatus {
Name : customMetricTarget . Name ,
2016-05-17 04:36:56 +00:00
CurrentValue : quantity ,
2016-01-29 11:20:19 +00:00
} )
}
byteStatusList , err := json . Marshal ( statusList )
if err != nil {
2016-03-02 09:08:17 +00:00
return 0 , "" , "" , time . Time { } , fmt . Errorf ( "failed to serialize custom metric status: %v" , err )
2016-01-29 11:20:19 +00:00
}
2016-03-02 09:08:17 +00:00
return replicas , metric , string ( byteStatusList ) , timestamp , nil
2016-01-29 11:20:19 +00:00
}
2016-05-05 10:27:24 +00:00
func ( a * HorizontalController ) reconcileAutoscaler ( hpa * autoscaling . HorizontalPodAutoscaler ) error {
reference := fmt . Sprintf ( "%s/%s/%s" , hpa . Spec . ScaleTargetRef . Kind , hpa . Namespace , hpa . Spec . ScaleTargetRef . Name )
2015-09-14 13:08:43 +00:00
2016-05-05 10:27:24 +00:00
scale , err := a . scaleNamespacer . Scales ( hpa . Namespace ) . Get ( hpa . Spec . ScaleTargetRef . Kind , hpa . Spec . ScaleTargetRef . Name )
2015-08-17 12:18:26 +00:00
if err != nil {
2016-03-02 08:29:17 +00:00
a . eventRecorder . Event ( hpa , api . EventTypeWarning , "FailedGetScale" , err . Error ( ) )
2015-09-14 13:08:43 +00:00
return fmt . Errorf ( "failed to query scale subresource for %s: %v" , reference , err )
2015-08-17 12:18:26 +00:00
}
2015-09-14 13:08:43 +00:00
currentReplicas := scale . Status . Replicas
2015-08-20 12:55:28 +00:00
2016-04-27 04:35:14 +00:00
cpuDesiredReplicas := int32 ( 0 )
2016-07-11 06:42:51 +00:00
cpuCurrentUtilization := new ( int32 )
2016-01-29 11:20:19 +00:00
cpuTimestamp := time . Time { }
2016-04-27 04:35:14 +00:00
cmDesiredReplicas := int32 ( 0 )
2016-03-02 09:08:17 +00:00
cmMetric := ""
2016-01-29 11:20:19 +00:00
cmStatus := ""
cmTimestamp := time . Time { }
2016-04-27 04:35:14 +00:00
desiredReplicas := int32 ( 0 )
2016-03-02 09:08:17 +00:00
rescaleReason := ""
2016-02-12 15:26:59 +00:00
timestamp := time . Now ( )
2016-01-29 11:20:19 +00:00
2016-07-19 15:54:38 +00:00
if scale . Spec . Replicas == 0 {
// Autoscaling is disabled for this resource
desiredReplicas = 0
} else if currentReplicas > hpa . Spec . MaxReplicas {
2016-03-02 09:08:17 +00:00
rescaleReason = "Current number of replicas above Spec.MaxReplicas"
2016-02-12 15:26:59 +00:00
desiredReplicas = hpa . Spec . MaxReplicas
} else if hpa . Spec . MinReplicas != nil && currentReplicas < * hpa . Spec . MinReplicas {
2016-03-02 09:08:17 +00:00
rescaleReason = "Current number of replicas below Spec.MinReplicas"
2016-02-12 15:26:59 +00:00
desiredReplicas = * hpa . Spec . MinReplicas
} else if currentReplicas == 0 {
2016-03-02 09:08:17 +00:00
rescaleReason = "Current number of replicas must be greater than 0"
2016-02-12 15:26:59 +00:00
desiredReplicas = 1
} else {
// All basic scenarios covered, the state should be sane, lets use metrics.
2016-02-29 11:02:54 +00:00
cmAnnotation , cmAnnotationFound := hpa . Annotations [ HpaCustomMetricsTargetAnnotationName ]
2016-02-12 15:26:59 +00:00
2016-05-05 10:27:24 +00:00
if hpa . Spec . TargetCPUUtilizationPercentage != nil || ! cmAnnotationFound {
2016-02-12 15:26:59 +00:00
cpuDesiredReplicas , cpuCurrentUtilization , cpuTimestamp , err = a . computeReplicasForCPUUtilization ( hpa , scale )
if err != nil {
2016-02-23 12:05:07 +00:00
a . updateCurrentReplicasInStatus ( hpa , currentReplicas )
2016-03-02 08:29:17 +00:00
a . eventRecorder . Event ( hpa , api . EventTypeWarning , "FailedComputeReplicas" , err . Error ( ) )
2016-02-12 15:26:59 +00:00
return fmt . Errorf ( "failed to compute desired number of replicas based on CPU utilization for %s: %v" , reference , err )
}
2016-01-29 11:20:19 +00:00
}
2016-02-29 11:02:54 +00:00
if cmAnnotationFound {
2016-03-02 09:08:17 +00:00
cmDesiredReplicas , cmMetric , cmStatus , cmTimestamp , err = a . computeReplicasForCustomMetrics ( hpa , scale , cmAnnotation )
2016-02-12 15:26:59 +00:00
if err != nil {
2016-02-23 12:05:07 +00:00
a . updateCurrentReplicasInStatus ( hpa , currentReplicas )
2016-03-02 08:29:17 +00:00
a . eventRecorder . Event ( hpa , api . EventTypeWarning , "FailedComputeCMReplicas" , err . Error ( ) )
2016-02-12 15:26:59 +00:00
return fmt . Errorf ( "failed to compute desired number of replicas based on Custom Metrics for %s: %v" , reference , err )
}
}
2016-01-29 11:20:19 +00:00
2016-03-02 09:08:17 +00:00
rescaleMetric := ""
2016-02-12 15:26:59 +00:00
if cpuDesiredReplicas > desiredReplicas {
desiredReplicas = cpuDesiredReplicas
timestamp = cpuTimestamp
2016-03-02 09:08:17 +00:00
rescaleMetric = "CPU utilization"
2016-02-12 15:26:59 +00:00
}
if cmDesiredReplicas > desiredReplicas {
desiredReplicas = cmDesiredReplicas
timestamp = cmTimestamp
2016-03-02 09:08:17 +00:00
rescaleMetric = cmMetric
}
if desiredReplicas > currentReplicas {
rescaleReason = fmt . Sprintf ( "%s above target" , rescaleMetric )
2016-07-11 06:42:51 +00:00
}
if desiredReplicas < currentReplicas {
2016-03-02 09:08:17 +00:00
rescaleReason = "All metrics below target"
2016-02-12 15:26:59 +00:00
}
2015-08-20 12:55:28 +00:00
2016-02-12 15:26:59 +00:00
if hpa . Spec . MinReplicas != nil && desiredReplicas < * hpa . Spec . MinReplicas {
desiredReplicas = * hpa . Spec . MinReplicas
}
2015-08-25 17:16:47 +00:00
2016-07-19 15:54:38 +00:00
// never scale down to 0, reserved for disabling autoscaling
2016-02-12 15:26:59 +00:00
if desiredReplicas == 0 {
desiredReplicas = 1
}
2015-09-07 10:25:04 +00:00
2016-02-12 15:26:59 +00:00
if desiredReplicas > hpa . Spec . MaxReplicas {
desiredReplicas = hpa . Spec . MaxReplicas
}
2016-10-17 15:14:15 +00:00
// Do not upscale too much to prevent incorrect rapid increase of the number of master replicas caused by
// bogus CPU usage report from heapster/kubelet (like in issue #32304).
if desiredReplicas > calculateScaleUpLimit ( currentReplicas ) {
desiredReplicas = calculateScaleUpLimit ( currentReplicas )
}
2015-09-14 13:08:43 +00:00
}
2016-02-23 10:29:40 +00:00
rescale := shouldScale ( hpa , currentReplicas , desiredReplicas , timestamp )
if rescale {
scale . Spec . Replicas = desiredReplicas
2016-05-05 10:27:24 +00:00
_ , err = a . scaleNamespacer . Scales ( hpa . Namespace ) . Update ( hpa . Spec . ScaleTargetRef . Kind , scale )
2016-02-23 10:29:40 +00:00
if err != nil {
2016-03-02 09:08:17 +00:00
a . eventRecorder . Eventf ( hpa , api . EventTypeWarning , "FailedRescale" , "New size: %d; reason: %s; error: %v" , desiredReplicas , rescaleReason , err . Error ( ) )
2016-02-23 10:29:40 +00:00
return fmt . Errorf ( "failed to rescale %s: %v" , reference , err )
}
2016-03-02 09:08:17 +00:00
a . eventRecorder . Eventf ( hpa , api . EventTypeNormal , "SuccessfulRescale" , "New size: %d; reason: %s" , desiredReplicas , rescaleReason )
glog . Infof ( "Successfull rescale of %s, old size: %d, new size: %d, reason: %s" ,
hpa . Name , currentReplicas , desiredReplicas , rescaleReason )
2016-02-23 10:29:40 +00:00
} else {
desiredReplicas = currentReplicas
}
return a . updateStatus ( hpa , currentReplicas , desiredReplicas , cpuCurrentUtilization , cmStatus , rescale )
}
2016-05-05 10:27:24 +00:00
func shouldScale ( hpa * autoscaling . HorizontalPodAutoscaler , currentReplicas , desiredReplicas int32 , timestamp time . Time ) bool {
2016-07-11 06:42:51 +00:00
if desiredReplicas == currentReplicas {
return false
}
2015-09-07 10:25:04 +00:00
2016-08-16 11:02:08 +00:00
if hpa . Status . LastScaleTime == nil {
return true
}
2016-07-11 06:42:51 +00:00
// Going down only if the usageRatio dropped significantly below the target
// and there was no rescaling in the last downscaleForbiddenWindow.
2016-08-16 11:02:08 +00:00
if desiredReplicas < currentReplicas && hpa . Status . LastScaleTime . Add ( downscaleForbiddenWindow ) . Before ( timestamp ) {
2016-07-11 06:42:51 +00:00
return true
}
// Going up only if the usage ratio increased significantly above the target
// and there was no rescaling in the last upscaleForbiddenWindow.
2016-08-16 11:02:08 +00:00
if desiredReplicas > currentReplicas && hpa . Status . LastScaleTime . Add ( upscaleForbiddenWindow ) . Before ( timestamp ) {
2016-07-11 06:42:51 +00:00
return true
2015-09-14 13:08:43 +00:00
}
2016-02-23 10:29:40 +00:00
return false
}
2015-08-25 17:16:47 +00:00
2016-05-05 10:27:24 +00:00
func ( a * HorizontalController ) updateCurrentReplicasInStatus ( hpa * autoscaling . HorizontalPodAutoscaler , currentReplicas int32 ) {
2016-02-23 12:05:07 +00:00
err := a . updateStatus ( hpa , currentReplicas , hpa . Status . DesiredReplicas , hpa . Status . CurrentCPUUtilizationPercentage , hpa . Annotations [ HpaCustomMetricsStatusAnnotationName ] , false )
if err != nil {
glog . Errorf ( "%v" , err )
}
}
2016-05-05 10:27:24 +00:00
func ( a * HorizontalController ) updateStatus ( hpa * autoscaling . HorizontalPodAutoscaler , currentReplicas , desiredReplicas int32 , cpuCurrentUtilization * int32 , cmStatus string , rescale bool ) error {
hpa . Status = autoscaling . HorizontalPodAutoscalerStatus {
2015-10-13 15:24:23 +00:00
CurrentReplicas : currentReplicas ,
DesiredReplicas : desiredReplicas ,
2016-01-29 11:20:19 +00:00
CurrentCPUUtilizationPercentage : cpuCurrentUtilization ,
2015-10-26 15:36:05 +00:00
LastScaleTime : hpa . Status . LastScaleTime ,
2015-09-14 13:08:43 +00:00
}
2016-01-29 11:20:19 +00:00
if cmStatus != "" {
hpa . Annotations [ HpaCustomMetricsStatusAnnotationName ] = cmStatus
}
2015-09-14 13:08:43 +00:00
if rescale {
2015-12-02 08:24:17 +00:00
now := unversioned . NewTime ( time . Now ( ) )
2015-10-13 15:24:23 +00:00
hpa . Status . LastScaleTime = & now
2015-09-14 13:08:43 +00:00
}
2016-03-02 08:29:17 +00:00
_ , err := a . hpaNamespacer . HorizontalPodAutoscalers ( hpa . Namespace ) . UpdateStatus ( hpa )
2015-09-14 13:08:43 +00:00
if err != nil {
2016-03-02 08:29:17 +00:00
a . eventRecorder . Event ( hpa , api . EventTypeWarning , "FailedUpdateStatus" , err . Error ( ) )
2015-09-14 13:08:43 +00:00
return fmt . Errorf ( "failed to update status for %s: %v" , hpa . Name , err )
}
2016-03-02 08:29:17 +00:00
glog . V ( 2 ) . Infof ( "Successfully updated status for %s" , hpa . Name )
2015-08-17 12:18:26 +00:00
return nil
}