Last modified October 14, 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
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!