Gateway API: The better way to Ingress

I am a Kubernetes Engineer passionate about leveraging Cloud Native ecosystem to run secure, efficient and effective workloads.
Introduction
While all our existing workloads work well with Ingress and we don’t want to touch what is working just fine. Everyone have to migrate from Ingress to Gateway API at some point. I always felt that ingress is too rigid and complex with a lot of nesting YAML and bulky annotation section. Gateway API splits the responsibilities of different people in a team and dedicates a YAML component for them like “GatewayClass“, “Gateway“ and “HTTPRoute“ etc.
Why Ingress sucks?
Ingress is an initial solution to the problem where majority did not have a clarity on where the Kubernetes project is going to endup. It became GA in 1.18 which is very early into Kubernetes and also lacked lot of features natively. There are many other drawbacks with Ingress that we can discuss later
GatewayAPI
Gateway API is truly non opinionated and loosely coupled way of routing outside traffic to your services. Developer, Cluster Administrator and Infrastructure Engineer have their own tasks to do and YAMLs to manage. This enables a great support for multi-tenancy and portability from one vendor to other. Here is an example on how Envoy Gateway API can be tested.
First you need to helm install envoy gateway and CRDs
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.5.0 -n envoy-gateway-system --create-namespace
This deploys envoy gateway deployment with following config
➜ ~ k get cm envoy-gateway-config -n envoy-gateway-system -o yaml
apiVersion: v1
data:
envoy-gateway.yaml: |
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyGateway
extensionApis: {}
gateway:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
logging:
level:
default: info
provider:
kubernetes:
rateLimitDeployment:
container:
image: docker.io/envoyproxy/ratelimit:3e085e5b
patch:
type: StrategicMerge
value:
spec:
template:
spec:
containers:
- imagePullPolicy: IfNotPresent
name: envoy-ratelimit
shutdownManager:
image: docker.io/envoyproxy/gateway:v1.5.0
type: Kubernetes
kind: ConfigMap
This configuration essentially defines controllerName and images that needs to be used to for purposes of shutdown and rate limiting.
Following this, you need to install quickstart.yaml to get all the necessary components for the Gateway and application. Practically speaking, GatewayClass and Gateway will already be installed as bootstrap before applications get installed. Here is the GatewayClass and Gateway YAML files.
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: eg
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: eg
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
Here in the GatewayClass, the controllerName must match with the one we have provided to EnvoyGateway. In that way, you can use different controllers for different GatewayClasses in the same cluster. The gatewayClassName in Gateway must match with the GatewayClass.
Next, installing application related components. After installing the entire quickstart.yaml, it creates below components
➜ ~ kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/v1.5.0/quickstart.yaml -n default
gatewayclass.gateway.networking.k8s.io/eg 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
You can see that the Gateway has spun up new pod(deployment) and service in envoy namespace
➜ ~ k get pods,svc -n envoy-gateway-system
NAME READY STATUS RESTARTS AGE
pod/envoy-default-eg-e41e7b31-55bf69f99d-pl6dl 2/2 Running 0 2m37s
pod/envoy-gateway-667545bc7d-dpmmz 1/1 Running 0 45m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/envoy-default-eg-e41e7b31 LoadBalancer 10.96.222.26 172.18.0.7 80:30823/TCP 2m37s
service/envoy-gateway ClusterIP 10.96.174.248 <none> 18000/TCP,18001/TCP,18002/TCP,19001/TCP,9443/TCP 45m
In the default namespace
➜ ~ k get pods,svc -n default
NAME READY STATUS RESTARTS AGE
pod/backend-869c8646c5-vrrtl 1/1 Running 0 5m2s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend ClusterIP 10.96.220.59 <none> 3000/TCP 5m2s
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 48m
Here is the httpRoute for this backend app
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: backend
spec:
parentRefs:
- name: eg
hostnames:
- "www.example.com"
rules:
- backendRefs:
- group: ""
kind: Service
name: backend
port: 3000
weight: 1
matches:
- path:
type: PathPrefix
value: /
To test the traffic, we can port-forward the gateway service and curl it
➜ ~ curl --verbose --header "Host: www.example.com" http://localhost:8888/get
* Host localhost:8888 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8888...
* Connected to localhost (::1) port 8888
> GET /get HTTP/1.1
> Host: www.example.com
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
Handling connection for 8888
< HTTP/1.1 200 OK
< content-type: application/json
< x-content-type-options: nosniff
< date: Wed, 03 Sep 2025 15:34:15 GMT
< content-length: 467
<
So, in order to present a mental map on how the traffic is flowing, here is a diagram

Conclusion
GatewayAPI is maturing with more vendors offering it as a solution. There are good migration guides as well. GatewayAPI is a key step towards easier, superior and de-coupled networking solution.




