mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 04:54:54 +00:00
Merge pull request #136998 from yongruilin/master-vg_hpa-ifenable
Migrate HPA MinReplicas to Declarative Validation with Feature Gate Support
This commit is contained in:
@@ -102,6 +102,12 @@ func Validate_HorizontalPodAutoscalerSpec(ctx context.Context, op operation.Oper
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "HPAScaleToZero", false, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList {
|
||||
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 1)
|
||||
})...)
|
||||
errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "HPAScaleToZero", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList {
|
||||
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)
|
||||
})...)
|
||||
return
|
||||
}(fldPath.Child("minReplicas"), obj.MinReplicas, safe.Field(oldObj, func(oldObj *autoscalingv1.HorizontalPodAutoscalerSpec) *int32 { return oldObj.MinReplicas }), oldObj != nil)...)
|
||||
|
||||
|
||||
@@ -94,6 +94,12 @@ func Validate_HorizontalPodAutoscalerSpec(ctx context.Context, op operation.Oper
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "HPAScaleToZero", false, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList {
|
||||
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 1)
|
||||
})...)
|
||||
errs = append(errs, validate.IfOption(ctx, op, fldPath, obj, oldObj, "HPAScaleToZero", true, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int32) field.ErrorList {
|
||||
return validate.Minimum(ctx, op, fldPath, obj, oldObj, 0)
|
||||
})...)
|
||||
return
|
||||
}(fldPath.Child("minReplicas"), obj.MinReplicas, safe.Field(oldObj, func(oldObj *autoscalingv2.HorizontalPodAutoscalerSpec) *int32 { return oldObj.MinReplicas }), oldObj != nil)...)
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ func validateHorizontalPodAutoscalerSpec(autoscaler autoscaling.HorizontalPodAut
|
||||
|
||||
if autoscaler.MinReplicas != nil && *autoscaler.MinReplicas < opts.MinReplicasLowerBound {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("minReplicas"), *autoscaler.MinReplicas,
|
||||
fmt.Sprintf("must be greater than or equal to %d", opts.MinReplicasLowerBound)))
|
||||
fmt.Sprintf("must be greater than or equal to %d", opts.MinReplicasLowerBound)).WithOrigin("minimum").MarkCoveredByDeclarative())
|
||||
}
|
||||
if autoscaler.MaxReplicas == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("maxReplicas"), "must be set and greater than 0").MarkCoveredByDeclarative())
|
||||
|
||||
@@ -19,11 +19,16 @@ package horizontalpodautoscaler
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/autoscaling"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
var apiVersions = []string{"v1", "v2"}
|
||||
@@ -41,12 +46,17 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
|
||||
Resource: "horizontalpodautoscalers",
|
||||
})
|
||||
testCases := map[string]struct {
|
||||
input api.HorizontalPodAutoscaler
|
||||
expectedErrs field.ErrorList
|
||||
input api.HorizontalPodAutoscaler
|
||||
expectedErrs field.ErrorList
|
||||
enableScaleToZero bool
|
||||
}{
|
||||
"valid: minReplicas = 5": {
|
||||
input: makeValidHPA(tweakMinReplicas(5)),
|
||||
},
|
||||
"valid: minReplicas = 0 (gate enabled)": {
|
||||
input: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...)),
|
||||
enableScaleToZero: true,
|
||||
},
|
||||
"valid: minReplicas not set (nil)": {
|
||||
input: makeValidHPA(), // Default, no minReplicas set
|
||||
},
|
||||
@@ -62,9 +72,16 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
|
||||
field.Invalid(field.NewPath("spec", "maxReplicas"), int32(-1), "must be greater than or equal to 1").WithOrigin("minimum"),
|
||||
},
|
||||
},
|
||||
"invalid: minReplicas = 0 (gate disabled)": {
|
||||
input: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "minReplicas"), int32(0), "must be greater than or equal to 1").WithOrigin("minimum"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, tc.enableScaleToZero)
|
||||
apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs)
|
||||
})
|
||||
}
|
||||
@@ -80,9 +97,10 @@ func TestDeclarativeValidateUpdate(t *testing.T) {
|
||||
|
||||
func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) {
|
||||
testCases := map[string]struct {
|
||||
oldObj api.HorizontalPodAutoscaler
|
||||
updateObj api.HorizontalPodAutoscaler
|
||||
expectedErrs field.ErrorList
|
||||
oldObj api.HorizontalPodAutoscaler
|
||||
updateObj api.HorizontalPodAutoscaler
|
||||
expectedErrs field.ErrorList
|
||||
enableScaleToZero bool
|
||||
}{
|
||||
"valid update": {
|
||||
oldObj: makeValidHPA(),
|
||||
@@ -92,6 +110,11 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) {
|
||||
oldObj: makeValidHPA(tweakMinReplicas(1)),
|
||||
updateObj: makeValidHPA(tweakMinReplicas(5)),
|
||||
},
|
||||
"valid update: minReplicas 1 -> 0 (gate enabled)": {
|
||||
oldObj: makeValidHPA(tweakMinReplicas(1), tweakMetrics(validScaleToZeroMetrics...)),
|
||||
updateObj: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...)),
|
||||
enableScaleToZero: true,
|
||||
},
|
||||
"invalid update: maxReplicas = 0 (required)": {
|
||||
oldObj: makeValidHPA(),
|
||||
updateObj: makeValidHPA(tweakMaxReplicas(0)),
|
||||
@@ -106,9 +129,22 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) {
|
||||
field.Invalid(field.NewPath("spec", "maxReplicas"), int32(-1), "must be greater than or equal to 1").WithOrigin("minimum"),
|
||||
},
|
||||
},
|
||||
"invalid update: minReplicas 1 -> 0 (gate disabled)": {
|
||||
oldObj: makeValidHPA(tweakMinReplicas(1), tweakMetrics(validScaleToZeroMetrics...)),
|
||||
updateObj: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "minReplicas"), int32(0), "must be greater than or equal to 1").WithOrigin("minimum"),
|
||||
},
|
||||
},
|
||||
"valid update: ratcheting minReplicas=0 when gate disabled": {
|
||||
oldObj: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...)),
|
||||
updateObj: makeValidHPA(tweakMinReplicas(0), tweakMetrics(validScaleToZeroMetrics...), tweakMaxReplicas(20)),
|
||||
enableScaleToZero: false,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, tc.enableScaleToZero)
|
||||
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{
|
||||
APIGroup: "autoscaling",
|
||||
APIVersion: apiVersion,
|
||||
@@ -155,3 +191,29 @@ func tweakMaxReplicas(replicas int32) func(*api.HorizontalPodAutoscaler) {
|
||||
hpa.Spec.MaxReplicas = replicas
|
||||
}
|
||||
}
|
||||
|
||||
func tweakMetrics(metrics ...api.MetricSpec) func(*api.HorizontalPodAutoscaler) {
|
||||
return func(hpa *api.HorizontalPodAutoscaler) {
|
||||
hpa.Spec.Metrics = metrics
|
||||
}
|
||||
}
|
||||
|
||||
var validScaleToZeroMetrics = []api.MetricSpec{
|
||||
{
|
||||
Type: api.ObjectMetricSourceType,
|
||||
Object: &api.ObjectMetricSource{
|
||||
Metric: api.MetricIdentifier{
|
||||
Name: "requests-per-second",
|
||||
},
|
||||
Target: api.MetricTarget{
|
||||
Type: api.ValueMetricType,
|
||||
Value: ptr.To(resource.MustParse("10k")),
|
||||
},
|
||||
DescribedObject: api.CrossVersionObjectReference{
|
||||
Kind: "Ingress",
|
||||
Name: "main-route",
|
||||
APIVersion: "networking.k8s.io/v1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -77,7 +77,11 @@ func (autoscalerStrategy) Validate(ctx context.Context, obj runtime.Object) fiel
|
||||
autoscaler := obj.(*autoscaling.HorizontalPodAutoscaler)
|
||||
opts := validationOptionsForHorizontalPodAutoscaler(autoscaler, nil)
|
||||
allErrs := validation.ValidateHorizontalPodAutoscaler(autoscaler, opts)
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, autoscaler, nil, allErrs, operation.Create)
|
||||
var options []string
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.HPAScaleToZero) {
|
||||
options = append(options, "HPAScaleToZero")
|
||||
}
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, autoscaler, nil, allErrs, operation.Create, rest.WithOptions(options))
|
||||
}
|
||||
|
||||
// WarningsOnCreate returns warnings for the creation of the given object.
|
||||
@@ -110,7 +114,12 @@ func (autoscalerStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.O
|
||||
oldHPA := old.(*autoscaling.HorizontalPodAutoscaler)
|
||||
opts := validationOptionsForHorizontalPodAutoscaler(newHPA, oldHPA)
|
||||
errs := validation.ValidateHorizontalPodAutoscalerUpdate(newHPA, oldHPA, opts)
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newHPA, oldHPA, errs, operation.Update)
|
||||
var options []string
|
||||
oldHasZeroMinReplicas := oldHPA.Spec.MinReplicas != nil && *oldHPA.Spec.MinReplicas == 0
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.HPAScaleToZero) || oldHasZeroMinReplicas {
|
||||
options = append(options, "HPAScaleToZero")
|
||||
}
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newHPA, oldHPA, errs, operation.Update, rest.WithOptions(options))
|
||||
}
|
||||
|
||||
// WarningsOnUpdate returns warnings for the given update.
|
||||
|
||||
@@ -203,6 +203,8 @@ message HorizontalPodAutoscalerSpec {
|
||||
// available.
|
||||
// +optional
|
||||
// +k8s:optional
|
||||
// +k8s:ifEnabled(HPAScaleToZero)=+k8s:minimum=0
|
||||
// +k8s:ifDisabled(HPAScaleToZero)=+k8s:minimum=1
|
||||
optional int32 minReplicas = 2;
|
||||
|
||||
// maxReplicas is the upper limit for the number of pods that can be set by the autoscaler; cannot be smaller than MinReplicas.
|
||||
|
||||
@@ -48,6 +48,8 @@ type HorizontalPodAutoscalerSpec struct {
|
||||
// available.
|
||||
// +optional
|
||||
// +k8s:optional
|
||||
// +k8s:ifEnabled(HPAScaleToZero)=+k8s:minimum=0
|
||||
// +k8s:ifDisabled(HPAScaleToZero)=+k8s:minimum=1
|
||||
MinReplicas *int32 `json:"minReplicas,omitempty" protobuf:"varint,2,opt,name=minReplicas"`
|
||||
|
||||
// maxReplicas is the upper limit for the number of pods that can be set by the autoscaler; cannot be smaller than MinReplicas.
|
||||
|
||||
@@ -249,6 +249,8 @@ message HorizontalPodAutoscalerSpec {
|
||||
// available.
|
||||
// +optional
|
||||
// +k8s:optional
|
||||
// +k8s:ifEnabled(HPAScaleToZero)=+k8s:minimum=0
|
||||
// +k8s:ifDisabled(HPAScaleToZero)=+k8s:minimum=1
|
||||
optional int32 minReplicas = 2;
|
||||
|
||||
// maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up.
|
||||
|
||||
@@ -60,6 +60,8 @@ type HorizontalPodAutoscalerSpec struct {
|
||||
// available.
|
||||
// +optional
|
||||
// +k8s:optional
|
||||
// +k8s:ifEnabled(HPAScaleToZero)=+k8s:minimum=0
|
||||
// +k8s:ifDisabled(HPAScaleToZero)=+k8s:minimum=1
|
||||
MinReplicas *int32 `json:"minReplicas,omitempty" protobuf:"varint,2,opt,name=minReplicas"`
|
||||
|
||||
// maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up.
|
||||
|
||||
Reference in New Issue
Block a user