Ingress & Ingress Controllers
Ingress & Ingress Controllers
A Kubernetes Service of type LoadBalancer gives you one cloud load-balancer per service. At ten services that is ten public IPs, ten monthly bills, and ten TLS certificates to rotate. Real production clusters serve dozens to hundreds of HTTP workloads. Ingress is the Kubernetes API object that solves this: one entry point to the cluster, with L7 (HTTP/HTTPS) routing rules that fan traffic out to the right Services based on hostname and path — no extra cloud resources per service.
The Ingress API vs. the Ingress Controller
The Ingress API object is just a configuration declaration stored in etcd — it does nothing on its own. The heavy lifting is done by an Ingress Controller: a Deployment running inside the cluster that watches Ingress objects and programs an actual reverse proxy accordingly. The two most widely deployed controllers are:
- ingress-nginx (maintained by the Kubernetes community) — wraps NGINX; the default choice for self-managed clusters and EKS/GKE bare setups.
- AWS Load Balancer Controller — provisions an Application Load Balancer (ALB) per Ingress or shares one across Ingresses using target-group binding; mandatory on EKS when you need WAF or native AWS certificate management.
Other controllers include Traefik, HAProxy Ingress, and Contour. The Ingress spec is controller-agnostic: the same YAML works (mostly) across controllers; controller-specific behaviour is expressed via annotations on the Ingress object.
Installing ingress-nginx
On a real cluster, ingress-nginx is installed via Helm. The chart creates the controller Deployment, a Service of type LoadBalancer (the single cloud LB), and all necessary RBAC.
Writing an Ingress Manifest
A minimal Ingress that routes two virtual hosts to two different Services:
Key fields to understand:
spec.tls— enables HTTPS. The controller terminates TLS here and forwards plain HTTP to the backend Service.spec.rules[*].host— exact hostname match; the controller uses the HTTPHostheader to pick a rule.pathType: Prefix— matches any path starting with the given string.Exactmatches only the literal path.ImplementationSpecificdelegates interpretation to the controller.annotations— the escape hatch for anything not in the Ingress spec: rate limiting, CORS headers, auth, WAF rules, canary weights, etc.
TLS Termination in Depth
Ingress TLS terminates at the controller. The Secret must contain tls.crt and tls.key. The most production-grade way to manage this is cert-manager, which integrates with Let's Encrypt and rotates certificates automatically before expiry:
nginx.ingress.kubernetes.io/ssl-redirect: "true" globally so HTTP is never served in production.Ingress Traffic Flow Diagram
Production Failure Modes
Understanding what breaks in production — and why — separates engineers who configure Ingress from those who operate it:
- Missing
ingressClassName/ annotation: If you run multiple controllers (nginx and ALB simultaneously) and omit the class selector, neither controller claims the Ingress. Traffic never arrives. Always setspec.ingressClassName: nginxor the equivalent annotation. - Service port mismatch: The Ingress backend references port
80but the Service exposes port8080. The controller silently returns 503. Always verify withkubectl describe ingress <name>— look for Endpoints in the output; if it shows<none>, the backend mapping is wrong. - TLS Secret in wrong namespace: The Ingress and the
tls.secretNameSecret must be in the same namespace. A cross-namespace Secret reference silently fails, leaving the controller serving a self-signed fallback certificate — the worst kind of failure because HTTPS still works, but with the wrong cert. - Controller not HA: A single-replica ingress-nginx Deployment means that a Pod restart or rolling update of the controller drops all inbound traffic for several seconds. Always run at least 2 replicas and configure a
PodDisruptionBudgetwithminAvailable: 1. - Annotation typo: NGINX annotations are read as raw strings — a typo like
nginx.ingress.kubernetes.io/ssl-redirctis ignored without error. Validate your manifest withkubectl apply --dry-run=serverand check the controller logs after applying.
client_max_body_size is 1 MB. File uploads and large JSON payloads hit this limit immediately. Set the annotation nginx.ingress.kubernetes.io/proxy-body-size: "50m" on the Ingress for any service that accepts uploads, or override it globally in the controller ConfigMap. Debugging this is infuriating because the error appears in the client, not in the application logs.Checking Ingress Health
A three-command workflow to diagnose any Ingress problem:
Ingress + ingress-nginx + cert-manager is the standard L7 entry-point stack for Kubernetes. In the next lesson you will see the Gateway API — the next-generation successor to Ingress that solves its multi-tenancy and expressiveness limitations while keeping the same L7 routing intent.