Ingress and Gateway API: one front door
Exposing every Service as a LoadBalancer means one cloud load balancer, one IP and one TLS certificate per app. An Ingress puts a single smart front door in front of all of them: it reads the host name and path of each request and forwards it to the right Service. The Gateway API is its newer, more powerful successor.
Two parts: rules and a controller
Ingress = the rules
A plain Kubernetes object: "/api/posts goes to posts-srv". On its own it does nothing at all.
Ingress controller = the doorman
A reverse proxy (nginx, Traefik, HAProxy, Envoy…) running as Pods. It watches Ingress objects and configures itself. You install one per cluster.
kubectl get ingress shows it, the ADDRESS column stays empty, and nothing answers.Installing a controller
$ helm repo add traefik https://traefik.github.io/charts
$ helm install traefik traefik/traefik -n traefik --create-namespace
$ kubectl get svc -n traefik
NAME TYPE EXTERNAL-IP PORT(S)
traefik LoadBalancer 34.120.8.10 80:31080/TCP,443:31443/TCPTraefik ships built into k3s and supports both Ingress and Gateway API. Note that the controller itself is exposed by one LoadBalancer Service: that is the single front door.
$ minikube addons enable ingress # local, installs ingress-nginxThe community ingress-nginx controller was the most common one for years and appears in countless tutorials, including this course's examples. The Kubernetes project retired it in March 2026: it gets no more releases or security fixes. Existing installs keep working, but for a new cluster pick an actively maintained controller (Traefik, a Gateway API implementation, or a vendor NGINX product). The Ingress YAML stays almost the same.
$ kubectl get ingressclass
NAME CONTROLLER ACCEPTS
traefik traefik.io/ingress-controller default
nginx k8s.io/ingress-nginxA cluster can run several controllers. spec.ingressClassName in your Ingress says which one should handle it. One class can be marked default.
Path-based routing
One host, different paths → different Services. Click the parts:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blog
spec:
ingressClassName: traefik
rules:
- host: blog.example.com
http:
paths:
- path: /api/posts
pathType: Prefix
backend:
service:
name: posts-srv
port:
number: 4000
- path: /api/comments
pathType: Prefix
backend:
service: { name: comments-srv, port: { number: 4001 } }
- path: /
pathType: Prefix
backend:
service: { name: client-srv, port: { number: 3000 } }
ingressClassName
Which controller handles this Ingress. Leave it out and the default class is used.
host
Only requests for this host name match these paths. Leave it out to match any host.
pathType
Prefix: matches the path and anything below it, compared segment by segment (/api/posts matches /api/posts/42, not /api/postsXYZ). Exact: only that exact path. When several match, Exact wins, then the longest Prefix.
backend
The Service and its Service port (not the container port). The Service then forwards to the Pods as usual.
Your Node services receive the full path: posts sees /api/posts/42, so its routes must include the prefix. (Rewriting paths is controller-specific; see annotations below.)
Host-based routing
Different host names on the same front door, each with its own rules. Point all the DNS names at the controller's one IP:
rules:
- host: blog.example.com
http: { paths: [ ... blog paths ... ] }
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: admin-srv, port: { number: 8080 } } }
# DNS (at your domain provider)
blog.example.com A 34.120.8.10
admin.example.com A 34.120.8.10 ← same IP, the Host header decides
Try the router
These are the rules from both examples combined, plus an Exact rule for /healthz. Type a URL or use the buttons:
TLS termination
"Terminating" TLS means the front door decrypts HTTPS, so your services only ever speak plain HTTP inside the cluster. One place holds the certificates; no app needs to handle them.
Browser ══ HTTPS (encrypted) ══▶ blog.example.com:443The browser connects to the controller's public IP on port 443.
Ingress controller: host blog.example.com → certificate from Secret blog-tls decrypt ✓The controller picks the certificate for that host from a
kubernetes.io/tlsSecret (chapter 19) and decrypts.controller ── HTTP (plain) ──▶ posts-srv:4000 ──▶ posts PodInside the cluster the request is plain HTTP. (For encryption inside too, use a service mesh or re-encrypt; most apps do not.)
spec:
tls:
- hosts: [blog.example.com, admin.example.com]
secretName: blog-tls # type kubernetes.io/tls, holds tls.crt + tls.key
rules: ...
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencryptAnnotations: the messy part
The Ingress API only covers hosts, paths and TLS. Anything else (rewrites, redirects, timeouts, rate limits, CORS) is done with controller-specific annotations:
# ingress-nginx
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/proxy-body-size: 10m
# Traefik uses its own Middleware objects instead
So an Ingress that works on one controller can silently behave differently on another. This is the main reason the Gateway API exists.
Gateway API
The Gateway API (stable since 2023, gateway.networking.k8s.io/v1) splits the front door into roles and puts common features in the standard itself:
| Object | Who owns it | Says |
|---|---|---|
GatewayClass | Infrastructure provider | "Gateways of this kind are implemented by Traefik / Envoy / the cloud." |
Gateway | Cluster operator | "Listen on port 443 for *.example.com, with this certificate." |
HTTPRoute | App developer | "Send /api/posts to posts-srv." Lives next to your app. |
Gateway (operator)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: web
spec:
gatewayClassName: traefik
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
certificateRefs:
- name: blog-tlsHTTPRoute (you)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: blog
spec:
parentRefs:
- name: web
hostnames: [blog.example.com]
rules:
- matches:
- path: { type: PathPrefix, value: /api/posts }
backendRefs:
- name: posts-srv
port: 4000
- matches:
- path: { type: PathPrefix, value: / }
backendRefs:
- name: client-srv
port: 3000| Ingress | Gateway API | |
|---|---|---|
| Host and path routing, TLS | ✓ | ✓ |
| Header matching, traffic splitting (90/10 canary), redirects, rewrites | Annotations, different per controller | Built into the standard |
| TCP, UDP, gRPC | No (HTTP only) | ✓ (TCPRoute, GRPCRoute…) |
| Roles (operator vs developer) | One object for everything | Gateway vs Route |
| Status | Stable, frozen: no new features | Stable, where new work happens |
Advice: learn Ingress, because it is everywhere. For a new cluster, prefer the Gateway API.
Try it locally
$ minikube addons enable ingress
$ kubectl apply -f blog-ingress.yaml
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS
blog nginx blog.example.com 192.168.49.2 80
# make the name point to your cluster, on your laptop only:
$ echo "$(minikube ip) blog.example.com" | sudo tee -a /etc/hosts
$ curl http://blog.example.com/api/posts
Recap
- An Ingress is a set of host and path rules; an ingress controller must be installed to run them.
- Path routing: Exact beats Prefix; the longest Prefix wins; Prefix matches whole segments.
- Host routing: many names, one IP; the Host header picks the rules.
- TLS is terminated at the controller using a
kubernetes.io/tlsSecret; cert-manager automates certificates. - Extra features need controller-specific annotations; the Gateway API standardises them and is the future.
Ingress and Gateway API: common questions
What is the difference between Ingress and an Ingress controller?
An Ingress is a set of routing rules. An Ingress controller is a reverse proxy running in the cluster that reads those rules and routes traffic. Without a controller, an Ingress does nothing.
Should I use Ingress or the Gateway API?
Ingress is everywhere and worth knowing, but it is frozen. For new clusters the Gateway API is recommended: it standardises features that Ingress needs annotations for.