hpa: remove v2beta2 support

autoscaling/v2beta2 has been deprecated since [kubernetes 1.26](https://kubernetes.io/docs/reference/using-api/deprecation-guide/#horizontalpodautoscaler-v126) and been [removed for kubernetes 1.36](https://github.com/kubernetes/kubernetes/pull/135615)
This commit removes the hpa fallback to v2beta2 if v2 did not work

Signed-off-by: Niklas Ott <niklas.ott@unwired.at>
This commit is contained in:
Niklas Ott
2026-05-21 11:13:46 +02:00
parent f5cccbf197
commit af2436ef1d
9 changed files with 14 additions and 221 deletions
+1 -1
View File
@@ -128,7 +128,7 @@ in the primary autoscaler when a rollout for the deployment starts and completes
Optionally, you can create two HPAs, one for canary and one for the primary to update the HPA without
doing a new rollout. As the canary deployment will be scaled to 0, the HPA on the canary will be inactive.
**Note** Flagger requires `autoscaling/v2` or `autoscaling/v2beta2` API version for HPAs.
**Note** Flagger requires `autoscaling/v2` API version for HPAs.
The progress deadline represents the maximum time in seconds for the canary deployment to
make progress before it is rolled back, defaults to ten minutes.
+3 -3
View File
@@ -17,7 +17,7 @@ limitations under the License.
package v1alpha1
import (
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
autoscalingv2 "k8s.io/api/autoscaling/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -106,7 +106,7 @@ type HorizontalPodAutoscalerConfig struct {
// +optional
Name string `json:"name,omitempty"`
// +optional
Behavior *autoscalingv2beta2.HorizontalPodAutoscalerBehavior `json:"behavior,omitempty"`
Behavior *autoscalingv2.HorizontalPodAutoscalerBehavior `json:"behavior,omitempty"`
}
// ScaleTarget holds the a reference to the scale target Object
@@ -129,7 +129,7 @@ type ScaleTriggers struct {
// +optional
AuthenticationRef *ScaledObjectAuthRef `json:"authenticationRef,omitempty"`
// +optional
MetricType autoscalingv2beta2.MetricTargetType `json:"metricType,omitempty"`
MetricType autoscalingv2.MetricTargetType `json:"metricType,omitempty"`
}
// +k8s:openapi-gen=true
@@ -22,7 +22,7 @@ limitations under the License.
package v1alpha1
import (
v2beta2 "k8s.io/api/autoscaling/v2beta2"
v2 "k8s.io/api/autoscaling/v2"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@@ -141,7 +141,7 @@ func (in *HorizontalPodAutoscalerConfig) DeepCopyInto(out *HorizontalPodAutoscal
*out = *in
if in.Behavior != nil {
in, out := &in.Behavior, &out.Behavior
*out = new(v2beta2.HorizontalPodAutoscalerBehavior)
*out = new(v2.HorizontalPodAutoscalerBehavior)
(*in).DeepCopyInto(*out)
}
return
-8
View File
@@ -191,14 +191,6 @@ func (c *DeploymentController) ScaleFromZero(cd *flaggerv1.Canary) error {
if hpa.Spec.MinReplicas != nil && *hpa.Spec.MinReplicas > 1 {
replicas = hpa.Spec.MinReplicas
}
} else {
// fallback to v2beta2
hpa, err := c.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers(cd.Namespace).Get(context.TODO(), cd.Spec.AutoscalerRef.Name, metav1.GetOptions{})
if err == nil {
if hpa.Spec.MinReplicas != nil && *hpa.Spec.MinReplicas > 1 {
replicas = hpa.Spec.MinReplicas
}
}
}
} else if cd.Spec.AutoscalerRef.Kind == "ScaledObject" {
so, err := c.flaggerClient.KedaV1alpha1().ScaledObjects(cd.Namespace).Get(context.TODO(), cd.Spec.AutoscalerRef.Name, metav1.GetOptions{})
+1 -1
View File
@@ -398,7 +398,7 @@ func newDeploymentControllerTestCanary(cc canaryConfigs) *flaggerv1.Canary {
},
AutoscalerRef: &flaggerv1.AutoscalerReference{
Name: "podinfo",
APIVersion: "autoscaling/v2beta2",
APIVersion: "autoscaling/v2",
Kind: "HorizontalPodAutoscaler",
}, Service: flaggerv1.CanaryService{
Port: 9898,
+3 -106
View File
@@ -9,7 +9,6 @@ import (
"github.com/google/go-cmp/cmp"
"go.uber.org/zap"
hpav2 "k8s.io/api/autoscaling/v2"
hpav2beta2 "k8s.io/api/autoscaling/v2beta2"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -35,28 +34,19 @@ func (hr *HPAReconciler) ReconcilePrimaryScaler(cd *flaggerv1.Canary, init bool)
}
func (hr *HPAReconciler) reconcilePrimaryHpa(cd *flaggerv1.Canary, init bool) error {
var betaHpa *hpav2beta2.HorizontalPodAutoscaler
hpa, err := hr.kubeClient.AutoscalingV2().HorizontalPodAutoscalers(cd.Namespace).Get(context.TODO(), cd.Spec.AutoscalerRef.Name, metav1.GetOptions{})
if err != nil {
hpa = nil
hr.logger.Debugf("v2 HorizontalPodAutoscaler %s.%s get query error: %s; falling back to v2beta2",
hr.logger.Debugf("v2 HorizontalPodAutoscaler %s.%s get query error: %s;",
cd.Namespace, cd.Spec.AutoscalerRef.Name, err)
var betaErr error
betaHpa, betaErr = hr.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers(cd.Namespace).Get(context.TODO(), cd.Spec.AutoscalerRef.Name, metav1.GetOptions{})
if betaErr != nil {
return fmt.Errorf("HorizontalPodAutoscaler %s.%s get query error for both v2beta2: %s and v2: %s",
cd.Spec.AutoscalerRef.Name, cd.Namespace, betaErr, err)
}
return fmt.Errorf("HorizontalPodAutoscaler %s.%s get query error: %s",
cd.Spec.AutoscalerRef.Name, cd.Namespace, err)
}
if hpa != nil {
if err = hr.reconcilePrimaryHpaV2(cd, hpa, init); err != nil {
return err
}
} else if betaHpa != nil {
if err = hr.reconcilePrimaryHpaV2Beta2(cd, betaHpa, init); err != nil {
return err
}
}
return nil
@@ -155,99 +145,6 @@ func (hr *HPAReconciler) reconcilePrimaryHpaV2(cd *flaggerv1.Canary, hpa *hpav2.
return nil
}
func (hr *HPAReconciler) reconcilePrimaryHpaV2Beta2(cd *flaggerv1.Canary, hpa *hpav2beta2.HorizontalPodAutoscaler, init bool) error {
primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
hpaSpec := hpav2beta2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: hpav2beta2.CrossVersionObjectReference{
Name: primaryName,
Kind: hpa.Spec.ScaleTargetRef.Kind,
APIVersion: hpa.Spec.ScaleTargetRef.APIVersion,
},
MinReplicas: hpa.Spec.MinReplicas,
MaxReplicas: hpa.Spec.MaxReplicas,
Metrics: hpa.Spec.Metrics,
Behavior: hpa.Spec.Behavior,
}
if replicas := cd.Spec.AutoscalerRef.PrimaryScalerReplicas; replicas != nil {
if minReplicas := replicas.MinReplicas; minReplicas != nil {
hpaSpec.MinReplicas = minReplicas
}
if maxReplicas := replicas.MaxReplicas; maxReplicas != nil {
hpaSpec.MaxReplicas = *maxReplicas
}
}
primaryHpaName := fmt.Sprintf("%s-primary", cd.Spec.AutoscalerRef.Name)
primaryHpa, err := hr.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers(cd.Namespace).Get(context.TODO(), primaryHpaName, metav1.GetOptions{})
// create HPA
if errors.IsNotFound(err) {
primaryHpa = &hpav2beta2.HorizontalPodAutoscaler{
ObjectMeta: makeObjectMeta(primaryHpaName, hpa.Labels, cd),
Spec: hpaSpec,
}
_, err = hr.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers(cd.Namespace).Create(context.TODO(), primaryHpa, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("creating HorizontalPodAutoscaler v2beta2 %s.%s failed: %w",
primaryHpa.Name, primaryHpa.Namespace, err)
}
hr.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof(
"HorizontalPodAutoscaler v2beta2 %s.%s created", primaryHpa.GetName(), cd.Namespace)
return nil
} else if err != nil {
return fmt.Errorf("HorizontalPodAutoscaler v2beta2 %s.%s get query failed: %w",
primaryHpa.Name, primaryHpa.Namespace, err)
}
// update HPA
if !init && primaryHpa != nil {
targetFields := hpaFields{
metrics: hpaSpec.Metrics,
behavior: hpaSpec.Behavior,
annotations: hpa.Annotations,
labels: hpa.Labels,
min: hpaSpec.MinReplicas,
max: hpaSpec.MaxReplicas,
}
primaryFields := hpaFields{
metrics: primaryHpa.Spec.Metrics,
behavior: primaryHpa.Spec.Behavior,
annotations: primaryHpa.Annotations,
labels: primaryHpa.Labels,
min: primaryHpa.Spec.MinReplicas,
max: primaryHpa.Spec.MaxReplicas,
}
if hasHPAChanged(targetFields, primaryFields) {
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
primaryHpa, err := hr.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers(cd.Namespace).Get(context.TODO(), primaryHpaName, metav1.GetOptions{})
if err != nil {
return err
}
primaryHpaClone := primaryHpa.DeepCopy()
primaryHpaClone.Spec.MaxReplicas = hpaSpec.MaxReplicas
primaryHpaClone.Spec.MinReplicas = hpaSpec.MinReplicas
primaryHpaClone.Spec.Metrics = hpaSpec.Metrics
primaryHpaClone.Spec.Behavior = hpaSpec.Behavior
hr.updateObjectMeta(primaryHpaClone.ObjectMeta, hpa.ObjectMeta)
_, err = hr.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers(cd.Namespace).Update(context.TODO(), primaryHpaClone, metav1.UpdateOptions{})
return err
})
if err != nil {
return fmt.Errorf("updating HorizontalPodAutoscaler v2beta2 %s.%s failed: %w",
primaryHpa.Name, primaryHpa.Namespace, err)
}
hr.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).
Infof("HorizontalPodAutoscaler v2beta2 %s.%s updated", primaryHpa.GetName(), cd.Namespace)
}
}
return nil
}
func (hr *HPAReconciler) PauseTargetScaler(cd *flaggerv1.Canary) error {
return nil
}
+1 -58
View File
@@ -8,8 +8,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
hpav2 "k8s.io/api/autoscaling/v2"
hpav2beta2 "k8s.io/api/autoscaling/v2beta2"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -17,27 +15,17 @@ func Test_reconcilePrimaryHpa(t *testing.T) {
mocks := newScalerReconcilerFixture(scalerConfig{
targetName: "podinfo",
scaler: "HorizontalPodAutoscaler",
// avoid creating a v2 HPA.
excludeObjs: []string{"HPAV2"},
})
hpaReconciler := mocks.scalerReconciler.(*HPAReconciler)
err := hpaReconciler.reconcilePrimaryHpa(mocks.canary, true)
require.NoError(t, err)
// assert that we fallback to v2beta2, when HPAv2 fails.
_, err = mocks.kubeClient.AutoscalingV2().HorizontalPodAutoscalers("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{})
assert.True(t, errors.IsNotFound(err))
hpa, err := mocks.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{})
require.NoError(t, err)
require.NotNil(t, hpa)
mocks = newScalerReconcilerFixture(scalerConfig{
targetName: "podinfo",
scaler: "HorizontalPodAutoscaler",
// avoid creating _any_ HPAs.
excludeObjs: []string{"HPAV2", "HPAV2Beta2"},
excludeObjs: []string{"HPAV2"},
})
hpaReconciler = mocks.scalerReconciler.(*HPAReconciler)
// assert that we return an error if no HPAs are found.
@@ -88,48 +76,3 @@ func Test_reconcilePrimaryHpaV2(t *testing.T) {
assert.Equal(t, primaryHPA.Spec.MinReplicas, mocks.canary.Spec.AutoscalerRef.PrimaryScalerReplicas.MinReplicas)
assert.Equal(t, primaryHPA.Spec.MaxReplicas, *mocks.canary.Spec.AutoscalerRef.PrimaryScalerReplicas.MaxReplicas)
}
func Test_reconcilePrimaryHpaV2Beta2(t *testing.T) {
mocks := newScalerReconcilerFixture(scalerConfig{
targetName: "podinfo",
scaler: "HorizontalPodAutoscaler",
})
hpaReconciler := mocks.scalerReconciler.(*HPAReconciler)
hpa, err := mocks.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
require.NoError(t, err)
err = hpaReconciler.reconcilePrimaryHpaV2Beta2(mocks.canary, hpa, true)
require.NoError(t, err)
primaryHPA, err := mocks.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{})
require.NoError(t, err)
assert.Equal(t, int(*primaryHPA.Spec.Metrics[0].Resource.Target.AverageUtilization), 99)
hpa.Spec.Metrics[0].Resource.Target = hpav2beta2.MetricTarget{AverageUtilization: int32p(50)}
hpa.Spec.MaxReplicas = 10
_, err = mocks.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers("default").Update(context.TODO(), hpa, metav1.UpdateOptions{})
require.NoError(t, err)
err = hpaReconciler.reconcilePrimaryHpaV2Beta2(mocks.canary, hpa, false)
require.NoError(t, err)
primaryHPA, err = mocks.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{})
require.NoError(t, err)
assert.Equal(t, int(*primaryHPA.Spec.Metrics[0].Resource.Target.AverageUtilization), 50)
assert.Equal(t, int(primaryHPA.Spec.MaxReplicas), 10)
// Test reconcile with PrimaryHorizontalPodAutoscalerOverride
mocks.canary.Spec.AutoscalerRef.PrimaryScalerReplicas = &flaggerv1.ScalerReplicas{
MinReplicas: int32p(2),
MaxReplicas: int32p(15),
}
err = hpaReconciler.reconcilePrimaryHpaV2Beta2(mocks.canary, hpa, false)
require.NoError(t, err)
primaryHPA, err = mocks.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{})
require.NoError(t, err)
assert.Equal(t, primaryHPA.Spec.MinReplicas, mocks.canary.Spec.AutoscalerRef.PrimaryScalerReplicas.MinReplicas)
assert.Equal(t, primaryHPA.Spec.MaxReplicas, *mocks.canary.Spec.AutoscalerRef.PrimaryScalerReplicas.MaxReplicas)
}
@@ -3,7 +3,6 @@ package canary
import (
"go.uber.org/zap"
hpav2 "k8s.io/api/autoscaling/v2"
hpav2beta2 "k8s.io/api/autoscaling/v2beta2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
@@ -40,7 +39,6 @@ func newScalerReconcilerFixture(cfg scalerConfig) scalerReconcilerFixture {
kubeClient := fake.NewSimpleClientset(
newScalerReconcilerTestHPAV2(),
newScalerReconcilerTestHPAV2Beta2(),
)
for _, obj := range cfg.excludeObjs {
if obj == "HPAV2" {
@@ -50,13 +48,6 @@ func newScalerReconcilerFixture(cfg scalerConfig) scalerReconcilerFixture {
Resource: "horizontalpodautoscalers",
}, "default", "podinfo")
}
if obj == "HPAV2Beta2" {
kubeClient.Tracker().Delete(schema.GroupVersionResource{
Group: "autoscaling",
Version: "v2beta2",
Resource: "horizontalpodautoscalers",
}, "default", "podinfo")
}
}
logger, _ := logger.NewLogger("debug")
@@ -88,36 +79,6 @@ func newScalerReconcilerFixture(cfg scalerConfig) scalerReconcilerFixture {
}
}
func newScalerReconcilerTestHPAV2Beta2() *hpav2beta2.HorizontalPodAutoscaler {
h := &hpav2beta2.HorizontalPodAutoscaler{
TypeMeta: metav1.TypeMeta{APIVersion: hpav2beta2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: hpav2beta2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: hpav2beta2.CrossVersionObjectReference{
Name: "podinfo",
APIVersion: "apps/v1",
Kind: "Deployment",
},
Metrics: []hpav2beta2.MetricSpec{
{
Type: "Resource",
Resource: &hpav2beta2.ResourceMetricSource{
Name: "cpu",
Target: hpav2beta2.MetricTarget{
AverageUtilization: int32p(99),
},
},
},
},
},
}
return h
}
func newScalerReconcilerTestHPAV2() *hpav2.HorizontalPodAutoscaler {
h := &hpav2.HorizontalPodAutoscaler{
TypeMeta: metav1.TypeMeta{APIVersion: hpav2.SchemeGroupVersion.String()},
@@ -26,7 +26,7 @@ import (
"github.com/stretchr/testify/require"
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
hpav2 "k8s.io/api/autoscaling/v2beta2"
hpav2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
@@ -300,7 +300,7 @@ func newDeploymentTestCanary() *flaggerv1.Canary {
},
AutoscalerRef: &flaggerv1.AutoscalerReference{
Name: "podinfo",
APIVersion: "autoscaling/v2beta2",
APIVersion: "autoscaling/v2",
Kind: "HorizontalPodAutoscaler",
}, Service: flaggerv1.CanaryService{
Port: 9898,
@@ -362,7 +362,7 @@ func newDeploymentTestCanaryAB() *flaggerv1.Canary {
},
AutoscalerRef: &flaggerv1.AutoscalerReference{
Name: "podinfo",
APIVersion: "autoscaling/v2beta2",
APIVersion: "autoscaling/v2",
Kind: "HorizontalPodAutoscaler",
}, Service: flaggerv1.CanaryService{
Port: 9898,