Ingress & Controllers
The previous page ended on a sharp limitation. A Service
of type LoadBalancer gets external traffic to a set of Pods — but it does so one cloud load balancer
per Service. Twenty microservices means twenty load balancers, twenty public IPs, twenty bills, and
no shared place to terminate TLS or route by URL. That’s the same crude, per-service plumbing you’d
wire up by hand on bare metal, just moved into the cloud. Kubernetes has a better answer for HTTP, and
it’s called Ingress.
The problem Ingress solves
Section titled “The problem Ingress solves”Almost everything users hit over the web shares one shape: requests arrive at https://example.com,
and which backend handles them depends on the host (api.example.com vs app.example.com) and
the path (/checkout vs /search). That’s L7 routing — the job of a reverse proxy, the same one
from Load Balancing & Proxies. Doing it manually means
hand-editing an nginx config every time a service is added, then reloading it without dropping
connections, then redoing the TLS certificates by hand. Each step is a chance to typo a server block
and take the site down.
Ingress turns all of that into one declarative object:
┌─────────────────────────────────────┐ Internet │ Kubernetes cluster │ │ │ example.com ──HTTPS──► │ [ Ingress Controller ] (one LB) │ │ │ routes by host + path │ │ ├─ api.example.com → Service api │ │ ├─ /search → Service web │ │ └─ /checkout → Service pay │ │ │ └─────────────────────────────────────┘One public entry point, TLS terminated once, all routing rules version-controlled as data.
Two things, often confused: the resource vs the controller
Section titled “Two things, often confused: the resource vs the controller”This is the single most important distinction on the page, and it’s the reconciliation loop wearing a new hat.
- The Ingress resource is a piece of YAML — desired state. It says “
/searchshould go to thewebService.” It does nothing on its own. Apply an Ingress to a cluster with no controller and traffic goes nowhere; the object just sits there. - The Ingress controller is a running program (NGINX, Traefik, HAProxy, or a cloud one) that watches Ingress resources and configures a real proxy to match. It’s the actuator that turns the data into behavior.
Because the resource is generic but controllers differ, you tell Kubernetes which controller should
own an Ingress with an IngressClass. No ingressClassName and no default class means no controller
picks it up — a classic “my Ingress does nothing” gotcha.
A real Ingress: host and path routing
Section titled “A real Ingress: host and path routing”First, install a controller. You don’t write one; you deploy a maintained one:
# Install the community NGINX ingress controller (creates one cloud LoadBalancer)helm install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx --create-namespace
# It comes with an IngressClass named "nginx"kubectl get ingressclassNow the routing rules. Note these reference Services by name — Ingress sends traffic to Services, which in turn select Pods:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: shopspec: ingressClassName: nginx # which controller owns this rules: - host: api.example.com # host-based routing http: paths: - path: / pathType: Prefix backend: service: name: api port: number: 80 - host: shop.example.com http: paths: - path: /search # path-based routing pathType: Prefix backend: service: { name: web, port: { number: 80 } } - path: /checkout pathType: Prefix backend: service: { name: pay, port: { number: 80 } }kubectl apply -f ingress.yamlkubectl get ingress shop # ADDRESS is the controller's public IPkubectl describe ingress shop # events show whether the controller accepted itpathType: Prefix matches /search and everything under it; Exact matches only the literal path.
The controller compiles these rules into its proxy config and reloads — gracefully, without dropping
in-flight requests, which is exactly the manual dance you no longer perform.
TLS termination
Section titled “TLS termination”TLS is where the manual process is most dangerous: a mistyped cert path or an expired certificate is a visible outage. Ingress moves the certificate into a Secret and lets the controller terminate TLS for you:
spec: tls: - hosts: [shop.example.com] secretName: shop-tls # a kubernetes.io/tls Secret holding cert + key rules: - host: shop.example.com # ...paths as aboveThe Secret holds the certificate and private key (see HTTP & TLS for the handshake itself). The controller presents the cert, terminates TLS at the edge, and forwards plain HTTP to Pods inside the cluster. Pair it with cert-manager and the entire certificate lifecycle — issue from Let’s Encrypt, renew before expiry, swap the Secret — becomes an automated control loop. The most common “we forgot to renew” outage simply stops happening.
Why this makes production safer
Section titled “Why this makes production safer”Tie it back to the book’s thread: what manual, error-prone step does this remove? Without Ingress, adding a service to your public surface is a hand-edited proxy config, a manual reload hoping not to drop connections, a manually installed TLS cert, and a calendar reminder to renew it — four chances to cause an outage, repeated for every change. Ingress collapses all of it into one reviewed, version- controlled YAML object reconciled by a controller. Routing rules become diffs in a pull request; certificates renew themselves. The front door of production stops being a place where one careless edit takes everyone offline. From the edge, the next question turns inward — how Pods get their configuration and secrets.
The architect’s lens
Section titled “The architect’s lens”Step back from the YAML and judge Ingress as a front-door design decision:
- Why does it exist? Because a
LoadBalancerService gives you one cloud LB per Service — twenty microservices means twenty LBs, twenty IPs, twenty bills, and no shared place to terminate TLS or route by URL. Ingress is the one HTTP front door that does host- and path-based L7 routing. - What problem does it solve? It collapses hand-edited nginx server blocks, risky reloads, and manually installed certs into one version-controlled object that a controller reconciles — and with cert-manager, TLS issuance and renewal become a control loop, deleting the “we forgot to renew” outage (the Microsoft Teams 2020 failure).
- What are the trade-offs? The resource is inert without a matching controller, so you must run and
operate one (NGINX, Traefik, a cloud one) and wire
ingressClassName— forget it and your Ingress silently does nothing. - When should I avoid it? When the traffic isn’t HTTP(S): raw TCP/UDP (a database, a game server)
still needs a
LoadBalancerService or controller-specific config, since Ingress speaks only L7 — the newer Gateway API is what generalizes beyond it. - What breaks if I remove it? Every public service falls back to its own cloud load balancer, TLS gets terminated (and forgotten) per service, and adding a route to the public surface becomes a hand-edited proxy config and a manual reload — four chances at an outage per change instead of a diff in a pull request.
Check your understanding
Section titled “Check your understanding”- A
LoadBalancerService already exposes a Pod. What specific problems appear when you have twenty web services, and how does Ingress address them? - Explain the difference between an Ingress resource and an Ingress controller. What happens if you apply an Ingress to a cluster that has no controller installed?
- How is Ingress “the controller pattern again”? Name two other controllers in Kubernetes that work the same way.
- What does
pathType: Prefixmean, and how does the controller routeshop.example.com/checkoutto the right Pods? (Hint: Ingress points at Services, not Pods directly.) - Using the book’s thread, describe the manual, error-prone steps around TLS that Ingress plus cert-manager eliminate.
Show answers
- A
LoadBalancerService exposes Pods one cloud load balancer per Service: twenty services means twenty LBs, twenty public IPs, twenty bills, and no shared place to terminate TLS or route by URL. Ingress provides one entry point that routes by host and path and terminates TLS once, with all rules in a single version-controlled object. - The resource is YAML (desired state) that does nothing on its own; the controller is a running program (NGINX, Traefik, a cloud one) that watches Ingress resources and configures a real proxy to match. Apply an Ingress with no controller installed and traffic goes nowhere — the object just sits there inert.
- It’s declare desired state, a controller reconciles reality to match: the Ingress controller watches Ingress resources and reconciles a proxy’s config. Two others: the scheduler (watches unassigned Pods) and the Deployment controller (watches/manages ReplicaSets).
pathType: Prefixmatches the path and everything under it (/checkoutand below), versusExactfor the literal path only. The controller compiles the rules into proxy config, matchesshop.example.com/checkoutto thepayService, and the Service then selects the actual Pods — Ingress points at Services, not Pods directly.- Without Ingress, TLS means a hand-installed certificate, a mistyped cert path or expired cert as a visible outage, and a calendar reminder to renew that someone forgets. Ingress moves the cert into a Secret and terminates TLS at the edge; pair it with cert-manager and issuance, renewal-before-expiry, and Secret swap become an automated control loop — the “we forgot to renew” outage stops happening.