Help us improve the AWS re:Post Knowledge Center by sharing your feedback in a brief survey. Your input can influence how we create and update our content to better support your AWS journey.
Implementing Kubernetes Gateway API in EKS Auto Mode using Envoy Gateway
Currently EKS Auto Mode build-in load balancer controller does not support Kubernetes (K8s) Gateway API. This article shows a way to implement it using Envoy Gateway, a popular open-source software.
Background
The open-source AWS Load Balancer Controller supports Gateway API in GA (General Available) since v3.0.0, but EKS Auto Mode built-in load balancer controller does not yet support Gateway API.
This fact and customer demand for implementing a Gateway API implementation of their choice, for example Envoy Gateway, led to the creation of this article.
Prerequisites
We use the EKS Auto Mode build-in system nodepool for system-critical components like Envoy Gateway. To use it , Envoy Gateway needs to tolerate the key CriticalAddonsOnly.
Envoy Gateway installation
The following Helm values.yaml file is used for customisation of the installation:
deployment: priorityClassName: system-cluster-critical replicas: 2 pod: tolerations: - key: CriticalAddonsOnly operator: Exists nodeSelector: karpenter.sh/nodepool: system topologySpreadConstraints: - labelSelector: matchLabels: app.kubernetes.io/instance: eg app.kubernetes.io/name: gateway-helm control-plane: envoy-gateway maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule
Installation is done using the Helm chart as follows:
$ helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.7.0 -n envoy-gateway-system --create-namespace -f values-envoy-gw.yaml Pulled: docker.io/envoyproxy/gateway-helm:v1.7.0 Digest: sha256:019b2ed08c3514f41954251e7a80d3ef62ae0549209f637103f5fc1351c56518 NAME: eg LAST DEPLOYED: Thu Mar 12 13:37:54 2026 NAMESPACE: envoy-gateway-system STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: ************************************************************************** *** PLEASE BE PATIENT: Envoy Gateway may take a few minutes to install *** ************************************************************************** Envoy Gateway is an open source project for managing Envoy Proxy as a standalone or Kubernetes-based application gateway. Thank you for installing Envoy Gateway! 🎉 Your release is named: eg. 🎉 Your release is in namespace: envoy-gateway-system. 🎉 To learn more about the release, try: $ helm status eg -n envoy-gateway-system $ helm get all eg -n envoy-gateway-system To have a quickstart of Envoy Gateway, please refer to https://gateway.envoyproxy.io/latest/tasks/quickstart. To get more details, please visit https://gateway.envoyproxy.io and https://github.com/envoyproxy/gateway.
which creates a namespace envoy-gateway-system with some resources:
$ k get ns envoy-gateway-system NAME STATUS AGE envoy-gateway-system Active 93s $ k get all -n envoy-gateway-system NAME READY STATUS RESTARTS AGE pod/envoy-gateway-6f78c85894-298wz 1/1 Running 0 71s pod/envoy-gateway-6f78c85894-r7zbr 1/1 Running 0 71s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/envoy-gateway ClusterIP 10.100.123.73 <none> 18000/TCP,18001/TCP,18002/TCP,19001/TCP,9443/TCP 72s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/envoy-gateway 2/2 2 2 73s NAME DESIRED CURRENT READY AGE replicaset.apps/envoy-gateway-6f78c85894 2 2 2 73s
Envoy Gateway Quickstart
By default Envoy Gateway provisions an AWS Classic Load Balancer (CLB) using the deprecated K8s in-tree code.
I use a slightly modified version of Envoy Gateway doc Quickstart, which will provision a Network Load Balancer (NLB) instead of a CLB. To implement this, the envoyService will utilise common NLB service Annotations.
This follows a common pattern of using an NLB in front of a L7 routing entity and was already used a lot for NGINX Ingress Controller by utilising respective NLB annotations on the service.
quickstart.yaml
apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: eg spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller --- apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyProxy metadata: name: custom-proxy-config namespace: default spec: provider: type: Kubernetes kubernetes: envoyDeployment: replicas: 2 envoyService: annotations: service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip service.beta.kubernetes.io/aws-load-balancer-name: envoy-gw-nlb --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: eg spec: infrastructure: parametersRef: group: gateway.envoyproxy.io kind: EnvoyProxy name: custom-proxy-config gatewayClassName: eg listeners: - name: http protocol: HTTP port: 80 --- apiVersion: v1 kind: ServiceAccount metadata: name: backend --- apiVersion: v1 kind: Service metadata: name: backend labels: app: backend service: backend spec: ports: - name: http port: 3000 targetPort: 3000 selector: app: backend --- apiVersion: apps/v1 kind: Deployment metadata: name: backend spec: replicas: 1 selector: matchLabels: app: backend version: v1 template: metadata: labels: app: backend version: v1 spec: serviceAccountName: backend containers: - image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e imagePullPolicy: IfNotPresent name: backend ports: - containerPort: 3000 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: backend spec: parentRefs: - name: eg hostnames: - "envoy-auto-mode.<redacted>" rules: - backendRefs: - group: "" kind: Service name: backend port: 3000 weight: 1 matches: - path: type: PathPrefix value: /
The important part here is the custom EnvoyProxy resource. It contains the service annotations to tell the Auto Mode build-in load balancer to create an NLB pointing to the Gateway service. This custom EnvoyProxy is referenced in the Gateway resource in spec.infrastructure.parametersRef section.
apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyProxy metadata: name: custom-proxy-config namespace: default spec: provider: type: Kubernetes kubernetes: envoyDeployment: replicas: 2 envoyService: annotations: service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip service.beta.kubernetes.io/aws-load-balancer-name: envoy-gw-nlb --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: eg spec: infrastructure: parametersRef: group: gateway.envoyproxy.io kind: EnvoyProxy name: custom-proxy-config ...
and apply it to the cluster:
$ kubectl apply -f quickstart.yaml gatewayclass.gateway.networking.k8s.io/eg created envoyproxy.gateway.envoyproxy.io/custom-proxy-config created gateway.gateway.networking.k8s.io/eg created serviceaccount/backend created service/backend created deployment.apps/backend created httproute.gateway.networking.k8s.io/backend created
The Gateway resource will now provision an internet-facing NLB with a listener on port 80 in IP target mode pointing to the two replicas of the service:
$ kubectl get gtw NAME CLASS ADDRESS PROGRAMMED AGE eg eg envoy-gw-nlb-<redacted>.elb.eu-west-1.amazonaws.com False 5s $ kubectl get svc -n envoy-gateway-system NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE envoy-default-eg-e41e7b31 LoadBalancer <redacted> envoy-gw-nlb-<redacted>.elb.eu-west-1.amazonaws.com 80:31962/TCP 21s envoy-gateway ClusterIP 10.100.123.73 <none> 18000/TCP,18001/TCP,18002/TCP,19001/TCP,9443/TCP 5m40s $ aws elbv2 describe-load-balancers --name envoy-gw-nlb { "LoadBalancers": [ { "LoadBalancerArn": "arn:aws:elasticloadbalancing:eu-west-1:<redacted>:loadbalancer/net/envoy-gw-nlb/<redacted>", "DNSName": "envoy-gw-nlb-<redacted>.elb.eu-west-1.amazonaws.com", "CanonicalHostedZoneId": "<redacted>", "CreatedTime": "2026-03-12T12:43:49.822000+00:00", "LoadBalancerName": "envoy-gw-nlb", "Scheme": "internet-facing", "VpcId": "vpc-<redacted>", "State": { "Code": "active" }, "Type": "network", $ kubectl get po -n envoy-gateway-system NAME READY STATUS RESTARTS AGE envoy-default-eg-e41e7b31-5f5b94b5df-kktxj 2/2 Running 0 72s envoy-default-eg-e41e7b31-5f5b94b5df-rvlp2 2/2 Running 0 72s
To match the spec.hostnames in HTTPRoute which I defined as "envoy-auto-mode.<redacted>", I just need to create an Route53 alias record as "envoy-auto-mode.<redacted>" (replace <redacted> with your public DNS zone) pointing to "envoy-gw-nlb-<redacted>.elb.eu-west-1.amazonaws.com" and test , that everything is working:
$ dig +short envoy-auto-mode.<redacted> <first internet-facing NLB IP> <second internet-facing NLB IP> $ curl -s -o /dev/null -w "%{http_code}\n" envoy-auto-mode.<redacted> 200 $ curl -v envoy-auto-mode.<redacted> * Host envoy-auto-mode.<redacted>:80 was resolved. * IPv6: (none) * IPv4: <first internet-facing NLB IP>, <second internet-facing NLB IP> * Trying <first internet-facing NLB IP>... * Connected to envoy-auto-mode.<redacted> (<first internet-facing NLB IP>) port 80 > GET / HTTP/1.1 > Host: envoy-auto-mode.<redacted> > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/1.1 200 OK < content-type: application/json < x-content-type-options: nosniff < date: Thu, 12 Mar 2026 13:55:15 GMT < content-length: 491 < { "path": "/", "host": "envoy-auto-mode.<redacted>", "method": "GET", "proto": "HTTP/1.1", "headers": { "Accept": [ "*/*" ], "User-Agent": [ "curl/8.7.1" ], "X-Envoy-External-Address": [ "192.168.13.66" ], "X-Forwarded-For": [ "192.168.13.66" ], "X-Forwarded-Proto": [ "http" ], "X-Request-Id": [ "a10a3db4-5f97-430a-8ecc-9fcf0ba3b6e3" ] }, "namespace": "default", "ingress": "", "service": "", "pod": "backend-86c6c76f-nvnth" * Connection #0 to host envoy-auto-mode.<redacted> left intact $ kubectl get po backend-86c6c76f-nvnth -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES backend-86c6c76f-nvnth 1/1 Running 0 74m 192.168.189.50 i-0ad29068d6848bc07 <none> <none>
The curl output shows, that the backend pod is used to serve the request.
Considerations for standard EKS cluster
This setup can be applied to a standard EKS cluster as well for customers which want to use a custom K8s Gateway API implementation.
The AWS Load Balancer Controller needs to be installed, but is it not used for K8s Gateway API implementation, but rather to provision the NLB which points to the Envoy Gateway service. The custom EnvoyProxy needs an additional annotation service.beta.kubernetes.io/aws-load-balancer-type: external:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
name: custom-proxy-config
namespace: default
spec:
provider:
type: Kubernetes
kubernetes:
envoyService:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-name: envoy-gw-nlb
All other steps follow the implementation steps from above.
- Topics
- Containers
- Language
- English
Relevant content
- Accepted Answerasked 10 months ago
- asked 6 months ago
- asked a year ago
