Chapter 10
Ingress & Load Balancing — The Front Door to Your Cluster
Before you read, guessWhat is Ingress and how does it manage traffic for multiple Services?
Take ten seconds and guess — even a wrong guess makes the answer stick. Tap to see where the chapter lands, or just read on.
Ingress is an API object describing HTTP(S) routing rules — host-based and path-based — sitting in front of multiple Services behind a single entry point.
Picture a mall where every store — the shoe shop, the electronics store, the food court stall — insisted on its own separate building, street entrance, and parking lot. You'd need twenty addresses memorized for one afternoon of errands. That's what you're doing right now exposing twenty microservices with twenty LoadBalancer Services. There's a saner way to run a mall, and a saner way to run a cluster. Let's fix it.
The problem: one cloud load balancer per Service gets expensive, fast
Rewind to Post 5. A LoadBalancer Service asks your cloud provider for a real, public-facing load balancer with its own external IP, sitting in front of one Service. That's the right tool for exposing one thing to the outside world.
The trouble starts when "one thing" becomes twenty things. Every LoadBalancer Service provisions its own cloud load balancer. Twenty microservices means twenty load balancers, twenty public IPs, twenty line items on your cloud bill — for traffic that could share one front door.
There's a second problem, and it's not just about cost. A LoadBalancer Service only understands L4 — Layer 4, meaning it reads IP addresses and ports, nothing else. It has no idea what an HTTP request actually says. It can't route "/api goes to the backend service" or "shoes.mall.com goes to the shoe service" — it just forwards whatever hits the port to whatever's behind it. Smart, content-aware routing — one address fanning requests out to many Services based on the URL — is simply out of reach for a plain Service.
Ingress: the rules for HTTP routing into your cluster
Ingress is the Kubernetes object built to solve exactly this. It's a set of HTTP(S) routing rules — "requests for this host go here," "requests for this path go there" — that sit in front of multiple Services, all reachable through one external entry point.
Here's the part that trips up almost everyone, so read it twice: an Ingress object, by itself, does nothing. It's YAML describing rules — intent, not running infrastructure. Nothing enforces those rules unless an Ingress Controller is running in your cluster.
The Ingress Controller — commonly ingress-nginx, though several exist — is the actual software that watches for Ingress objects, reads their rules, and does the routing. It runs as its own pods, usually fronted by one LoadBalancer Service for the whole controller. That single external IP becomes the door into your entire cluster's HTTP traffic. Write Ingress YAML with no controller installed, and you get a valid object sitting in etcd, doing precisely nothing.
/electronics → second floor" or "shoes.mall.com → ground floor, shoe store." The Ingress Controller is the mall staff standing next to that board, actually walking each shopper down the right hallway. The board with no staff does nothing — a shopper can read "second floor" all day and never get there without someone directing them. That's an Ingress object with no controller: rules nobody enforces.Path-based and host-based routing, in YAML
Here's one Ingress doing the two kinds of routing you'll see constantly: path-based (same domain, different URL paths) and host-based (different subdomains entirely).
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop-ingress
spec:
ingressClassName: nginx
rules:
- host: mall.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: backend-service
port:
number: 80
- path: /images
pathType: Prefix
backend:
service:
name: image-service
port:
number: 80
- host: shoes.mall.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shoe-service
port:
number: 80
Read it top to bottom: mall.example.com/api routes to backend-service, and mall.example.com/images routes to image-service — that's path-based routing, one domain split by URL. The separate hostname shoes.mall.example.com routes to shoe-service instead — that's host-based routing. One Ingress object, one Controller, one external IP, three backend Services fanned out to depending on what the request says.
pathType: Prefix means "match anything starting with this path" — the type you'll reach for most often. Each backend.service points at a normal ClusterIP Service, the same kind from Post 5. Ingress doesn't replace Services — it routes traffic to them.
TLS: letting Ingress handle HTTPS too
Ingress can also terminate HTTPS — meaning the Controller handles encryption, and your backend Services never have to worry about certificates at all. Add a tls section pointing at a Secret that holds the certificate and key:
spec:
tls:
- hosts:
- mall.example.com
secretName: mall-tls-secret
rules:
- host: mall.example.com
...
That Secret is a regular Kubernetes Secret (Post 6) of type kubernetes.io/tls, holding a cert and private key. In real setups, a separate tool called cert-manager usually creates and renews that Secret automatically — a topic for another day. For now, just know the tls block exists and roughly what it does.
On GKE specifically: Ingress means a real Google Cloud Load Balancer
Worth flagging early, since you'll hit this directly once the series gets you onto GCP: GKE ships with its own built-in Ingress Controller, based on GCE. Create an Ingress object on GKE with no extra setup, and GKE actually provisions a real Google Cloud Load Balancer for you behind the scenes. Same Ingress API — just know that on a real cloud, that YAML turns into real, billed infrastructure. More on this once we deploy there later in the series.
Trying it locally with minikube
If you're practicing on minikube, here's the one step people always forget: minikube doesn't ship with an Ingress Controller running by default. Turn it on explicitly:
$ minikube addons enable ingress
🔎 Verifying ingress addon...
🌟 The 'ingress' addon is enabled
That installs ingress-nginx into your minikube cluster. Once it's running, apply your Ingress object like any other resource:
$ kubectl apply -f ingress.yaml
ingress.networking.k8s.io/shop-ingress created
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
shop-ingress nginx mall.example.com,shoes.mall.example.com 192.168.49.2 80 45s
$ kubectl describe ingress shop-ingress
Name: shop-ingress
Namespace: default
Address: 192.168.49.2
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
mall.example.com
/api backend-service:80 (10.244.0.12:80)
/images image-service:80 (10.244.0.13:80)
shoes.mall.example.com
/ shoe-service:80 (10.244.0.14:80)
Check that ADDRESS field first — it's your quickest sanity check. Rules with no ADDRESS means something upstream is missing.
minikube addons enable ingress, then create two small Deployments and ClusterIP Services (reuse the nginx pattern from Post 5). Write an Ingress with two path-based rules pointing at each, apply it, and check kubectl get ingress for an ADDRESS. Then add an /etc/hosts entry pointing a fake hostname at that address and confirm host-based routing works too by curling it.The exam trap: no ingressClassName, no error, no routing
This one quietly wastes people's time, and it's exactly the kind of thing the CKA exam likes to probe: forget ingressClassName in your Ingress spec — or rely on a "default" IngressClass that doesn't actually exist in this cluster — and Kubernetes accepts your Ingress object anyway. kubectl apply succeeds. No error, nothing red. It just sits there forever, doing nothing, because no controller has claimed it.
kubectl describe ingress <name> first and check whether an ADDRESS was ever assigned — no address almost always means no controller picked up the object, usually because ingressClassName is missing or wrong. Then confirm a controller is actually running with something like kubectl get pods -n ingress-nginx. Only once a controller exists, is running, and has claimed your Ingress should you start doubting the routing rules themselves.Key Takeaways
- A LoadBalancer Service per microservice means one cloud load balancer per service — expensive at scale, and limited to L4 IP/port routing with no awareness of paths or hostnames.
- Ingress is an API object describing HTTP(S) routing rules — host-based and path-based — sitting in front of multiple Services behind a single entry point.
- Ingress does nothing on its own. An Ingress Controller (like ingress-nginx, or GKE's built-in GCE-based controller) must be running in the cluster to actually implement those rules.
- Path-based routing splits traffic by URL on the same host; host-based routing splits by hostname entirely — both live in the same Ingress object's rules.
- Ingress can terminate TLS via a
tlsblock referencing a Secret holding your certificate, so backend Services never handle HTTPS themselves. - On GKE, creating an Ingress automatically provisions a real Google Cloud Load Balancer — directly relevant once you deploy there later.
- On minikube, enable the controller explicitly with
minikube addons enable ingressbefore anything will route. - If an Ingress isn't working, check
kubectl describe ingressfor a missing ADDRESS and confirm a controller is actually running before assuming your YAML is broken.