Ingress & Gateway API
Ingress routes external HTTP(S) traffic to Kubernetes Services, but the Ingress object does nothing until an Ingress controller is installed. The controller receives traffic, reads Ingress rules, terminates or passes TLS, and proxies to Services with healthy endpoints. Gateway API is the newer, more role-oriented model: platform teams own GatewayClass/Gateway and app teams attach HTTPRoutes.
Mental Model
A Service gives stable access inside the cluster. Ingress gives HTTP and HTTPS access from outside the cluster into those Services. It is Layer 7 routing: hostnames, paths, headers, TLS certificates, redirects, and controller-specific annotations.
The biggest beginner mistake is thinking an Ingress is itself a load balancer. It is only a routing rule. A real controller such as NGINX Ingress, Traefik, HAProxy, AWS Load Balancer Controller, Istio, or another implementation must watch that rule and configure real proxy/load-balancing infrastructure.
External HTTP(S) traffic flows through a controller before it reaches a Service.
Ingress Vs Gateway API
| Concept | Ingress | Gateway API |
|---|---|---|
| Controller | Ingress controller watches Ingress objects. | Gateway controller watches Gateway API objects. |
| Infrastructure owner | Often hidden in controller install, Service type, and annotations. | GatewayClass and Gateway make infra ownership explicit. |
| App owner | Creates Ingress rules. | Creates HTTPRoute objects that attach to a Gateway. |
| Portability | Basic routing is portable; advanced behavior is annotation-heavy. | More expressive standard for routing, attachment, status, and delegation. |
| Client reality | Still extremely common. | Growing quickly, especially in newer platform teams. |
Build From Scratch
This example assumes you already have a working web-api Service in namespace app. The Ingress maps api.example.com to that Service. In a local lab, replace the hostname with something you control in /etc/hosts, local DNS, or your cloud DNS zone.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-api
namespace: app
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-api
port:
number: 80kubectl get ingressclass
kubectl apply -f basic-ingress.yaml
kubectl get ingress web-api -n app -o wide
kubectl describe ingress web-api -n app
# Confirm the backend Service has endpoints before testing the Ingress.
kubectl get svc web-api -n app -o wide
kubectl get endpointslice -n app -l kubernetes.io/service-name=web-api -o wide
# Replace INGRESS_IP with the load balancer IP/DNS for your controller.
curl -v --resolve api.example.com:80:<INGRESS_IP> http://api.example.com/Host And Path Routing
Ingress can route several hostnames and paths through one controller. Keep routes boring where possible: exact hostnames, clear path prefixes, and one backend Service per app boundary.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: platform-routes
namespace: app
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: web-api
port:
number: 80
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-ui
port:
number: 80TLS And cert-manager
Ingress TLS usually terminates at the controller. The Ingress references a Kubernetes TLS Secret in the same namespace. cert-manager can create and renew that Secret from an ACME issuer such as Let's Encrypt or from an internal CA.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-api
namespace: app
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: api-example-com-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-api
port:
number: 80kubectl get certificate,certificaterequest,order,challenge -n app
kubectl describe certificate api-example-com-tls -n app
kubectl get secret api-example-com-tls -n app -o yaml
openssl s_client -connect api.example.com:443 -servername api.example.com -showcertsAnnotations And Controller Behavior
Ingress annotations are not portable Kubernetes behavior; they are controller-specific configuration. NGINX, Traefik, AWS Load Balancer Controller, HAProxy, and cloud provider controllers all interpret different annotations.
| Need | Where It Usually Lives | Risk |
|---|---|---|
| Redirect HTTP to HTTPS | Controller annotation or global controller config. | Wrong annotation can do nothing or redirect unexpectedly. |
| Request body size | NGINX/Traefik-specific annotation or middleware. | Uploads fail with 413 if too small. |
| Timeouts | Controller annotation, proxy config, or Gateway policy. | Long requests fail with 504. |
| Rewrite path | Controller annotation or route filter. | Apps see different paths than expected. |
| Cloud load balancer type | Controller Service or cloud-specific Ingress annotation. | Can create public exposure when private was intended. |
Gateway API Pattern
Gateway API splits responsibilities more cleanly. A platform team can own the Gateway and listener configuration; app teams can own HTTPRoutes that attach to the Gateway, subject to allowed namespaces and route policies.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public-gateway
namespace: gateway-system
spec:
gatewayClassName: nginx
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: api.example.com
tls:
mode: Terminate
certificateRefs:
- name: api-example-com-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web-api
namespace: app
spec:
parentRefs:
- name: public-gateway
namespace: gateway-system
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web-api
port: 80Debugging Checklist
Debug from backend to edge. If the Service has no endpoints, the Ingress layer cannot fix it. If the controller is missing or watching a different class, the Ingress object may look valid while no traffic moves.
NS=<namespace>
ING=<ingress>
SVC=<service>
HOST=<hostname>
# 1. Backend health.
kubectl get svc "$SVC" -n "$NS" -o wide
kubectl get endpointslice -n "$NS" -l kubernetes.io/service-name="$SVC" -o wide
# 2. Ingress rule and class.
kubectl get ingressclass
kubectl get ingress "$ING" -n "$NS" -o wide
kubectl describe ingress "$ING" -n "$NS"
kubectl get ingress "$ING" -n "$NS" -o yaml
# 3. Controller and external address.
kubectl get pods -A | grep -Ei 'ingress|traefik|nginx|haproxy'
kubectl get svc -A | grep -Ei 'ingress|traefik|nginx|haproxy'
kubectl logs -n <controller-namespace> deploy/<controller-deployment> --tail=100
# 4. Edge test with explicit host header.
curl -vk --resolve "$HOST:443:<INGRESS_IP>" "https://$HOST/"Symptom To Cause
| Symptom | Likely Cause | Check First |
|---|---|---|
| Ingress has no external address | Controller Service pending, cloud LB issue, missing controller. | Controller Service, events, cloud controller logs. |
| 404 from controller | Host/path does not match any rule, wrong ingress class, default backend response. | describe ingress, curl Host header, controller logs. |
| 502 or 503 | Service has no endpoints, Pod not ready, wrong Service port. | EndpointSlice and Service YAML. |
| 504 timeout | Backend slow, timeout too low, network policy, app not responding. | App logs, direct Service curl, controller timeout settings. |
| TLS certificate mismatch | Wrong Secret, wrong host, stale certificate, cert-manager failure. | Ingress TLS block, Secret, cert-manager resources, openssl s_client. |
| HTTP works, HTTPS fails | TLS listener missing, firewall/LB issue, Secret invalid. | Controller Service ports, TLS Secret, external LB listeners. |
| Path rewrite breaks app | Controller-specific rewrite annotation or pathType behavior. | Controller annotations and app expected base path. |
Safe Change Pattern
- Identify the controller and class before editing anything.
- Confirm backend Service endpoints are healthy.
- Change the source of truth: Helm values, ArgoCD app, Flux repo, or Terraform/platform module.
- Test with
curl -H "Host: ..."or--resolvebefore changing public DNS. - After rollout, verify status, controller logs, TLS certificate, access logs, and backend metrics.