From Ingress to Gateway API
Express the same routing twice — as an Ingress and as a Gateway — and see what the newer model actually fixes.
- Time
- 50 min
- Level
- Intermediate
- Objectives
- 4 objectives
- Cost
- Free
Where this fits in the platform
This lab adds
- The same routing expressed in the API that replaces Ingress
Which lets you
—
Before you start
You will need
- kind or minikube
- kubectl 1.28+
- A Gateway API controller (Envoy Gateway or NGINX Gateway Fabric)
You do not need these already — the lab environment below provides them.
You will be able to
- Write a Gateway and an HTTPRoute for an existing Service
- Explain the role split Gateway API introduces
- Do a weighted traffic split without controller-specific annotations
Cost — Free
— kind or minikube with a Gateway controller.
Nothing to pay in the browser. Open the terminal runs this against a simulated cloud — the same API calls and the same commands, with no account and no bill. The figure above applies only if you build it in your own.
The scenario#
Ingress is stable, everywhere, and feature-frozen. Everything it cannot express — header matching, traffic splitting, timeouts — moved into vendor annotations that mean different things on different controllers.
Gateway API is the replacement. Learn Ingress first, because it is what existing clusters run; learn this, because new ones will not.
Hands-on environment
Run this lab in a real terminal, free and in your browser. The environment is temporary and yours alone — break it as much as you like.
Open the terminalOpens in Killercoda, in a new tab — keep this page open for the steps.
Run it on your own machine
Run this lab on your own machine. One command starts the environment, with everything the lab needs already installed:
You will need:
- docker
- kubectl
- kind
git clone https://github.com/EgyKode/EgyKode-lab.git
cd EgyKode-lab
./egykode start k8s
./egykode shellYou need Docker and Git installed. Everything else runs inside the environment. The first start downloads it and takes a few minutes; later starts are seconds.
Not sure what you already have? Run: npm run doctor — it checks and changes nothing.
Anything you tick here is your own record. EgyKode cannot see inside that terminal, so the success criteria stay self-assessed even when the environment checks your work for you.
Install a controller and the CRDs
Step 1 of 5
What you are proving: You can install Gateway API alongside a controller that implements it
This step settles no success criterion on its own.
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/standard-install.yaml
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.1.0 -n envoy-gateway-system --create-namespace
kubectl get gatewayclassGateway API ships as CRDs, not as part of core Kubernetes. Nothing works until both the CRDs and a controller implementing them are present.
What you are proving: You can serve the same application through an Ingress and through an HTTPRoute
Marking this settles success criterion 1.
Ingress — one object, one owner:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # controller-specific
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: web, port: { number: 80 } }Gateway API — two objects, two owners:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: platform
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All # which namespaces may attach routes
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web
spec:
parentRefs:
- name: platform
hostnames: ["app.example.com"]
rules:
- matches:
- path: { type: PathPrefix, value: / }
backendRefs:
- name: web
port: 80That split is the entire point. A platform team owns the Gateway — the
listeners, the certificates, which namespaces may attach. Application teams own
their HTTPRoute and can change their own routing without touching shared
infrastructure or asking anyone.
Under Ingress, both live in one object, so either everyone edits the shared routing or nobody can change their own.
What you are proving: You can split traffic by weight with no annotations involved
Marking this settles success criterion 2.
rules:
# Header matching — an annotation on every controller, if it exists at all
- matches:
- headers:
- name: x-canary
value: "true"
backendRefs:
- name: web-canary
port: 80
# A weighted split, in the spec itself
- backendRefs:
- name: web
port: 80
weight: 90
- name: web-canary
port: 80
weight: 10for i in $(seq 1 20); do curl -s -H "Host: app.example.com" http://<gw-ip>/; done | sort | uniq -cRoughly 90/10. Doing that with Ingress requires controller-specific annotations that do not port between NGINX, Traefik and ALB — which is exactly the fragmentation Gateway API exists to end.
What you are proving: You can name two things Gateway API expresses that Ingress cannot
Marking this settles success criterion 3.
| Ingress | Gateway API | |
|---|---|---|
| Objects | One | GatewayClass → Gateway → *Route |
| Ownership | Single team | Platform / application split |
| Header, method, query matching | Annotations | Typed fields |
| Traffic splitting | Annotations | weight |
| Protocols | HTTP(S) | HTTP, TCP, UDP, TLS, gRPC |
| Portability | Annotations differ per controller | Conformance-tested |
| Status | Frozen | Actively developed |
What you are proving: You can say who owns a Gateway and who owns an HTTPRoute, and why that split matters
Marking this settles success criterion 4.
Both can run at once, on different hostnames or different controllers. The sensible path is a new Gateway alongside the existing Ingress, one route moved at a time, with DNS as the switch.
kubectl get gateway platform -o jsonpath='{.status.conditions}' | jq
kubectl get httproute web -o jsonpath='{.status.parents}' | jqRead the status, not just the spec. An HTTPRoute whose parentRef is not
accepted reports Accepted: False with a reason — and unlike an Ingress that
silently does nothing, it tells you why.
no matches for kind Gateway
The CRDs are not installed. They ship separately from Kubernetes.
The Gateway has no address
No controller is watching that gatewayClassName, or it is waiting on a LoadBalancer. kubectl describe gateway shows the condition.
HTTPRoute exists but nothing routes
Check status.parents — the Gateway's allowedRoutes may not permit that namespace.
The weighted split looks wrong
Weights are statistical, not per-request, and keep-alive reuses connections. Send more requests without keep-alive.
Clean up#
Run this even if you did not finish.
Destructive — This removes real resources. Check which environment you are in first.
kubectl delete namespace <ns> --ignore-not-found
kubectl get all -A | grep -v kube-system
kubectl delete gateway,httproute --all -ACost of this lab: Free — kind or minikube with a Gateway controller.
Success criteria
0 of 4
The concept behind it
Next up
Lab 38 of 59 on the project path
Previous: Application Routing with K8s Ingress & AWS Load Balancer Controller