diff --git a/pkg/apis/autoscaling/v1alpha1/pa_lifecycle.go b/pkg/apis/autoscaling/v1alpha1/pa_lifecycle.go index 6606ac54058d..3ce6d98ad75e 100644 --- a/pkg/apis/autoscaling/v1alpha1/pa_lifecycle.go +++ b/pkg/apis/autoscaling/v1alpha1/pa_lifecycle.go @@ -191,6 +191,12 @@ func (pas *PodAutoscalerStatus) CanMarkInactive(idlePeriod time.Duration) bool { return pas.inStatusFor(corev1.ConditionTrue, idlePeriod) } +// IsActivatingTimeout checks whether the pod autoscaler has been in activating +// for at least the specified timeout period. +func (pas *PodAutoscalerStatus) IsActivatingTimeout(activatingTimeout time.Duration) bool { + return pas.inStatusFor(corev1.ConditionUnknown, activatingTimeout) +} + // inStatusFor returns true if the PodAutoscalerStatus's Active condition has stayed in // the specified status for at least the specified duration. Otherwise it returns false, // including when the status is undetermined (Active condition is not found.) diff --git a/pkg/autoscaler/config.go b/pkg/autoscaler/config.go index 88e68a2725f7..6e23c291b910 100644 --- a/pkg/autoscaler/config.go +++ b/pkg/autoscaler/config.go @@ -51,6 +51,7 @@ type Config struct { TickInterval time.Duration ScaleToZeroGracePeriod time.Duration + ActivatingTimeout time.Duration } // TargetConcurrency calculates the target concurrency for a given container-concurrency @@ -142,6 +143,10 @@ func NewConfigFromMap(data map[string]string) (*Config, error) { key: "tick-interval", field: &lc.TickInterval, defaultValue: 2 * time.Second, + }, { + key: "activating-timeout", + field: &lc.ActivatingTimeout, + defaultValue: 2 * time.Minute, }} { if raw, ok := data[dur.key]; !ok { *dur.field = dur.defaultValue diff --git a/pkg/autoscaler/config_test.go b/pkg/autoscaler/config_test.go index b6765df2892c..98df9b2300da 100644 --- a/pkg/autoscaler/config_test.go +++ b/pkg/autoscaler/config_test.go @@ -79,6 +79,7 @@ func TestNewConfig(t *testing.T) { "tick-interval": "2s", "panic-window-percentage": "10", "panic-threshold-percentage": "200", + "activating-timeout": "2m", }, want: &Config{ EnableScaleToZero: true, @@ -91,6 +92,7 @@ func TestNewConfig(t *testing.T) { TickInterval: 2 * time.Second, PanicWindowPercentage: 10.0, PanicThresholdPercentage: 200.0, + ActivatingTimeout: 2 * time.Minute, }, }, { name: "with toggles on", @@ -104,6 +106,7 @@ func TestNewConfig(t *testing.T) { "tick-interval": "2s", "panic-window-percentage": "10", "panic-threshold-percentage": "200", + "activating-timeout": "2m", }, want: &Config{ EnableScaleToZero: true, @@ -116,6 +119,7 @@ func TestNewConfig(t *testing.T) { TickInterval: 2 * time.Second, PanicWindowPercentage: 10.0, PanicThresholdPercentage: 200.0, + ActivatingTimeout: 2 * time.Minute, }, }, { name: "with toggles on strange casing", @@ -129,6 +133,7 @@ func TestNewConfig(t *testing.T) { "tick-interval": "2s", "panic-window-percentage": "10", "panic-threshold-percentage": "200", + "activating-timeout": "2m", }, want: &Config{ EnableScaleToZero: true, @@ -141,6 +146,7 @@ func TestNewConfig(t *testing.T) { TickInterval: 2 * time.Second, PanicWindowPercentage: 10.0, PanicThresholdPercentage: 200.0, + ActivatingTimeout: 2 * time.Minute, }, }, { name: "with toggles explicitly off", @@ -154,6 +160,7 @@ func TestNewConfig(t *testing.T) { "tick-interval": "2s", "panic-window-percentage": "10", "panic-threshold-percentage": "200", + "activating-timeout": "2m", }, want: &Config{ ContainerConcurrencyTargetPercentage: 0.5, @@ -165,6 +172,7 @@ func TestNewConfig(t *testing.T) { TickInterval: 2 * time.Second, PanicWindowPercentage: 10.0, PanicThresholdPercentage: 200.0, + ActivatingTimeout: 2 * time.Minute, }, }, { name: "with explicit grace period", @@ -179,6 +187,7 @@ func TestNewConfig(t *testing.T) { "tick-interval": "2s", "panic-window-percentage": "10", "panic-threshold-percentage": "200", + "activating-timeout": "2m", }, want: &Config{ ContainerConcurrencyTargetPercentage: 0.5, @@ -190,6 +199,7 @@ func TestNewConfig(t *testing.T) { TickInterval: 2 * time.Second, PanicWindowPercentage: 10.0, PanicThresholdPercentage: 200.0, + ActivatingTimeout: 2 * time.Minute, }, }, { name: "malformed float", @@ -202,6 +212,7 @@ func TestNewConfig(t *testing.T) { "tick-interval": "2s", "panic-window-percentage": "10", "panic-threshold-percentage": "200", + "activating-timeout": "2m", }, wantErr: true, }, { @@ -215,6 +226,7 @@ func TestNewConfig(t *testing.T) { "tick-interval": "2s", "panic-window-percentage": "10", "panic-threshold-percentage": "200", + "activating-timeout": "2m", }, wantErr: true, }} diff --git a/pkg/reconciler/autoscaling/kpa/kpa.go b/pkg/reconciler/autoscaling/kpa/kpa.go index 2a04e5675670..47a97127682b 100644 --- a/pkg/reconciler/autoscaling/kpa/kpa.go +++ b/pkg/reconciler/autoscaling/kpa/kpa.go @@ -214,6 +214,12 @@ func (c *Reconciler) reconcile(ctx context.Context, pa *pav1alpha1.PodAutoscaler return perrors.Wrap(err, "error reporting metrics") } + if pa.Status.IsActivating() { + timeout := config.FromContext(ctx).Autoscaler.ActivatingTimeout + logger.Infof("enqueue delay for check activating timeout after %v.", timeout) + c.scaler.enqueueCB(pa, timeout) + } + // computeActiveCondition decides if we need to change the SKS mode, // and returns true if the status has changed. if changed := computeActiveCondition(pa, want, got); changed { diff --git a/pkg/reconciler/autoscaling/kpa/scaler.go b/pkg/reconciler/autoscaling/kpa/scaler.go index 6c35ba2e0f41..c00c3848ddf9 100644 --- a/pkg/reconciler/autoscaling/kpa/scaler.go +++ b/pkg/reconciler/autoscaling/kpa/scaler.go @@ -253,13 +253,19 @@ func (ks *scaler) applyScale(ctx context.Context, pa *pav1alpha1.PodAutoscaler, // Scale attempts to scale the given PA's target reference to the desired scale. func (ks *scaler) Scale(ctx context.Context, pa *pav1alpha1.PodAutoscaler, desiredScale int32) (int32, error) { logger := logging.FromContext(ctx) + autoscalerConfig := config.FromContext(ctx).Autoscaler if desiredScale < 0 { - logger.Debug("Metrics are not yet being collected.") - return desiredScale, nil + // check is activating timeout + if pa.Status.IsActivatingTimeout(autoscalerConfig.ActivatingTimeout) { + logger.Infof("Activating pa timeout after %v", autoscalerConfig.ActivatingTimeout) + } else { + logger.Debug("Metrics are not yet being collected.") + return desiredScale, nil + } } - desiredScale, shouldApplyScale := ks.handleScaleToZero(pa, desiredScale, config.FromContext(ctx).Autoscaler) + desiredScale, shouldApplyScale := ks.handleScaleToZero(pa, desiredScale, autoscalerConfig) if !shouldApplyScale { return desiredScale, nil }