From adb0e10cd63e644121ae3a6030a127a06abb7796 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Fri, 24 Apr 2026 10:28:38 +0100 Subject: [PATCH 1/2] feat(api): add healthCheckNodePort to EnvoyProxy service spec Allows specifying a fixed health-check NodePort on the Envoy Service when type is LoadBalancer and externalTrafficPolicy is Local. Without this, kube-apiserver auto-allocates the port from the cluster NodePort range, making it hard to allowlist in network policies or firewall rules. A CEL validation rule restricts the field to type: LoadBalancer with externalTrafficPolicy: Local, matching Kubernetes' own rules. When unset, behavior is unchanged and Kubernetes continues to auto-allocate. Fixes #8842 Signed-off-by: Seth Malaki --- api/v1alpha1/shared_types.go | 14 ++++++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 5 +++++ .../gateway.envoyproxy.io_envoyproxies.yaml | 18 ++++++++++++++++++ .../gateway.envoyproxy.io_envoyproxies.yaml | 18 ++++++++++++++++++ .../kubernetes/resource/resource.go | 3 +++ .../kubernetes/resource/resource_test.go | 13 +++++++++++++ release-notes/current.yaml | 1 + site/content/en/latest/api/extension_types.md | 1 + test/helm/gateway-crds-helm/all.out.yaml | 18 ++++++++++++++++++ test/helm/gateway-crds-helm/e2e.out.yaml | 18 ++++++++++++++++++ .../envoy-gateway-crds.out.yaml | 18 ++++++++++++++++++ 11 files changed, 127 insertions(+) diff --git a/api/v1alpha1/shared_types.go b/api/v1alpha1/shared_types.go index 5450cb4913..aa3de0d74a 100644 --- a/api/v1alpha1/shared_types.go +++ b/api/v1alpha1/shared_types.go @@ -320,6 +320,7 @@ const ( // +kubebuilder:validation:XValidation:message="allocateLoadBalancerNodePorts can only be set for LoadBalancer type",rule="!has(self.allocateLoadBalancerNodePorts) || self.type == 'LoadBalancer'" // +kubebuilder:validation:XValidation:message="loadBalancerSourceRanges can only be set for LoadBalancer type",rule="!has(self.loadBalancerSourceRanges) || self.type == 'LoadBalancer'" // +kubebuilder:validation:XValidation:message="loadBalancerIP can only be set for LoadBalancer type",rule="!has(self.loadBalancerIP) || self.type == 'LoadBalancer'" +// +kubebuilder:validation:XValidation:message="healthCheckNodePort can only be set for LoadBalancer type with Local externalTrafficPolicy",rule="!has(self.healthCheckNodePort) || (self.type == 'LoadBalancer' && (!has(self.externalTrafficPolicy) || self.externalTrafficPolicy == 'Local'))" type KubernetesServiceSpec struct { // Annotations that should be appended to the service. // By default, no annotations are appended. @@ -379,6 +380,19 @@ type KubernetesServiceSpec struct { // +optional ExternalTrafficPolicy *ServiceExternalTrafficPolicy `json:"externalTrafficPolicy,omitempty"` + // HealthCheckNodePort specifies the healthcheck nodePort for the Envoy Service. + // This only applies when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local. + // If a value is specified, is in-range, and is not in use, it will be used. If not specified, + // a value will be automatically allocated by the Kubernetes API server. External systems + // (e.g. load-balancers) can use this port to determine if a given node holds endpoints for + // this service or not. Specifying the value allows network policies to allowlist a known port + // rather than a randomly allocated one. + // + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + // +optional + HealthCheckNodePort *int32 `json:"healthCheckNodePort,omitempty"` + // Patch defines how to perform the patch operation to the service // // +optional diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 22be0adbc7..6343750ddc 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5392,6 +5392,11 @@ func (in *KubernetesServiceSpec) DeepCopyInto(out *KubernetesServiceSpec) { *out = new(ServiceExternalTrafficPolicy) **out = **in } + if in.HealthCheckNodePort != nil { + in, out := &in.HealthCheckNodePort, &out.HealthCheckNodePort + *out = new(int32) + **out = **in + } if in.Patch != nil { in, out := &in.Patch, &out.Patch *out = new(KubernetesPatchSpec) diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml index 124e867bc2..3eebc73422 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -11124,6 +11124,19 @@ spec: - Local - Cluster type: string + healthCheckNodePort: + description: |- + HealthCheckNodePort specifies the healthcheck nodePort for the Envoy Service. + This only applies when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local. + If a value is specified, is in-range, and is not in use, it will be used. If not specified, + a value will be automatically allocated by the Kubernetes API server. External systems + (e.g. load-balancers) can use this port to determine if a given node holds endpoints for + this service or not. Specifying the value allows network policies to allowlist a known port + rather than a randomly allocated one. + format: int32 + maximum: 65535 + minimum: 1 + type: integer labels: additionalProperties: type: string @@ -11204,6 +11217,11 @@ spec: - message: loadBalancerIP can only be set for LoadBalancer type rule: '!has(self.loadBalancerIP) || self.type == ''LoadBalancer''' + - message: healthCheckNodePort can only be set for LoadBalancer + type with Local externalTrafficPolicy + rule: '!has(self.healthCheckNodePort) || (self.type == ''LoadBalancer'' + && (!has(self.externalTrafficPolicy) || self.externalTrafficPolicy + == ''Local''))' envoyServiceAccount: description: EnvoyServiceAccount defines the desired state of the Envoy service account resource. diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml index 833448d41c..53500b36ee 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -11123,6 +11123,19 @@ spec: - Local - Cluster type: string + healthCheckNodePort: + description: |- + HealthCheckNodePort specifies the healthcheck nodePort for the Envoy Service. + This only applies when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local. + If a value is specified, is in-range, and is not in use, it will be used. If not specified, + a value will be automatically allocated by the Kubernetes API server. External systems + (e.g. load-balancers) can use this port to determine if a given node holds endpoints for + this service or not. Specifying the value allows network policies to allowlist a known port + rather than a randomly allocated one. + format: int32 + maximum: 65535 + minimum: 1 + type: integer labels: additionalProperties: type: string @@ -11203,6 +11216,11 @@ spec: - message: loadBalancerIP can only be set for LoadBalancer type rule: '!has(self.loadBalancerIP) || self.type == ''LoadBalancer''' + - message: healthCheckNodePort can only be set for LoadBalancer + type with Local externalTrafficPolicy + rule: '!has(self.healthCheckNodePort) || (self.type == ''LoadBalancer'' + && (!has(self.externalTrafficPolicy) || self.externalTrafficPolicy + == ''Local''))' envoyServiceAccount: description: EnvoyServiceAccount defines the desired state of the Envoy service account resource. diff --git a/internal/infrastructure/kubernetes/resource/resource.go b/internal/infrastructure/kubernetes/resource/resource.go index 7e4c21e986..33883e8a58 100644 --- a/internal/infrastructure/kubernetes/resource/resource.go +++ b/internal/infrastructure/kubernetes/resource/resource.go @@ -42,6 +42,9 @@ func ExpectedServiceSpec(service *egv1a1.KubernetesServiceSpec) corev1.ServiceSp if service.LoadBalancerIP != nil { serviceSpec.LoadBalancerIP = *service.LoadBalancerIP } + if service.HealthCheckNodePort != nil { + serviceSpec.HealthCheckNodePort = *service.HealthCheckNodePort + } serviceSpec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicy(*service.ExternalTrafficPolicy) case egv1a1.ServiceTypeNodePort: serviceSpec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicy(*service.ExternalTrafficPolicy) diff --git a/internal/infrastructure/kubernetes/resource/resource_test.go b/internal/infrastructure/kubernetes/resource/resource_test.go index fe11f8ef0e..4c57862cd6 100644 --- a/internal/infrastructure/kubernetes/resource/resource_test.go +++ b/internal/infrastructure/kubernetes/resource/resource_test.go @@ -100,6 +100,19 @@ func TestExpectedServiceSpec(t *testing.T) { ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeLocal, }, }, + { + name: "LoadBalancerWithHealthCheckNodePort", + args: args{service: &egv1a1.KubernetesServiceSpec{ + Type: egv1a1.GetKubernetesServiceType(egv1a1.ServiceTypeLoadBalancer), + HealthCheckNodePort: new(int32(30123)), + }}, + want: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + HealthCheckNodePort: 30123, + SessionAffinity: corev1.ServiceAffinityNone, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeLocal, + }, + }, { name: "ClusterIP", args: args{service: &egv1a1.KubernetesServiceSpec{ diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 21e424ead4..c45abedd4e 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -46,6 +46,7 @@ new features: | Added support for OpenTelemetry sampler configuration for tracing. Added support for default EnvoyProxy settings on EnvoyGatewaySpec that can be overridden by GatewayClass or Gateway-level EnvoyProxy configurations. A new MergeType field allows choosing between Replace (default), StrategicMerge, or JSONMerge strategies for combining configurations. Added support for sending Envoy Gateway route metadata to external authorization backends via `SecurityPolicy.spec.extAuth.includeRouteMetadata`. + Added support for specifying `healthCheckNodePort` on the Envoy Service, for use when `type: LoadBalancer` and `externalTrafficPolicy: Local`. bug fixes: | Fixed local rate limit rules with identical sourceCIDR client selectors producing conflicting descriptors. diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index a04adfb42e..ece7ac1d5f 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -3667,6 +3667,7 @@ _Appears in:_ | `loadBalancerSourceRanges` | _string array_ | false | | LoadBalancerSourceRanges defines a list of allowed IP addresses which will be configured as
firewall rules on the platform providers load balancer. This is not guaranteed to be working as
it happens outside of kubernetes and has to be supported and handled by the platform provider.
This field may only be set for services with type LoadBalancer and will be cleared if the type
is changed to any other type. | | `loadBalancerIP` | _string_ | false | | LoadBalancerIP defines the IP Address of the underlying load balancer service. This field
may be ignored if the load balancer provider does not support this feature.
This field has been deprecated in Kubernetes, but it is still used for setting the IP Address in some cloud
providers such as GCP. | | `externalTrafficPolicy` | _[ServiceExternalTrafficPolicy](#serviceexternaltrafficpolicy)_ | false | Local | ExternalTrafficPolicy determines the externalTrafficPolicy for the Envoy Service. Valid options
are Local and Cluster. Default is "Local". "Local" means traffic will only go to pods on the node
receiving the traffic. "Cluster" means connections are loadbalanced to all pods in the cluster. | +| `healthCheckNodePort` | _integer_ | false | | HealthCheckNodePort specifies the healthcheck nodePort for the Envoy Service.
This only applies when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.
If a value is specified, is in-range, and is not in use, it will be used. If not specified,
a value will be automatically allocated by the Kubernetes API server. External systems
(e.g. load-balancers) can use this port to determine if a given node holds endpoints for
this service or not. Specifying the value allows network policies to allowlist a known port
rather than a randomly allocated one. | | `patch` | _[KubernetesPatchSpec](#kubernetespatchspec)_ | false | | Patch defines how to perform the patch operation to the service | | `name` | _string_ | false | | Name of the service.
When unset, this defaults to an autogenerated name. | diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 010be86793..953673c999 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -42007,6 +42007,19 @@ spec: - Local - Cluster type: string + healthCheckNodePort: + description: |- + HealthCheckNodePort specifies the healthcheck nodePort for the Envoy Service. + This only applies when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local. + If a value is specified, is in-range, and is not in use, it will be used. If not specified, + a value will be automatically allocated by the Kubernetes API server. External systems + (e.g. load-balancers) can use this port to determine if a given node holds endpoints for + this service or not. Specifying the value allows network policies to allowlist a known port + rather than a randomly allocated one. + format: int32 + maximum: 65535 + minimum: 1 + type: integer labels: additionalProperties: type: string @@ -42087,6 +42100,11 @@ spec: - message: loadBalancerIP can only be set for LoadBalancer type rule: '!has(self.loadBalancerIP) || self.type == ''LoadBalancer''' + - message: healthCheckNodePort can only be set for LoadBalancer + type with Local externalTrafficPolicy + rule: '!has(self.healthCheckNodePort) || (self.type == ''LoadBalancer'' + && (!has(self.externalTrafficPolicy) || self.externalTrafficPolicy + == ''Local''))' envoyServiceAccount: description: EnvoyServiceAccount defines the desired state of the Envoy service account resource. diff --git a/test/helm/gateway-crds-helm/e2e.out.yaml b/test/helm/gateway-crds-helm/e2e.out.yaml index 1e50eb9b5a..9e19c63b91 100644 --- a/test/helm/gateway-crds-helm/e2e.out.yaml +++ b/test/helm/gateway-crds-helm/e2e.out.yaml @@ -19980,6 +19980,19 @@ spec: - Local - Cluster type: string + healthCheckNodePort: + description: |- + HealthCheckNodePort specifies the healthcheck nodePort for the Envoy Service. + This only applies when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local. + If a value is specified, is in-range, and is not in use, it will be used. If not specified, + a value will be automatically allocated by the Kubernetes API server. External systems + (e.g. load-balancers) can use this port to determine if a given node holds endpoints for + this service or not. Specifying the value allows network policies to allowlist a known port + rather than a randomly allocated one. + format: int32 + maximum: 65535 + minimum: 1 + type: integer labels: additionalProperties: type: string @@ -20060,6 +20073,11 @@ spec: - message: loadBalancerIP can only be set for LoadBalancer type rule: '!has(self.loadBalancerIP) || self.type == ''LoadBalancer''' + - message: healthCheckNodePort can only be set for LoadBalancer + type with Local externalTrafficPolicy + rule: '!has(self.healthCheckNodePort) || (self.type == ''LoadBalancer'' + && (!has(self.externalTrafficPolicy) || self.externalTrafficPolicy + == ''Local''))' envoyServiceAccount: description: EnvoyServiceAccount defines the desired state of the Envoy service account resource. diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index 12abf42660..89078b4561 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -19980,6 +19980,19 @@ spec: - Local - Cluster type: string + healthCheckNodePort: + description: |- + HealthCheckNodePort specifies the healthcheck nodePort for the Envoy Service. + This only applies when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local. + If a value is specified, is in-range, and is not in use, it will be used. If not specified, + a value will be automatically allocated by the Kubernetes API server. External systems + (e.g. load-balancers) can use this port to determine if a given node holds endpoints for + this service or not. Specifying the value allows network policies to allowlist a known port + rather than a randomly allocated one. + format: int32 + maximum: 65535 + minimum: 1 + type: integer labels: additionalProperties: type: string @@ -20060,6 +20073,11 @@ spec: - message: loadBalancerIP can only be set for LoadBalancer type rule: '!has(self.loadBalancerIP) || self.type == ''LoadBalancer''' + - message: healthCheckNodePort can only be set for LoadBalancer + type with Local externalTrafficPolicy + rule: '!has(self.healthCheckNodePort) || (self.type == ''LoadBalancer'' + && (!has(self.externalTrafficPolicy) || self.externalTrafficPolicy + == ''Local''))' envoyServiceAccount: description: EnvoyServiceAccount defines the desired state of the Envoy service account resource. From dba93d8794d364793119ebcf30c02e72253641ce Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Fri, 24 Apr 2026 10:45:13 +0100 Subject: [PATCH 2/2] test(api): add Go-level validation and CEL tests for healthCheckNodePort Matches the pattern established for loadBalancerIP and loadBalancerSourceRanges: a Go-level check in validateService to catch invalid combinations in offline/CLI paths, unit tests for that check, and CEL admission tests covering both the pass (LoadBalancer with default or explicit Local externalTrafficPolicy) and fail (ClusterIP, or LoadBalancer with Cluster externalTrafficPolicy) cases. Signed-off-by: Seth Malaki --- .../validation/envoyproxy_validate.go | 8 +++ .../validation/envoyproxy_validate_test.go | 64 +++++++++++++++++ test/cel-validation/envoyproxy_test.go | 71 +++++++++++++++++++ 3 files changed, 143 insertions(+) diff --git a/api/v1alpha1/validation/envoyproxy_validate.go b/api/v1alpha1/validation/envoyproxy_validate.go index 7a17d1af95..6df80cd278 100644 --- a/api/v1alpha1/validation/envoyproxy_validate.go +++ b/api/v1alpha1/validation/envoyproxy_validate.go @@ -180,6 +180,14 @@ func validateService(spec *egv1a1.EnvoyProxySpec) []error { errs = append(errs, fmt.Errorf("loadBalancerIP:%s is an invalid IP address", *serviceLoadBalancerIP)) } } + if serviceType, serviceHealthCheckNodePort := spec.Provider.Kubernetes.EnvoyService.Type, spec.Provider.Kubernetes.EnvoyService.HealthCheckNodePort; serviceType != nil && serviceHealthCheckNodePort != nil { + if *serviceType != egv1a1.ServiceTypeLoadBalancer { + errs = append(errs, fmt.Errorf("healthCheckNodePort can only be set for %v type", egv1a1.ServiceTypeLoadBalancer)) + } + if etp := spec.Provider.Kubernetes.EnvoyService.ExternalTrafficPolicy; etp != nil && *etp != egv1a1.ServiceExternalTrafficPolicyLocal { + errs = append(errs, fmt.Errorf("healthCheckNodePort can only be set when externalTrafficPolicy is %v", egv1a1.ServiceExternalTrafficPolicyLocal)) + } + } if patch := spec.Provider.Kubernetes.EnvoyService.Patch; patch != nil { if patch.Value.Raw == nil { errs = append(errs, fmt.Errorf("envoy service patch object cannot be empty")) diff --git a/api/v1alpha1/validation/envoyproxy_validate_test.go b/api/v1alpha1/validation/envoyproxy_validate_test.go index b498edad17..b3f57e2e87 100644 --- a/api/v1alpha1/validation/envoyproxy_validate_test.go +++ b/api/v1alpha1/validation/envoyproxy_validate_test.go @@ -335,6 +335,70 @@ func TestValidateEnvoyProxy(t *testing.T) { }, expected: false, }, + { + name: "envoy service type 'LoadBalancer' with healthCheckNodePort", + proxy: &egv1a1.EnvoyProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.EnvoyProxyProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyService: &egv1a1.KubernetesServiceSpec{ + Type: egv1a1.GetKubernetesServiceType(egv1a1.ServiceTypeLoadBalancer), + HealthCheckNodePort: new(int32(30123)), + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "non envoy service type 'LoadBalancer' with healthCheckNodePort", + proxy: &egv1a1.EnvoyProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.EnvoyProxyProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyService: &egv1a1.KubernetesServiceSpec{ + Type: egv1a1.GetKubernetesServiceType(egv1a1.ServiceTypeClusterIP), + HealthCheckNodePort: new(int32(30123)), + }, + }, + }, + }, + }, + expected: false, + }, + { + name: "envoy service type 'LoadBalancer' with Cluster externalTrafficPolicy and healthCheckNodePort", + proxy: &egv1a1.EnvoyProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + }, + Spec: egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.EnvoyProxyProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyService: &egv1a1.KubernetesServiceSpec{ + Type: egv1a1.GetKubernetesServiceType(egv1a1.ServiceTypeLoadBalancer), + ExternalTrafficPolicy: egv1a1.GetKubernetesServiceExternalTrafficPolicy(egv1a1.ServiceExternalTrafficPolicyCluster), + HealthCheckNodePort: new(int32(30123)), + }, + }, + }, + }, + }, + expected: false, + }, { name: "should invalid when accesslog enabled using Text format, but `text` field being empty", proxy: &egv1a1.EnvoyProxy{ diff --git a/test/cel-validation/envoyproxy_test.go b/test/cel-validation/envoyproxy_test.go index b5569ab4a6..6d134b1bc1 100644 --- a/test/cel-validation/envoyproxy_test.go +++ b/test/cel-validation/envoyproxy_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" @@ -171,6 +172,76 @@ func TestEnvoyProxyProvider(t *testing.T) { }, wantErrors: []string{"loadBalancerSourceRanges can only be set for LoadBalancer type"}, }, + { + desc: "healthCheckNodePort-pass-with-default-trafficPolicy", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.EnvoyProxyProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyService: &egv1a1.KubernetesServiceSpec{ + Type: new(egv1a1.ServiceTypeLoadBalancer), + HealthCheckNodePort: ptr.To[int32](30123), + }, + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "healthCheckNodePort-pass-with-explicit-Local", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.EnvoyProxyProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyService: &egv1a1.KubernetesServiceSpec{ + Type: new(egv1a1.ServiceTypeLoadBalancer), + ExternalTrafficPolicy: ptr.To(egv1a1.ServiceExternalTrafficPolicyLocal), + HealthCheckNodePort: ptr.To[int32](30123), + }, + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "healthCheckNodePort-fail-non-LoadBalancer", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.EnvoyProxyProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyService: &egv1a1.KubernetesServiceSpec{ + Type: new(egv1a1.ServiceTypeClusterIP), + HealthCheckNodePort: ptr.To[int32](30123), + }, + }, + }, + } + }, + wantErrors: []string{"healthCheckNodePort can only be set for LoadBalancer type with Local externalTrafficPolicy"}, + }, + { + desc: "healthCheckNodePort-fail-Cluster-trafficPolicy", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + Provider: &egv1a1.EnvoyProxyProvider{ + Type: egv1a1.EnvoyProxyProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{ + EnvoyService: &egv1a1.KubernetesServiceSpec{ + Type: new(egv1a1.ServiceTypeLoadBalancer), + ExternalTrafficPolicy: ptr.To(egv1a1.ServiceExternalTrafficPolicyCluster), + HealthCheckNodePort: ptr.To[int32](30123), + }, + }, + }, + } + }, + wantErrors: []string{"healthCheckNodePort can only be set for LoadBalancer type with Local externalTrafficPolicy"}, + }, { desc: "ServiceTypeLoadBalancer-with-valid-IP", mutate: func(envoy *egv1a1.EnvoyProxy) {