From 172cbb286fd391a42b54ff515e0d02a56a21eaf1 Mon Sep 17 00:00:00 2001 From: keithfz Date: Fri, 13 Dec 2024 20:27:06 -0500 Subject: [PATCH] feat: support patching on EnvoyProxy.spec.provider.kubernetes.envoyHpa and EnvoyProxy.spec.provider.kubernetes.envoyPDB (#4910) * Add patch field for envoyHPA and envoyPDB in EnvoyGateway API Signed-off-by: keithfz * It's actually currently the envoyPDB field, not envoyPdb Signed-off-by: keithfz * fix comment Signed-off-by: keithfz * fix error messages Signed-off-by: keithfz * Add validation for hpa and pdb Signed-off-by: keithfz * lint and gen-check Signed-off-by: keithfz * adding test coverage Signed-off-by: keithfz * lint Signed-off-by: keithfz --------- Signed-off-by: keithfz --- api/v1alpha1/kubernetes_helpers.go | 74 +++++++ api/v1alpha1/shared_types.go | 10 + .../validation/envoyproxy_validate.go | 38 ++++ .../validation/envoyproxy_validate_test.go | 186 ++++++++++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 10 + .../gateway.envoyproxy.io_envoyproxies.yaml | 34 ++++ .../kubernetes/proxy/resource_provider.go | 23 ++- .../proxy/resource_provider_test.go | 52 +++++ .../proxy/testdata/hpa/patch-json-hpa.yaml | 21 ++ .../testdata/hpa/patch-strategic-hpa.yaml | 21 ++ .../proxy/testdata/pdb/patch-json-pdb.yaml | 15 ++ .../testdata/pdb/patch-strategic-pdb.yaml | 10 + release-notes/current.yaml | 1 + site/content/en/latest/api/extension_types.md | 4 + site/content/zh/latest/api/extension_types.md | 4 + 15 files changed, 497 insertions(+), 6 deletions(-) create mode 100644 internal/infrastructure/kubernetes/proxy/testdata/hpa/patch-json-hpa.yaml create mode 100644 internal/infrastructure/kubernetes/proxy/testdata/hpa/patch-strategic-hpa.yaml create mode 100644 internal/infrastructure/kubernetes/proxy/testdata/pdb/patch-json-pdb.yaml create mode 100644 internal/infrastructure/kubernetes/proxy/testdata/pdb/patch-strategic-pdb.yaml diff --git a/api/v1alpha1/kubernetes_helpers.go b/api/v1alpha1/kubernetes_helpers.go index 1ac790b9c13..761f880d29b 100644 --- a/api/v1alpha1/kubernetes_helpers.go +++ b/api/v1alpha1/kubernetes_helpers.go @@ -11,7 +11,9 @@ import ( jsonpatch "github.com/evanphx/json-patch" appsv1 "k8s.io/api/apps/v1" + autoscalingv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/utils/ptr" @@ -263,3 +265,75 @@ func (service *KubernetesServiceSpec) ApplyMergePatch(old *corev1.Service) (*cor return &patchedService, nil } + +// ApplyMergePatch applies a merge patch to a HorizontalPodAutoscaler based on the merge type +func (hpa *KubernetesHorizontalPodAutoscalerSpec) ApplyMergePatch(old *autoscalingv2.HorizontalPodAutoscaler) (*autoscalingv2.HorizontalPodAutoscaler, error) { + if hpa.Patch == nil { + return old, nil + } + + var patchedJSON []byte + var err error + + // Serialize the current HPA to JSON + originalJSON, err := json.Marshal(old) + if err != nil { + return nil, fmt.Errorf("error marshaling original HorizontalPodAutoscaler: %w", err) + } + + switch { + case hpa.Patch.Type == nil || *hpa.Patch.Type == StrategicMerge: + patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, hpa.Patch.Value.Raw, autoscalingv2.HorizontalPodAutoscaler{}) + case *hpa.Patch.Type == JSONMerge: + patchedJSON, err = jsonpatch.MergePatch(originalJSON, hpa.Patch.Value.Raw) + default: + return nil, fmt.Errorf("unsupported merge type: %s", *hpa.Patch.Type) + } + if err != nil { + return nil, fmt.Errorf("error applying merge patch: %w", err) + } + + // Deserialize the patched JSON into a new HorizontalPodAutoscaler object + var patchedHpa autoscalingv2.HorizontalPodAutoscaler + if err := json.Unmarshal(patchedJSON, &patchedHpa); err != nil { + return nil, fmt.Errorf("error unmarshaling patched HorizontalPodAutoscaler: %w", err) + } + + return &patchedHpa, nil +} + +// ApplyMergePatch applies a merge patch to a PodDisruptionBudget based on the merge type +func (pdb *KubernetesPodDisruptionBudgetSpec) ApplyMergePatch(old *policyv1.PodDisruptionBudget) (*policyv1.PodDisruptionBudget, error) { + if pdb.Patch == nil { + return old, nil + } + + var patchedJSON []byte + var err error + + // Serialize the PDB deployment to JSON + originalJSON, err := json.Marshal(old) + if err != nil { + return nil, fmt.Errorf("error marshaling original PodDisruptionBudget: %w", err) + } + + switch { + case pdb.Patch.Type == nil || *pdb.Patch.Type == StrategicMerge: + patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, pdb.Patch.Value.Raw, policyv1.PodDisruptionBudget{}) + case *pdb.Patch.Type == JSONMerge: + patchedJSON, err = jsonpatch.MergePatch(originalJSON, pdb.Patch.Value.Raw) + default: + return nil, fmt.Errorf("unsupported merge type: %s", *pdb.Patch.Type) + } + if err != nil { + return nil, fmt.Errorf("error applying merge patch: %w", err) + } + + // Deserialize the patched JSON into a new HorizontalPodAutoscaler object + var patchedPdb policyv1.PodDisruptionBudget + if err := json.Unmarshal(patchedJSON, &patchedPdb); err != nil { + return nil, fmt.Errorf("error unmarshaling patched PodDisruptionBudget: %w", err) + } + + return &patchedPdb, nil +} diff --git a/api/v1alpha1/shared_types.go b/api/v1alpha1/shared_types.go index b79839a7dda..036054dc47e 100644 --- a/api/v1alpha1/shared_types.go +++ b/api/v1alpha1/shared_types.go @@ -406,6 +406,11 @@ type KubernetesPodDisruptionBudgetSpec struct { // and resilience during maintenance operations. // +optional MinAvailable *int32 `json:"minAvailable,omitempty"` + + // Patch defines how to perform the patch operation to the PodDisruptionBudget + // + // +optional + Patch *KubernetesPatchSpec `json:"patch,omitempty"` } // KubernetesHorizontalPodAutoscalerSpec defines Kubernetes Horizontal Pod Autoscaler settings of Envoy Proxy Deployment. @@ -443,6 +448,11 @@ type KubernetesHorizontalPodAutoscalerSpec struct { // // +optional Behavior *autoscalingv2.HorizontalPodAutoscalerBehavior `json:"behavior,omitempty"` + + // Patch defines how to perform the patch operation to the HorizontalPodAutoscaler + // + // +optional + Patch *KubernetesPatchSpec `json:"patch,omitempty"` } // HTTPStatus defines the http status code. diff --git a/api/v1alpha1/validation/envoyproxy_validate.go b/api/v1alpha1/validation/envoyproxy_validate.go index 74ce4e0451c..a13fdacbd3d 100644 --- a/api/v1alpha1/validation/envoyproxy_validate.go +++ b/api/v1alpha1/validation/envoyproxy_validate.go @@ -72,6 +72,14 @@ func validateProvider(spec *egv1a1.EnvoyProxySpec) []error { if len(validateDeploymentErrs) != 0 { errs = append(errs, validateDeploymentErrs...) } + validateHpaErrors := validateHpa(spec) + if len(validateHpaErrors) != 0 { + errs = append(errs, validateHpaErrors...) + } + validatePdbErrors := validatePdb(spec) + if len(validatePdbErrors) != 0 { + errs = append(errs, validatePdbErrors...) + } validateServiceErrs := validateService(spec) if len(validateServiceErrs) != 0 { errs = append(errs, validateServiceErrs...) @@ -95,6 +103,36 @@ func validateDeployment(spec *egv1a1.EnvoyProxySpec) []error { return errs } +func validateHpa(spec *egv1a1.EnvoyProxySpec) []error { + var errs []error + if spec.Provider.Kubernetes != nil && spec.Provider.Kubernetes.EnvoyHpa != nil { + if patch := spec.Provider.Kubernetes.EnvoyHpa.Patch; patch != nil { + if patch.Value.Raw == nil { + errs = append(errs, fmt.Errorf("envoy hpa patch object cannot be empty")) + } + if patch.Type != nil && *patch.Type != egv1a1.JSONMerge && *patch.Type != egv1a1.StrategicMerge { + errs = append(errs, fmt.Errorf("unsupported envoy hpa patch type %s", *patch.Type)) + } + } + } + return errs +} + +func validatePdb(spec *egv1a1.EnvoyProxySpec) []error { + var errs []error + if spec.Provider.Kubernetes != nil && spec.Provider.Kubernetes.EnvoyPDB != nil { + if patch := spec.Provider.Kubernetes.EnvoyPDB.Patch; patch != nil { + if patch.Value.Raw == nil { + errs = append(errs, fmt.Errorf("envoy pdb patch object cannot be empty")) + } + if patch.Type != nil && *patch.Type != egv1a1.JSONMerge && *patch.Type != egv1a1.StrategicMerge { + errs = append(errs, fmt.Errorf("unsupported envoy pdb patch type %s", *patch.Type)) + } + } + } + return errs +} + // TODO: remove this function if CEL validation became stable func validateService(spec *egv1a1.EnvoyProxySpec) []error { var errs []error diff --git a/api/v1alpha1/validation/envoyproxy_validate_test.go b/api/v1alpha1/validation/envoyproxy_validate_test.go index e4b400b34dd..8a784db59ab 100644 --- a/api/v1alpha1/validation/envoyproxy_validate_test.go +++ b/api/v1alpha1/validation/envoyproxy_validate_test.go @@ -450,6 +450,192 @@ func TestValidateEnvoyProxy(t *testing.T) { }, expected: true, }, + { + name: "should be valid when pdb patch type and patch are empty", + proxy: &egv1a1.EnvoyProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.ProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyPDB: &egv1a1.KubernetesPodDisruptionBudgetSpec{ + Patch: &egv1a1.KubernetesPatchSpec{ + Value: apiextensionsv1.JSON{ + Raw: []byte{}, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "should be valid when pdb patch and type are set", + proxy: &egv1a1.EnvoyProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.ProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyPDB: &egv1a1.KubernetesPodDisruptionBudgetSpec{ + Patch: &egv1a1.KubernetesPatchSpec{ + Type: ptr.To(egv1a1.StrategicMerge), + Value: apiextensionsv1.JSON{ + Raw: []byte("{}"), + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "should be invalid when pdb patch not set", + proxy: &egv1a1.EnvoyProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.ProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyPDB: &egv1a1.KubernetesPodDisruptionBudgetSpec{ + Patch: &egv1a1.KubernetesPatchSpec{ + Type: ptr.To(egv1a1.StrategicMerge), + }, + }, + }, + }, + }, + }, + expected: false, + }, + { + name: "should be invalid when pdb type not set", + proxy: &egv1a1.EnvoyProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.ProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyPDB: &egv1a1.KubernetesPodDisruptionBudgetSpec{ + Patch: &egv1a1.KubernetesPatchSpec{ + Type: ptr.To(egv1a1.StrategicMerge), + }, + }, + }, + }, + }, + }, + expected: false, + }, + { + name: "should be valid when hpa patch and type are empty", + proxy: &egv1a1.EnvoyProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.ProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyHpa: &egv1a1.KubernetesHorizontalPodAutoscalerSpec{ + Patch: &egv1a1.KubernetesPatchSpec{ + Value: apiextensionsv1.JSON{ + Raw: []byte{}, + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "should be valid when hpa patch and type are set", + proxy: &egv1a1.EnvoyProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.ProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyHpa: &egv1a1.KubernetesHorizontalPodAutoscalerSpec{ + Patch: &egv1a1.KubernetesPatchSpec{ + Type: ptr.To(egv1a1.StrategicMerge), + Value: apiextensionsv1.JSON{ + Raw: []byte("{}"), + }, + }, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "should be invalid when hpa patch not set", + proxy: &egv1a1.EnvoyProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.ProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyHpa: &egv1a1.KubernetesHorizontalPodAutoscalerSpec{ + Patch: &egv1a1.KubernetesPatchSpec{ + Type: ptr.To(egv1a1.StrategicMerge), + }, + }, + }, + }, + }, + }, + expected: false, + }, + { + name: "should be invalid when hpa type not set", + proxy: &egv1a1.EnvoyProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.ProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyHpa: &egv1a1.KubernetesHorizontalPodAutoscalerSpec{ + Patch: &egv1a1.KubernetesPatchSpec{ + Type: ptr.To(egv1a1.StrategicMerge), + }, + }, + }, + }, + }, + }, + expected: false, + }, { name: "should invalid when patch object is empty", proxy: &egv1a1.EnvoyProxy{ diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 742ffed1b25..dbc28e6aca2 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3515,6 +3515,11 @@ func (in *KubernetesHorizontalPodAutoscalerSpec) DeepCopyInto(out *KubernetesHor *out = new(v2.HorizontalPodAutoscalerBehavior) (*in).DeepCopyInto(*out) } + if in.Patch != nil { + in, out := &in.Patch, &out.Patch + *out = new(KubernetesPatchSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesHorizontalPodAutoscalerSpec. @@ -3556,6 +3561,11 @@ func (in *KubernetesPodDisruptionBudgetSpec) DeepCopyInto(out *KubernetesPodDisr *out = new(int32) **out = **in } + if in.Patch != nil { + in, out := &in.Patch, &out.Patch + *out = new(KubernetesPatchSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesPodDisruptionBudgetSpec. diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml index 84fb126a79b..1a262dd466b 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -10109,6 +10109,23 @@ spec: x-kubernetes-validations: - message: minReplicas must be greater than 0 rule: self > 0 + patch: + description: Patch defines how to perform the patch operation + to the HorizontalPodAutoscaler + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object required: - maxReplicas type: object @@ -10126,6 +10143,23 @@ spec: and resilience during maintenance operations. format: int32 type: integer + patch: + description: Patch defines how to perform the patch operation + to the PodDisruptionBudget + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object type: object envoyService: description: |- diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider.go b/internal/infrastructure/kubernetes/proxy/resource_provider.go index 9830bafad71..9c25886a6bf 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider.go @@ -433,13 +433,13 @@ func (r *ResourceRender) PodDisruptionBudgetSpec() (*egv1a1.KubernetesPodDisrupt } func (r *ResourceRender) PodDisruptionBudget() (*policyv1.PodDisruptionBudget, error) { - podDisruptionBudget, er := r.PodDisruptionBudgetSpec() + podDisruptionBudgetConfig, err := r.PodDisruptionBudgetSpec() // If podDisruptionBudget config is nil or MinAvailable is nil, ignore PodDisruptionBudget. - if podDisruptionBudget == nil { - return nil, er + if podDisruptionBudgetConfig == nil { + return nil, err } - return &policyv1.PodDisruptionBudget{ + podDisruptionBudget := &policyv1.PodDisruptionBudget{ ObjectMeta: metav1.ObjectMeta{ Name: r.Name(), Namespace: r.Namespace, @@ -449,10 +449,17 @@ func (r *ResourceRender) PodDisruptionBudget() (*policyv1.PodDisruptionBudget, e Kind: "PodDisruptionBudget", }, Spec: policyv1.PodDisruptionBudgetSpec{ - MinAvailable: &intstr.IntOrString{IntVal: ptr.Deref(podDisruptionBudget.MinAvailable, 0)}, + MinAvailable: &intstr.IntOrString{IntVal: ptr.Deref(podDisruptionBudgetConfig.MinAvailable, 0)}, Selector: r.stableSelector(), }, - }, nil + } + + // apply merge patch to PodDisruptionBudget + if podDisruptionBudget, err = podDisruptionBudgetConfig.ApplyMergePatch(podDisruptionBudget); err != nil { + return nil, err + } + + return podDisruptionBudget, nil } // HorizontalPodAutoscalerSpec returns the `HorizontalPodAutoscaler` sets spec. @@ -506,6 +513,10 @@ func (r *ResourceRender) HorizontalPodAutoscaler() (*autoscalingv2.HorizontalPod hpa.Spec.ScaleTargetRef.Name = r.Name() } + if hpa, err = hpaConfig.ApplyMergePatch(hpa); err != nil { + return nil, err + } + return hpa, nil } diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go index ad286bfc930..0f5f6e3bf27 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go @@ -1298,6 +1298,32 @@ func TestPDB(t *testing.T) { MinAvailable: ptr.To(int32(1)), }, }, + { + caseName: "patch-json-pdb", + infra: newTestInfra(), + pdb: &egv1a1.KubernetesPodDisruptionBudgetSpec{ + MinAvailable: ptr.To(int32(1)), + Patch: &egv1a1.KubernetesPatchSpec{ + Type: ptr.To(egv1a1.JSONMerge), + Value: apiextensionsv1.JSON{ + Raw: []byte("{\"metadata\":{\"name\":\"foo\"}, \"spec\": {\"selector\": {\"matchLabels\": {\"app\": \"bar\"}}}}"), + }, + }, + }, + }, + { + caseName: "patch-strategic-pdb", + infra: newTestInfra(), + pdb: &egv1a1.KubernetesPodDisruptionBudgetSpec{ + MinAvailable: ptr.To(int32(1)), + Patch: &egv1a1.KubernetesPatchSpec{ + Type: ptr.To(egv1a1.StrategicMerge), + Value: apiextensionsv1.JSON{ + Raw: []byte("{\"metadata\":{\"name\":\"foo\"}, \"spec\": {\"selector\": {\"matchLabels\": {\"app\": \"bar\"}}}}"), + }, + }, + }, + }, } for _, tc := range cases { @@ -1375,6 +1401,32 @@ func TestHorizontalPodAutoscaler(t *testing.T) { }, }, }, + { + caseName: "patch-json-hpa", + infra: newTestInfra(), + hpa: &egv1a1.KubernetesHorizontalPodAutoscalerSpec{ + MaxReplicas: ptr.To[int32](1), + Patch: &egv1a1.KubernetesPatchSpec{ + Type: ptr.To(egv1a1.JSONMerge), + Value: apiextensionsv1.JSON{ + Raw: []byte("{\"metadata\":{\"name\":\"foo\"}, \"spec\": {\"scaleTargetRef\": {\"name\": \"bar\"}}}"), + }, + }, + }, + }, + { + caseName: "patch-strategic-hpa", + infra: newTestInfra(), + hpa: &egv1a1.KubernetesHorizontalPodAutoscalerSpec{ + MaxReplicas: ptr.To[int32](1), + Patch: &egv1a1.KubernetesPatchSpec{ + Type: ptr.To(egv1a1.StrategicMerge), + Value: apiextensionsv1.JSON{ + Raw: []byte("{\"metadata\":{\"name\":\"foo\"}, \"spec\": {\"metrics\": [{\"resource\": {\"name\": \"cpu\", \"target\": {\"averageUtilization\": 50, \"type\": \"Utilization\"}}, \"type\": \"Resource\"}]}}"), + }, + }, + }, + }, { caseName: "with-deployment-name", infra: newTestInfra(), diff --git a/internal/infrastructure/kubernetes/proxy/testdata/hpa/patch-json-hpa.yaml b/internal/infrastructure/kubernetes/proxy/testdata/hpa/patch-json-hpa.yaml new file mode 100644 index 00000000000..38d3d474d81 --- /dev/null +++ b/internal/infrastructure/kubernetes/proxy/testdata/hpa/patch-json-hpa.yaml @@ -0,0 +1,21 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: default + gateway.envoyproxy.io/owning-gateway-namespace: default + name: foo + namespace: envoy-gateway-system +spec: + metrics: + - resource: + name: cpu + target: + averageUtilization: 80 + type: Utilization + type: Resource + maxReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: bar diff --git a/internal/infrastructure/kubernetes/proxy/testdata/hpa/patch-strategic-hpa.yaml b/internal/infrastructure/kubernetes/proxy/testdata/hpa/patch-strategic-hpa.yaml new file mode 100644 index 00000000000..24a9f6f3a1d --- /dev/null +++ b/internal/infrastructure/kubernetes/proxy/testdata/hpa/patch-strategic-hpa.yaml @@ -0,0 +1,21 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: default + gateway.envoyproxy.io/owning-gateway-namespace: default + name: foo + namespace: envoy-gateway-system +spec: + metrics: + - resource: + name: cpu + target: + averageUtilization: 50 + type: Utilization + type: Resource + maxReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: envoy-default-37a8eec1 diff --git a/internal/infrastructure/kubernetes/proxy/testdata/pdb/patch-json-pdb.yaml b/internal/infrastructure/kubernetes/proxy/testdata/pdb/patch-json-pdb.yaml new file mode 100644 index 00000000000..cc4aa473337 --- /dev/null +++ b/internal/infrastructure/kubernetes/proxy/testdata/pdb/patch-json-pdb.yaml @@ -0,0 +1,15 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: foo + namespace: envoy-gateway-system +spec: + minAvailable: 1 + selector: + matchLabels: + app: bar + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: default + gateway.envoyproxy.io/owning-gateway-namespace: default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/pdb/patch-strategic-pdb.yaml b/internal/infrastructure/kubernetes/proxy/testdata/pdb/patch-strategic-pdb.yaml new file mode 100644 index 00000000000..20a25b7e1b0 --- /dev/null +++ b/internal/infrastructure/kubernetes/proxy/testdata/pdb/patch-strategic-pdb.yaml @@ -0,0 +1,10 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: foo + namespace: envoy-gateway-system +spec: + minAvailable: 1 + selector: + matchLabels: + app: bar diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 29e3b8fbdb6..4d61dd6b19f 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -16,6 +16,7 @@ security updates: | new features: | Added support for trusted CIDRs in the ClientIPDetectionSettings API Added support for sending attributes to external processor in EnvoyExtensionPolicy API + Added support for patching EnvoyProxy.spec.provider.kubernetes.envoyHpa and EnvoyProxy.spec.provider.kubernetes.envoyPDB # Fixes for bugs identified in previous versions. bug fixes: | diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index c6a7121d7ca..5119d756646 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -2529,6 +2529,7 @@ _Appears in:_ | `maxReplicas` | _integer_ | true | maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up.
It cannot be less that minReplicas. | | `metrics` | _[MetricSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#metricspec-v2-autoscaling) array_ | false | metrics contains the specifications for which to use to calculate the
desired replica count (the maximum replica count across all metrics will
be used).
If left empty, it defaults to being based on CPU utilization with average on 80% usage. | | `behavior` | _[HorizontalPodAutoscalerBehavior](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#horizontalpodautoscalerbehavior-v2-autoscaling)_ | false | behavior configures the scaling behavior of the target
in both Up and Down directions (scaleUp and scaleDown fields respectively).
If not set, the default HPAScalingRules for scale up and scale down are used.
See k8s.io.autoscaling.v2.HorizontalPodAutoScalerBehavior. | +| `patch` | _[KubernetesPatchSpec](#kubernetespatchspec)_ | false | Patch defines how to perform the patch operation to the HorizontalPodAutoscaler | #### KubernetesPatchSpec @@ -2542,6 +2543,8 @@ Note also that, currently, strings containing literal JSON are _rejected_. _Appears in:_ - [KubernetesDaemonSetSpec](#kubernetesdaemonsetspec) - [KubernetesDeploymentSpec](#kubernetesdeploymentspec) +- [KubernetesHorizontalPodAutoscalerSpec](#kuberneteshorizontalpodautoscalerspec) +- [KubernetesPodDisruptionBudgetSpec](#kubernetespoddisruptionbudgetspec) - [KubernetesServiceSpec](#kubernetesservicespec) | Field | Type | Required | Description | @@ -2562,6 +2565,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | | `minAvailable` | _integer_ | false | MinAvailable specifies the minimum number of pods that must be available at all times during voluntary disruptions,
such as node drains or updates. This setting ensures that your envoy proxy maintains a certain level of availability
and resilience during maintenance operations. | +| `patch` | _[KubernetesPatchSpec](#kubernetespatchspec)_ | false | Patch defines how to perform the patch operation to the PodDisruptionBudget | #### KubernetesPodSpec diff --git a/site/content/zh/latest/api/extension_types.md b/site/content/zh/latest/api/extension_types.md index c6a7121d7ca..5119d756646 100644 --- a/site/content/zh/latest/api/extension_types.md +++ b/site/content/zh/latest/api/extension_types.md @@ -2529,6 +2529,7 @@ _Appears in:_ | `maxReplicas` | _integer_ | true | maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up.
It cannot be less that minReplicas. | | `metrics` | _[MetricSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#metricspec-v2-autoscaling) array_ | false | metrics contains the specifications for which to use to calculate the
desired replica count (the maximum replica count across all metrics will
be used).
If left empty, it defaults to being based on CPU utilization with average on 80% usage. | | `behavior` | _[HorizontalPodAutoscalerBehavior](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#horizontalpodautoscalerbehavior-v2-autoscaling)_ | false | behavior configures the scaling behavior of the target
in both Up and Down directions (scaleUp and scaleDown fields respectively).
If not set, the default HPAScalingRules for scale up and scale down are used.
See k8s.io.autoscaling.v2.HorizontalPodAutoScalerBehavior. | +| `patch` | _[KubernetesPatchSpec](#kubernetespatchspec)_ | false | Patch defines how to perform the patch operation to the HorizontalPodAutoscaler | #### KubernetesPatchSpec @@ -2542,6 +2543,8 @@ Note also that, currently, strings containing literal JSON are _rejected_. _Appears in:_ - [KubernetesDaemonSetSpec](#kubernetesdaemonsetspec) - [KubernetesDeploymentSpec](#kubernetesdeploymentspec) +- [KubernetesHorizontalPodAutoscalerSpec](#kuberneteshorizontalpodautoscalerspec) +- [KubernetesPodDisruptionBudgetSpec](#kubernetespoddisruptionbudgetspec) - [KubernetesServiceSpec](#kubernetesservicespec) | Field | Type | Required | Description | @@ -2562,6 +2565,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | | `minAvailable` | _integer_ | false | MinAvailable specifies the minimum number of pods that must be available at all times during voluntary disruptions,
such as node drains or updates. This setting ensures that your envoy proxy maintains a certain level of availability
and resilience during maintenance operations. | +| `patch` | _[KubernetesPatchSpec](#kubernetespatchspec)_ | false | Patch defines how to perform the patch operation to the PodDisruptionBudget | #### KubernetesPodSpec