Ingress: Routing HTTP Into the Cluster

K
Kai··4 min read

Article 5 left one rough edge: NodePort opens a port like 30080, 30081... for each service — ugly and hard to manage when you have many services. LoadBalancer gives each service its own public IP, which is costly. What we really want: one entry point, routing by domain and path to the right service behind it — like a reverse proxy. That's Ingress.

The problem Ingress solves

   Without Ingress (one port/IP per service):
      shop.com:30080 ──► Service A
      shop.com:30081 ──► Service B      (weird ports, hard to remember, hard to do TLS)

   With Ingress (one entry point, smart routing):
                          ┌─► /          ──► Service web
      shop.com:80/443 ──► │  /api        ──► Service api
       (Ingress)          └─► blog.shop.com ──► Service blog
                          + terminates TLS here

Ingress gives you: a standard HTTP/HTTPS address (80/443), routing by host (domain) and path to different services, and one central place to configure TLS. This is how you expose a web application to the outside in production.

Ingress needs an Ingress Controller

This is an easy place to trip up. An Ingress object is just routing rules — it's useless if no one enforces those rules. The enforcer is the Ingress Controller: a real reverse proxy (ingress-nginx, Traefik, HAProxy...) running in the cluster, reading the Ingress rules and configuring itself accordingly.

   Ingress (rules)  ──read by──►  Ingress Controller (real nginx running in the cluster)
                                              │ routes the actual traffic
                                              ▼
                                        Service → Pod

A blank cluster does not come with an Ingress Controller — you have to install one. On minikube, just enable the addon:

minikube addons enable ingress
* Verifying ingress addon...
* The 'ingress' addon is enabled

It installs ingress-nginx into the ingress-nginx namespace. Wait for the controller to be ready:

kubectl wait --namespace ingress-nginx \
  --for=condition=Ready pod -l app.kubernetes.io/component=controller --timeout=120s
pod/ingress-nginx-controller-596f8778bc-smchl condition met

Write an Ingress rule

Route every request with host web.local to the service web (Article 5):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress
spec:
  rules:
    - host: web.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web        # points to Service "web" (Article 5)
                port:
                  number: 80
kubectl apply -f ingress.yaml
kubectl get ingress web-ingress
NAME          CLASS   HOSTS       ADDRESS   PORTS   AGE
web-ingress   nginx   web.local             80      5s

Notice the chain of links: Ingress → Service web → (via selector) → the Pods web. Ingress doesn't talk to pods directly; it always points through a Service. Every piece from Articles 4–6 links up into a complete path from the internet to the pod.

Test Host-based routing

Ingress chooses the service based on the Host header. Send a request with the right host to the cluster's IP:

minikube ssh "curl -s -H 'Host: web.local' http://localhost/ | grep title"
<title>Welcome to nginx!</title>

The request enters the Ingress Controller's port 80, the controller sees Host: web.local matching the rule, forwards to Service web, and the Service load-balances down to an nginx pod. The whole chain works.

Docker driver note (macOS/Windows): with the Docker driver, the minikube IP can't be called straight from the host machine, so we test from inside the node (minikube ssh). To access from your real machine, run minikube tunnel (opens a path to the cluster), then add the line 127.0.0.1 web.local to /etc/hosts, then curl http://web.local. On Linux (default driver), curl -H 'Host: web.local' http://$(minikube ip)/ works directly.

Routing multiple services and TLS

Ingress's real power shows up when you have multiple rules — routing by path or by subdomain:

  rules:
    - host: shop.local
      http:
        paths:
          - path: /api          # shop.local/api  → service api
            pathType: Prefix
            backend: { service: { name: api, port: { number: 8080 } } }
          - path: /             # shop.local/     → service web
            pathType: Prefix
            backend: { service: { name: web, port: { number: 80 } } }

And TLS is declared compactly by pointing to a Secret holding the certificate (remember mounting a Secret as a file in Article 7):

spec:
  tls:
    - hosts: ["shop.local"]
      secretName: shop-tls       # a tls-type Secret holding cert + key

In production, people usually pair Ingress with cert-manager to automatically obtain and renew Let's Encrypt certificates — TLS becomes almost "automatic". This part is out of scope for the fundamentals, but worth knowing to picture the full picture.

Wrap-up

Ingress is a single HTTP/HTTPS entry point, routing by host and path to multiple Services behind it, with one central place to configure TLS — far cleaner than opening many NodePorts. The key thing to remember: the Ingress object is just rules; you must have an Ingress Controller (ingress-nginx, Traefik...) running in the cluster to enforce them — on minikube, enable it with minikube addons enable ingress. Ingress always points to pods through a Service, closing the chain internet → Ingress → Service → Pod.

The "connectivity & configuration" part is complete here. From Article 10 we move into operations: how Kubernetes knows a pod is truly healthy and ready to receive traffic — via liveness and readiness probes.