From f9c568168b81f34dc64618781440e2dd8dd5f394 Mon Sep 17 00:00:00 2001 From: thiagomedina Date: Fri, 27 Feb 2026 18:58:08 -0300 Subject: [PATCH 1/4] use scale subresource instead of hardcoded deployment patching --- config/core/200-roles/clusterrole.yaml | 3 ++ pkg/reconciler/autoscaling/kpa/scaler.go | 47 +++++++++++------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/config/core/200-roles/clusterrole.yaml b/config/core/200-roles/clusterrole.yaml index 5ab0e53829ff..3ad88d21028e 100644 --- a/config/core/200-roles/clusterrole.yaml +++ b/config/core/200-roles/clusterrole.yaml @@ -33,6 +33,9 @@ rules: - apiGroups: ["apps"] resources: ["deployments", "deployments/finalizers"] # finalizers are needed for the owner reference of the webhook verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + - apiGroups: ["*"] + resources: ["*/scale"] + verbs: ["get", "update", "patch"] - apiGroups: ["admissionregistration.k8s.io"] resources: ["mutatingwebhookconfigurations", "validatingwebhookconfigurations"] verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] diff --git a/pkg/reconciler/autoscaling/kpa/scaler.go b/pkg/reconciler/autoscaling/kpa/scaler.go index daafcf746cbb..a09c45ce688c 100644 --- a/pkg/reconciler/autoscaling/kpa/scaler.go +++ b/pkg/reconciler/autoscaling/kpa/scaler.go @@ -24,6 +24,9 @@ import ( "strconv" "time" + autoscalingv1 "k8s.io/api/autoscaling/v1" + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/pkg/apis/duck" "knative.dev/pkg/injection/clients/dynamicclient" "knative.dev/pkg/logging" @@ -299,30 +302,15 @@ func (ks *scaler) handleScaleToZero(ctx context.Context, pa *autoscalingv1alpha1 } func (ks *scaler) applyScale(ctx context.Context, pa *autoscalingv1alpha1.PodAutoscaler, desiredScale int32, - ps *autoscalingv1alpha1.PodScalable, -) error { + gvr *schema.GroupVersionResource, resourceName string) error { logger := logging.FromContext(ctx) - gvr, name, err := resources.ScaleResourceArguments(pa.Spec.ScaleTargetRef) - if err != nil { - return err - } + patchBytes := []byte(fmt.Sprintf(`[{"op":"replace","path":"/spec/replicas","value":%d}]`, desiredScale)) - psNew := ps.DeepCopy() - psNew.Spec.Replicas = &desiredScale - patch, err := duck.CreatePatch(ps, psNew) + _, err := ks.dynamicClient.Resource(*gvr).Namespace(pa.Namespace).Patch(ctx, resourceName, types.JSONPatchType, + patchBytes, metav1.PatchOptions{}, "scale") if err != nil { - return err - } - patchBytes, err := patch.MarshalJSON() - if err != nil { - return err - } - - _, err = ks.dynamicClient.Resource(*gvr).Namespace(pa.Namespace).Patch(ctx, ps.Name, types.JSONPatchType, - patchBytes, metav1.PatchOptions{}) - if err != nil { - return fmt.Errorf("failed to apply scale %d to scale target %s: %w", desiredScale, name, err) + return fmt.Errorf("failed to apply scale %d to scale target %s: %w", desiredScale, resourceName, err) } logger.Debug("Successfully scaled to ", desiredScale) @@ -362,19 +350,28 @@ func (ks *scaler) scale(ctx context.Context, pa *autoscalingv1alpha1.PodAutoscal return desiredScale, nil } - ps, err := resources.GetScaleResource(pa.Namespace, pa.Spec.ScaleTargetRef, ks.listerFactory) + gvr, name, err := resources.ScaleResourceArguments(pa.Spec.ScaleTargetRef) + if err != nil { + return desiredScale, err + } + + scaleObj, err := ks.dynamicClient.Resource(*gvr).Namespace(pa.Namespace).Get(ctx, name, metav1.GetOptions{}, "scale") if err != nil { return desiredScale, fmt.Errorf("failed to get scale target %v: %w", pa.Spec.ScaleTargetRef, err) } - currentScale := int32(1) - if ps.Spec.Replicas != nil { - currentScale = *ps.Spec.Replicas + scale := &autoscalingv1.Scale{} + + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(scaleObj.UnstructuredContent(), + scale); err != nil { + return desiredScale, fmt.Errorf("failed to convert to autoscalingv1.Scale type: %w", err) } + + currentScale := scale.Spec.Replicas if desiredScale == currentScale { return desiredScale, nil } logger.Infof("Scaling from %d to %d", currentScale, desiredScale) - return desiredScale, ks.applyScale(ctx, pa, desiredScale, ps) + return desiredScale, ks.applyScale(ctx, pa, desiredScale, gvr, name) } From 3942f76c07bb0fb4b2bb6e466c2d6a7f1c458faf Mon Sep 17 00:00:00 2001 From: thiagomedina Date: Fri, 27 Feb 2026 18:58:55 -0300 Subject: [PATCH 2/4] update kpa tests for scale subresource --- pkg/reconciler/autoscaling/kpa/kpa_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/reconciler/autoscaling/kpa/kpa_test.go b/pkg/reconciler/autoscaling/kpa/kpa_test.go index 107d232ff0c8..6c7efdcbaff0 100644 --- a/pkg/reconciler/autoscaling/kpa/kpa_test.go +++ b/pkg/reconciler/autoscaling/kpa/kpa_test.go @@ -438,7 +438,7 @@ func TestReconcile(t *testing.T) { Namespace: testNamespace, }, Name: deployName, - Patch: []byte(`[{"op":"add","path":"/spec/replicas","value":11}]`), + Patch: []byte(`[{"op":"replace","path":"/spec/replicas","value":11}]`), }}, }, { Name: "scale up deployment failure", @@ -459,7 +459,7 @@ func TestReconcile(t *testing.T) { Namespace: testNamespace, }, Name: deployName, - Patch: []byte(`[{"op":"add","path":"/spec/replicas","value":11}]`), + Patch: []byte(`[{"op":"replace","path":"/spec/replicas","value":11}]`), }}, WantEvents: []string{ Eventf(corev1.EventTypeWarning, "InternalError", @@ -735,14 +735,14 @@ func TestReconcile(t *testing.T) { WithPAMetricsService(privateSvc), WithObservedGeneration(1)), sks(testNamespace, testRevision, WithDeployRef(deployName), WithProxyMode, WithSKSReady), metric(testNamespace, testRevision), - deploy(testNamespace, testRevision), + deploy(testNamespace, testRevision, func(d *appsv1.Deployment) { d.Spec.Replicas = ptr.Int32(1) }), }, WantPatches: []clientgotesting.PatchActionImpl{{ ActionImpl: clientgotesting.ActionImpl{ Namespace: testNamespace, }, Name: deployName, - Patch: []byte(`[{"op":"add","path":"/spec/replicas","value":0}]`), + Patch: []byte(`[{"op":"replace","path":"/spec/replicas","value":0}]`), }}, }, { Name: "from serving to proxy", @@ -777,7 +777,7 @@ func TestReconcile(t *testing.T) { WithPAStatusService(testRevision), WithPAMetricsService(privateSvc), WithObservedGeneration(1)), defaultSKS, metric(testNamespace, testRevision), - deploy(testNamespace, testRevision), defaultReady, + deploy(testNamespace, testRevision, func(d *appsv1.Deployment) { d.Spec.Replicas = ptr.Int32(1) }), defaultReady, }, }, { Name: "activation failure", @@ -790,7 +790,7 @@ func TestReconcile(t *testing.T) { WithPAMetricsService(privateSvc)), defaultSKS, metric(testNamespace, testRevision), - deploy(testNamespace, testRevision), defaultReady, + deploy(testNamespace, testRevision, func(d *appsv1.Deployment) { d.Spec.Replicas = ptr.Int32(1) }), defaultReady, }, WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ Object: kpa(testNamespace, testRevision, markScaleTargetInitialized, WithPASKSReady, WithPAMetricsService(privateSvc), @@ -806,7 +806,7 @@ func TestReconcile(t *testing.T) { Namespace: testNamespace, }, Name: deployName, - Patch: []byte(`[{"op":"add","path":"/spec/replicas","value":0}]`), + Patch: []byte(`[{"op":"replace","path":"/spec/replicas","value":0}]`), }}, }, { Name: "want=-1, underscaled, PA inactive", From 9df51438aab8d8a2aeb4d35ff1e843862dbb6412 Mon Sep 17 00:00:00 2001 From: thiagomedina Date: Fri, 27 Feb 2026 19:20:27 -0300 Subject: [PATCH 3/4] fix lint --- pkg/reconciler/autoscaling/kpa/scaler.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/reconciler/autoscaling/kpa/scaler.go b/pkg/reconciler/autoscaling/kpa/scaler.go index a09c45ce688c..b20c18bf30d8 100644 --- a/pkg/reconciler/autoscaling/kpa/scaler.go +++ b/pkg/reconciler/autoscaling/kpa/scaler.go @@ -302,7 +302,8 @@ func (ks *scaler) handleScaleToZero(ctx context.Context, pa *autoscalingv1alpha1 } func (ks *scaler) applyScale(ctx context.Context, pa *autoscalingv1alpha1.PodAutoscaler, desiredScale int32, - gvr *schema.GroupVersionResource, resourceName string) error { + gvr *schema.GroupVersionResource, resourceName string, +) error { logger := logging.FromContext(ctx) patchBytes := []byte(fmt.Sprintf(`[{"op":"replace","path":"/spec/replicas","value":%d}]`, desiredScale)) From 1161ec470e8a2d9b67145edc2bdfcea008970b12 Mon Sep 17 00:00:00 2001 From: thiagomedina Date: Sat, 28 Feb 2026 11:08:06 -0300 Subject: [PATCH 4/4] remove empty lines --- pkg/reconciler/autoscaling/kpa/scaler.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/reconciler/autoscaling/kpa/scaler.go b/pkg/reconciler/autoscaling/kpa/scaler.go index b20c18bf30d8..2b3e7271893a 100644 --- a/pkg/reconciler/autoscaling/kpa/scaler.go +++ b/pkg/reconciler/autoscaling/kpa/scaler.go @@ -355,19 +355,16 @@ func (ks *scaler) scale(ctx context.Context, pa *autoscalingv1alpha1.PodAutoscal if err != nil { return desiredScale, err } - scaleObj, err := ks.dynamicClient.Resource(*gvr).Namespace(pa.Namespace).Get(ctx, name, metav1.GetOptions{}, "scale") if err != nil { return desiredScale, fmt.Errorf("failed to get scale target %v: %w", pa.Spec.ScaleTargetRef, err) } - scale := &autoscalingv1.Scale{} if err := runtime.DefaultUnstructuredConverter.FromUnstructured(scaleObj.UnstructuredContent(), scale); err != nil { return desiredScale, fmt.Errorf("failed to convert to autoscalingv1.Scale type: %w", err) } - currentScale := scale.Spec.Replicas if desiredScale == currentScale { return desiredScale, nil