Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --If you have 2 or more metrics defined on an HPA and one metric is suggesting to scale down and the other metric is unavailable, the HPA will scale down despite missing metrics. Example: Imagine the following Horizontal Pod Autoscaler resource: And the deployment is currently scaled to replicas. If the metric is suggesting to scale to 1 AND the metric is unavailable e.g. HPA can't fetch the metrics. Then it will happily scale down to despite not knowing what the currently unavailable metric would suggest. It's clearly stated in the docs the scale down should not happen if one or more metrics are unavailable: Until Kubernetes v1.15.x the behavior was as expected. However with the . The logic was removing the special handling of stopping scale down in case of unavailable metrics. Kubernetes version (use ): Cloud provider or hardware configuration: OS (e.g: ): Kernel (e.g. ): Install tools: Network plugin and version (if this is a network-related bug): Others: /bug /sig autoscaling
/triage accepted

Discussion:
} } <del> // If all metrics are invalid return error and set condition on hpa based on first invalid metric. if invalidMetricsCount >= len(metricSpecs) { </del> <ins> // If all metrics are invalid or some are invalid and we would scale down, // return an error and set the condition of the hpa based on the first invalid metric. // Otherwise set the condition as scaling active as we're going to scale if invalidMetricsCount >= len(metricSpecs) || (invalidMetricsCount > 0 && replicas < specReplicas) { </ins> setCondition(hpa, invalidMetricCondition.Type, invalidMetricCondition.Status, invalidMetricCondition.Reason, invalidMetricCondition.Message) return 0, "", statuses, time.Time{}, fmt.Errorf("invalid metrics (%v invalid out of %v), first error is: %v", invalidMetricsCount, len(metricSpecs), invalidMetricError) }

tc.runTest(t) } <del> func TestScaleDownOneMetricInvalid(t *testing.T) { tc := testCase{ minReplicas:             2, maxReplicas:             6, specReplicas:            5, statusReplicas:          5, expectedDesiredReplicas: 3, CPUTarget:               50, metricsTarget: []autoscalingv2.MetricSpec{ { Type: "CheddarCheese", }, }, reportedLevels:      []uint64{100, 300, 500, 250, 250}, reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, useMetricsAPI:       true, recommendations:     []timestampedRecommendation{}, } tc.runTest(t) } func TestScaleDownOneMetricEmpty(t *testing.T) { tc := testCase{ minReplicas:             2, maxReplicas:             6, specReplicas:            5, statusReplicas:          5, expectedDesiredReplicas: 3, CPUTarget:               50, metricsTarget: []autoscalingv2.MetricSpec{ { Type: autoscalingv2.ExternalMetricSourceType, External: &autoscalingv2.ExternalMetricSource{ Metric: autoscalingv2.MetricIdentifier{ Name:     "qps", Selector: &metav1.LabelSelector{}, }, Target: autoscalingv2.MetricTarget{ Type:         autoscalingv2.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(1000, resource.DecimalSI), }, }, }, }, reportedLevels:      []uint64{100, 300, 500, 250, 250}, reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, useMetricsAPI:       true, recommendations:     []timestampedRecommendation{}, } _, _, _, testEMClient, _ := tc.prepareTestClient(t) testEMClient.PrependReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) { return true, &emapi.ExternalMetricValueList{}, fmt.Errorf("something went wrong") }) tc.testEMClient = testEMClient tc.runTest(t) } </del> func TestScaleDownIgnoreHotCpuPods(t *testing.T) { tc := testCase{ minReplicas:             2,

reportedLevels:      []uint64{100, 300, 500, 250, 250}, reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, useMetricsAPI:       true, <ins> recommendations:     []timestampedRecommendation{}, </ins> expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{ <del> {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "ScaleDownStabilized"}, {Type: autoscalingv1.ScalingActive, Status: v1.ConditionTrue, Reason: "ValidMetricFound"}, {Type: autoscalingv1.ScalingLimited, Status: v1.ConditionFalse, Reason: "DesiredWithinRange"}, </del> <ins> {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"}, {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: "InvalidMetricSourceType"}, </ins> }, }

reportedLevels:      []uint64{100, 300, 500, 250, 250}, reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, useMetricsAPI:       true, <ins> recommendations:     []timestampedRecommendation{}, </ins> expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{ <del> {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "ScaleDownStabilized"}, {Type: autoscalingv1.ScalingActive, Status: v1.ConditionTrue, Reason: "ValidMetricFound"}, {Type: autoscalingv1.ScalingLimited, Status: v1.ConditionFalse, Reason: "DesiredWithinRange"}, </del> <ins> {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"}, {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: "FailedGetExternalMetric"}, </ins> }, } _, _, _, testEMClient, _ := tc.prepareTestClient(t)