AWS Builder Center
Kubernetes From Scratch, Part 3: Exposing Services the Right Way with Ingress

Kubernetes From Scratch, Part 3: Exposing Services the Right Way with Ingress

Part 3 of my Kubernetes learning series why I moved past NodePort, what an Ingress controller actually does, and the routing bug that taught me how it really works.

In Part 1 of this series I covered Pods, Deployments, and Services. In Part 2, I moved into ConfigMaps, Secrets, and Namespaces. This time, I'm tackling something I'd been avoiding because it seemed more complex than it needed to be: exposing services to the outside world properly, using Ingress.

Where I started: NodePort

My early experiments exposed services using NodePort it's the simplest way to get external traffic into a cluster, since it opens a static port on every node. It works, but it's clunky: you're stuck with a narrow port range (30000-32767), every service needs its own port, and there's no clean way to route based on hostname or path. For anything beyond a quick local test, it just doesn't scale.

What an Ingress actually does

An Ingress isn't a replacement for a Service it sits in front of your Services and gives you a single entry point that can route traffic based on rules: hostname, URL path, or both. Instead of exposing five different NodePorts for five different services, I could expose one Ingress and route /api to one Service and /dashboard to another, all through standard HTTP/HTTPS ports.
The part that confused me initially: an Ingress resource by itself does nothing. You need an Ingress controller actually running in the cluster (like NGINX Ingress Controller) to read those Ingress rules and act on them. Writing an Ingress YAML file without a controller deployed is like writing a config file for software that was never installed it just sits there.

The bug that taught me how routing really works

Once I had NGINX Ingress Controller running, my first Ingress rule routed traffic to the wrong Service entirely requests to /dashboard were hitting my API service instead. The cause: path matching in Ingress rules evaluates in a specific order and is sensitive to whether you use pathType: Prefix vs pathType: Exact. I had two rules with overlapping prefixes, and the more general one was catching requests meant for the more specific one. Reordering the rules and being explicit about pathType fixed it, but it drove home that Ingress routing isn't "first match wins" in the naive way I assumed.

TLS termination

Once routing worked, I added TLS by referencing a Secret containing a certificate directly in the Ingress spec. This was the first time the Secrets knowledge from Part 2 directly fed into something new the Ingress controller reads that Secret to handle HTTPS termination, so traffic arrives encrypted at the edge without every backend Service needing to handle TLS itself.

What's next

With Ingress working, I'm now looking at Horizontal Pod Autoscaling automatically scaling replicas based on load rather than a fixed number and bringing readiness/liveness probes into the mix so the cluster actually knows when a Pod is healthy enough to receive traffic.
If you're still on NodePort for everything like I was: Ingress is worth the initial complexity. It's the difference between patch-working external access and actually designing how traffic enters your cluster.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article