Last modified October 26, 2026
Installing Gateway API with Envoy Gateway
Prerequisites
Before setting up Gateway API, ensure you have:
- A Giant Swarm workload cluster
kubectlconfigured to access your workload cluster- Access to the Giant Swarm platform API for app installation
- Basic understanding of Kubernetes networking concepts
- On AWS based clusters,
aws-load-balancer-controlleris required for configuring AWS Network Load Balancers.
Installation
Gateway API support is provided through three apps that work together. You can install them individually or use the Gateway API Bundle for simplified deployment. Our recommendation is to use the Gateway API Bundle, which installs all required components (Gateway API CRDs, Envoy Gateway and a preconfigured default Gateway):
Note: The Gateway API Bundle currently ships as an App custom resource, which is being phased out in favor of Flux HelmRelease. The bundle itself doesn’t yet have a HelmRelease equivalent because it deploys nested App resources internally. For application deployments outside the bundle, use HelmRelease.
apiVersion: v1
kind: ConfigMap
metadata:
name: <CLUSTER_NAME>-gateway-api-bundle
namespace: org-<ORGANIZATION>
data:
values: |
clusterID: <CLUSTER_NAME>
organization: <ORGANIZATION>
---
apiVersion: application.giantswarm.io/v1alpha1
kind: App
metadata:
labels:
app-operator.giantswarm.io/version: 0.0.0
name: <CLUSTER_NAME>-gateway-api-bundle
namespace: org-<ORGANIZATION>
spec:
catalog: giantswarm
kubeConfig:
inCluster: true
name: gateway-api-bundle
namespace: org-<ORGANIZATION>
userConfig:
configMap:
name: <CLUSTER_NAME>-gateway-api-bundle
namespace: org-<ORGANIZATION>
version: 2.0.0
Run the kubectl apply -f <bundle-config.yaml> command on your management cluster to install the bundle, then wait until the child apps are deployed (CRDs, Envoy Gateway, and Gateway default config).
Configuration
Certificate management with cert-manager
In order to obtain TLS certificates for your domains served by Gateway API, cert-manager needs to be configured to handle Gateway resources.
In your clusters user ConfigMap, add the following section. Do this only after Gateway API CRDs are installed.
global:
apps:
certManager:
values:
config:
apiVersion: controller.config.cert-manager.io/v1alpha1
enableGatewayAPI: true
kind: ControllerConfiguration
Enabling ListenerSet Support
A ListenerSet lets you add listeners to an existing Gateway from your own namespace, for example to serve your own domain with its own certificate, without changing the shared giantswarm-default Gateway. cert-manager can issue certificates for ListenerSets the same way it does for Gateways, but this support is an alpha feature that’s turned off by default.
ListenerSet support needs cert-manager v1.20 or newer, which ships with cluster release v35.0.0 and newer. These releases include cert-manager-app v4, which expects the upstream chart values under the cert-manager key.
To turn it on, extend the cert-manager configuration in your cluster’s user ConfigMap:
global:
apps:
certManager:
values:
cert-manager:
config:
apiVersion: controller.config.cert-manager.io/v1alpha1
kind: ControllerConfiguration
enableGatewayAPI: true
enableGatewayAPIListenerSet: true
featureGates:
ListenerSets: true
All three settings are required:
enableGatewayAPIturns on the Gateway API integration that ListenerSet support builds on.enableGatewayAPIListenerSetturns on the cert-manager controller that watches ListenerSets.featureGates.ListenerSetsenables the alpha feature gate. Without it, cert-manager ignoresenableGatewayAPIListenerSet.
cert-manager checks for the ListenerSet CRD when it starts and fails to start if the CRD is missing. The Gateway API Bundle installs the CRD, so apply this configuration only after the bundle is deployed. If you install the CRDs later, restart the cert-manager controller.
ListenerSets created in your own namespaces can only attach to a Gateway that allows them. Open the default Gateway to ListenerSets in the bundle configuration:
apiVersion: v1
kind: ConfigMap
metadata:
name: <CLUSTER_NAME>-gateway-api-bundle
namespace: org-<ORGANIZATION>
data:
values: |
clusterID: <CLUSTER_NAME>
organization: <ORGANIZATION>
apps:
gatewayApiConfig:
userConfig:
configMap:
values: |
gateways:
default:
allowedListeners:
enabled: true
namespaces:
from: All
Use from: Selector with a selector to limit which namespaces may attach ListenerSets.
With both changes in place, annotate a ListenerSet with an issuer and reference a Secret in each HTTPS listener. cert-manager creates a Certificate for every listener’s hostname and stores it in that Secret:
apiVersion: gateway.networking.k8s.io/v1
kind: ListenerSet
metadata:
name: my-listeners
namespace: my-namespace
annotations:
cert-manager.io/cluster-issuer: letsencrypt-giantswarm
spec:
parentRef:
group: gateway.networking.k8s.io
kind: Gateway
name: giantswarm-default
namespace: envoy-gateway-system
listeners:
- name: https
hostname: myapp.example.com
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: myapp-example-com-tls
allowedRoutes:
namespaces:
from: Same
If the ListenerSet has no issuer annotation, it inherits the one from its parent Gateway. Check that the ListenerSet is attached with kubectl get listenerset -n my-namespace my-listeners, and that the certificate was issued with kubectl get certificate -n my-namespace.
DNS records for ListenerSet hostnames
cert-manager doesn’t create DNS records. external-dns can create them, but neither ListenerSets nor HTTPRoutes are among the sources it watches by default. It only creates records for Services and for the DNSEndpoint resources that the Gateway API Bundle generates.
ListenerSet support needs external-dns-app v3.5.0 or newer, which ships with cluster release v35.0.0 and newer.
To turn it on, add the following section to your cluster’s user ConfigMap:
global:
apps:
externalDns:
values:
sources:
- service
- crd
- gateway-httproute
enableGatewayListenerSets: true
Both settings are required:
sourcesaddsgateway-httproute, so external-dns creates a record for every HTTPRoute hostname. Keepserviceandcrdin the list, because the list replaces the default instead of extending it.enableGatewayListenerSetslets external-dns follow an HTTPRoute that’s attached to a ListenerSet up to its parent Gateway, and use the Gateway’s load balancer address for the record.
external-dns creates the record from the HTTPRoute, not from the ListenerSet. A hostname only gets a record once an HTTPRoute attaches to the ListenerSet listener. The managed external-dns only creates records for subdomains of your cluster’s base domain. For other domains, run your own external-dns instance.
Using the default gateway
When you install the Gateway API Bundle, it automatically creates a default GatewayClass called giantswarm-default managed by the Envoy Gateway controller. You can verify it exists on your workload cluster:
kubectl get gatewayclass
Expected output:
NAME CONTROLLER ACCEPTED
giantswarm-default gateway.envoyproxy.io/gatewayclass-controller True
Additionally, it also creates the giantswarm-default Gateway ready to use in the cluster:
NAME CLASS ADDRESS PROGRAMMED
giantswarm-default giantswarm-default axx8.eu-west-2.elb.amazonaws.com True
Note: Since DNS and TLS certificates in the Gateway API world are different from Ingress, you must configure which domains the Gateway accepts.
The default Gateway is configured to accept traffic for your cluster’s base domain (*.CLUSTER_ID.k8s.gigantic.io) and is ready to attach any HTTPRoute resources matching that pattern. However, no TLS certificates or DNS records are automatically created based on the HTTPRoute resources.
Option 1: Adding each subdomain
You can define a list of subdomains inside the HTTPS listener. This will automatically create a CNAME (Canonical Name) DNS record pointing to the Gateway load balancer and add it to the dnsNames list in the certificate.
apiVersion: v1
kind: ConfigMap
metadata:
name: <CLUSTER_NAME>-gateway-api-bundle
namespace: org-<ORGANIZATION>
data:
values: |
clusterID: <CLUSTER_NAME>
organization: <ORGANIZATION>
apps:
gatewayApiConfig:
userConfig:
configMap:
values: |
gateways:
default:
listeners:
https:
subdomains:
- myapplication
Note: The base domain is appended to each subdomain to compose the FQDN (Fully Qualified Domain Name), myapplication.CLUSTER_ID.k8s.gigantic.io in our example.
Option 2: Overriding the base domain
It’s possible to completely override the base domain and make the Gateway only accept a domain that is not the cluster domain.
apiVersion: v1
kind: ConfigMap
metadata:
name: <CLUSTER_NAME>-gateway-api-bundle
namespace: org-<ORGANIZATION>
data:
values: |
clusterID: <CLUSTER_NAME>
organization: <ORGANIZATION>
apps:
gatewayApiConfig:
userConfig:
configMap:
values: |
gateways:
default:
overrideBaseDomain: "example.com"
listeners:
https:
subdomains:
- myapplication
This results in a Gateway with an HTTPS listener that accepts traffic for myapplication.example.com with a TLS certificate attached.
Option 3: Adding a new listener
In this example, you observe how to add a new listener for the base domain example.com. In this case, TLS certificates are created by cert-manager, but external-dns integration is disabled.
apiVersion: v1
kind: ConfigMap
metadata:
name: <CLUSTER_NAME>-gateway-api-bundle
namespace: org-<ORGANIZATION>
data:
values: |
clusterID: <CLUSTER_NAME>
organization: <ORGANIZATION>
apps:
gatewayApiConfig:
userConfig:
configMap:
values: |
gateways:
default:
listeners:
example-https:
name: example-https
protocol: HTTPS
hostname: '*.example.com'
port: 443
allowedRoutes:
namespaces:
from: All
tls:
mode: Terminate
certificateRefs: []
subdomains:
- myapplication
- docs
- otherapp
certificate:
enabled: true
dnsEndpoints:
enabled: false
You can add multiple listeners to the same Gateway to accept traffic from different domains. Each listener can have its own configuration for TLS certificates, DNS endpoints, and subdomain lists, allowing you to manage multiple domains within a single Gateway resource. The giantswarm-default gateway comes with 2 listeners enabled by default: one on port 80 (HTTP) and one on port 443 (HTTPS). Keep in mind that there is a limit of 64 listeners per Gateway, and each listener must have a unique combination of port, protocol, and hostname.
Load balancers on AWS
On CAPA (AWS) clusters, each Gateway gets an AWS Network Load Balancer (NLB) in front of its Envoy proxy pods. You tune the NLB with the service.beta.kubernetes.io/aws-load-balancer-* annotations of the AWS Load Balancer Controller, which you set in apps.gatewayApiConfig.userConfig.configMap.values.gateways.<name>.service.annotations of the gateway-api-bundle. The gateway-api-config chart adds a few defaults so that the proxy pods and the NLB stay in step.
Zone spread of the proxy pods
Starting with gateway-api-config v1.13.0, the chart prefers an even spread of the proxy pods of each Gateway across availability zones. It adds this topologySpreadConstraint to the Envoy proxy deployment:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
nodeTaintsPolicy: Honor
labelSelector:
matchLabels:
gateway.envoyproxy.io/owning-gateway-name: <GATEWAY_NAME>
gateway.envoyproxy.io/owning-gateway-namespace: <GATEWAY_NAMESPACE>
matchLabelKeys:
- pod-template-hash
The existing preferred anti-affinity, one proxy pod per node, stays in place. The default only applies on CAPA clusters with an NLB. With nodeTaintsPolicy: Honor, only zones with nodes that the proxy pods can run on count. Zones that only have control plane nodes don’t count. Clusters with worker nodes in a single zone need no change.
As whenUnsatisfiable is ScheduleAnyway, the spread is a preference. If a zone has no free node, the scheduler puts the pod in a different zone, so a scale-out never stops. If you need a strict spread, set your own list with DoNotSchedule.
Note: The first gateway-api-bundle upgrade that includes gateway-api-config v1.13.0 restarts all proxy pods.
You can change the default in three ways:
- Set
apps.gatewayApiConfig.userConfig.configMap.values.gatewayClasses.<name>.envoyProxy.envoyDeployment.pod.topologySpreadConstraintsto define the spread for all Gateways of a class. The chart then adds no default. - Set a non-empty list in
apps.gatewayApiConfig.userConfig.configMap.values.gateways.<name>.envoyProxy.envoyDeployment.pod.topologySpreadConstraintsto replace the default for one Gateway. - Set
apps.gatewayApiConfig.userConfig.configMap.values.gateways.<name>.provider.aws.zoneSpreadtofalseto remove the default for one Gateway.
Readiness gates in IP target mode
Starting with gateway-api-config v1.13.0, if a Gateway or GatewayClass on a CAPA cluster uses NLB IP target mode (service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip), the chart labels the envoy-gateway-system namespace with elbv2.k8s.aws/pod-readiness-gate-inject: enabled. New proxy pods then only become Ready once the NLB reports their target as healthy. If an update’s new pods aren’t ready, the NLB stops sending traffic to them, and the old pods keep serving traffic.
In instance target mode, the default, the chart doesn’t add the label.
IP target mode only works on clusters that run Cilium in ENI IPAM mode. In IP target mode, the NLB sends traffic straight to the pod IPs. By default, Cilium gives pods IPs from 100.64.0.0/12, which aren’t part of the VPC, so the NLB can’t register them as targets. The readiness gate then never turns true, and the new proxy pods never become Ready. You can only set the IPAM mode when you create the cluster. On clusters with the default IPAM mode, keep instance target mode.
Load balancer subnets
If you set the subnets of the NLB with service.beta.kubernetes.io/aws-load-balancer-subnets, the list must contain one subnet for each availability zone that can run proxy pods. The NLB marks targets in zones without a subnet as unused, and traffic to the Gateway times out.
Starting with gateway-api-config v1.13.0, the chart refuses to render if the list has an empty entry or if the value is empty. A trailing comma such as subnet-a, adds an empty entry. To unblock the upgrade, remove the empty entry or the annotation.
If the NLB must stay in fewer zones, pin the proxy pods to the same zones with a node affinity on topology.kubernetes.io/zone. The node affinity merges with the default anti-affinity. This example keeps the default Gateway in two zones:
apiVersion: v1
kind: ConfigMap
metadata:
name: <CLUSTER_NAME>-gateway-api-bundle
namespace: org-<ORGANIZATION>
data:
values: |
clusterID: <CLUSTER_NAME>
organization: <ORGANIZATION>
apps:
gatewayApiConfig:
userConfig:
configMap:
values: |
gateways:
default:
service:
annotations:
# One subnet per zone: eu-central-1a and eu-central-1b
service.beta.kubernetes.io/aws-load-balancer-subnets: subnet-0aaa1111,subnet-0bbb2222
envoyProxy:
envoyDeployment:
pod:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- eu-central-1a
- eu-central-1b
Keep the zones in the node affinity and the subnets in the annotation in sync. The zone spread from v1.13.0 then only spreads the proxy pods across these two zones.
Troubleshooting
Envoy Gateway provides the egctl command-line tool for diagnostics. To get an overview of the status of all Gateway API resources across all namespaces, run:
egctl x status all -A
This displays the status conditions (such as Accepted, Programmed, or ResolvedRefs) for GatewayClasses, Gateways, HTTPRoutes, and other Gateway API resources, making it easier to spot misconfigurations or issues.
Find more information about the command in the official documentation.
Proxy pods never become ready in IP target mode
If a Gateway uses NLB IP target mode and its new proxy pods never become ready, although their containers are ready, kubectl get pods -n envoy-gateway-system -o wide shows 0/1 in the READINESS GATES column. List the conditions of such a pod:
kubectl get pod -n envoy-gateway-system <POD_NAME> -o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'
If the target-health.elbv2.k8s.aws/... condition stays False or is missing, check which IPAM mode Cilium uses:
kubectl get configmap -n kube-system cilium-config -o jsonpath='{.data.ipam}'
If the command doesn’t print eni, the pod IPs aren’t part of the VPC and the NLB can’t register them. Switch the Gateway back to instance target mode, or use a cluster with Cilium ENI IPAM mode.
Need help, got feedback?
We listen to your Slack support channel. You can also reach us at support@giantswarm.io. And of course, we welcome your pull requests!