The question this answers
How does a request from the public internet reach the right pod, with TLS terminated and without one load balancer per service?
Three services must be publicly reachable: the marketing site on example.com, the API on api.example.com, and an admin panel on example.com/admin restricted by source address. All must be HTTPS with automatically renewed certificates.
A single external entry point with host- and path-based routing to internal Services, TLS terminated in one place, and a certificate lifecycle that renews without anyone remembering.
The path, hop by hop
A request from a browser makes five hops. DNS resolves api.example.com to the address of a provider load balancer — a real, billed, internet-facing resource that lives outside the cluster. That load balancer forwards to the ingress controller, which is an ordinary set of pods running a reverse proxy. The controller reads the routing rules you declared, terminates TLS, picks the right internal Service, and the Service delivers the connection to a Ready pod.
The important structural point is that the ingress controller is *just a workload*. It is a Deployment of proxy pods, exposed by one LoadBalancer Service, that happens to watch the cluster API for routing objects and reconfigure itself. That is why it scales, fails and is upgraded like anything else you run — and why "the ingress is down" is usually a pod problem rather than a networking mystery.
What this buys you is arithmetic. Without it, every publicly reachable Service needs its own LoadBalancer Service, which means its own provider load balancer, its own public IP, its own certificate and its own hourly charge. With it, twelve services share one entry point. That consolidation — not the YAML — is the reason ingress exists.
The rules, declared
Routing rules are objects like anything else. The controller watches them and rewrites its own proxy configuration. Below is the classic three-rule case: two hosts, one path prefix, and TLS backed by a certificate in a Secret that a certificate controller renews on a schedule.
Two honest limitations of the older Ingress object are worth knowing before you build on it. First, it only really models HTTP — TCP and UDP routing are done through controller-specific annotations, which is to say not portably. Second, everything beyond basic host and path matching is an annotation, and annotations are controller-specific strings with no schema and no validation. A manifest full of nginx.ingress.kubernetes.io/... keys is not portable to a different controller, and typos in them fail silently.
The Gateway API is the successor designed around those problems: typed resources rather than annotations, protocols beyond HTTP, and a deliberate split of responsibility so that a platform team owns the listeners and certificates while application teams own their own routes without editing a shared object. If you are choosing today and your controller supports it, it is the better foundation — see also Load Balancers as Infrastructure for the layer underneath.
1apiVersion: networking.k8s.io/v12kind: Ingress3metadata:4 name: public5 annotations:6 cert-manager.io/cluster-issuer: letsencrypt # controller-specific string, no schema7spec:8 ingressClassName: nginx9 tls:10 - hosts: [example.com, api.example.com]11 secretName: public-tls # a Secret the cert controller writes and renews12 rules:13 - host: api.example.com14 http:15 paths:16 - path: /17 pathType: Prefix18 backend: { service: { name: api, port: { number: 80 } } }19 - host: example.com20 http:21 paths:22 - path: /admin # more specific prefix must come first23 pathType: Prefix24 backend: { service: { name: admin, port: { number: 80 } } }25 - path: /26 pathType: Prefix27 backend: { service: { name: web, port: { number: 80 } } }28---29# The same intent in the Gateway API: typed, and split by ownership.30# Gateway (platform team) -> listeners, certificates, addresses31# HTTPRoute (product team) -> "api.example.com goes to Service api"Ingress, Gateway API, or neither
The choice is not only between two Kubernetes objects. For many systems the right edge is a managed application load balancer or a CDN in front of the cluster, with the cluster exposing exactly one backend. That moves TLS, WAF rules, rate limiting and DDoS absorption to a managed service that is better at all four, and it keeps the cluster out of the path of a class of attacks entirely.
The clearest reason to route inside the cluster is that routing rules should live with the application and change with it. The clearest reason not to is that an in-cluster ingress controller is a workload you now operate: it needs capacity, its upgrades are traffic-affecting, and when it is unhealthy every service behind it is unreachable at once. That last property is the one to weigh — consolidating twelve load balancers into one entry point also consolidates twelve failure domains into one.
| Option | What it gives | What it costs | Choose it when |
|---|---|---|---|
| LoadBalancer Service per app | Total isolation between services; no shared component. | One billed load balancer, IP and certificate per service. | One or two services, and you want no extra moving parts. |
| Ingress object + controller | Host/path routing and TLS in one place; rules live with the app. | A proxy Deployment you operate; annotations that are not portable. | Several HTTP services and a team comfortable running the controller. |
| Gateway API + controller | Typed routing, non-HTTP protocols, platform/app ownership split. | Newer, controller support varies, more concepts to learn. | Starting fresh, multiple teams sharing one edge. |
| Managed ALB or CDN in front | TLS, WAF, rate limiting and DDoS absorption handled outside the cluster. | Routing config lives away from the app; a provider-shaped dependency. | Public traffic where edge security matters more than config locality. |
| No ingress at all | Nothing to run or debug. | Nothing. | Internal-only workloads. Not everything in a cluster needs a door. |
Key points
- The path is internet → provider load balancer → ingress controller pods → Service → pod, and only the first hop is a billed external resource.
- The ingress controller is an ordinary Deployment running a reverse proxy that reconfigures itself from cluster objects.
- The reason it exists is consolidation: one entry point and one certificate instead of one load balancer per service.
- The Ingress object models HTTP only; everything beyond host and path routing is controller-specific annotations with no schema.
- Consolidating twelve entry points also consolidates twelve failure domains — an unhealthy controller makes every service behind it unreachable.
The loop, answered
Every field is required, which is why no lesson here can recommend something without saying what it costs and what simpler thing to consider first.
- • DNS resolves the public hostname to a provider load balancer created for the ingress controller's Service.
- • The load balancer forwards connections to the controller pods on the cluster nodes.
- • The controller watches routing objects through the API server and rewrites its proxy configuration whenever they change.
- • It terminates TLS using a certificate stored in a Secret, then matches the request by host and path.
- • It forwards to the selected Service, which delivers the connection to one Ready pod.
- • The controller Deployment: its replica count, its resource requests, its upgrades — all of which affect every public request.
- • Certificate lifecycle: issuance, renewal and the alert that fires well before expiry rather than on the day — see Key Management and Encryption at Rest.
- • Routing rule conflicts, especially overlapping path prefixes where the more specific rule must be ordered first.
- • Timeouts and body-size limits at three layers — provider load balancer, controller, application — which must be consistent or the shortest one wins invisibly.
- • Client IP preservation, which requires deliberate configuration and which your rate limiting and audit logs depend on.
- • Certificate expiry: every request fails with a TLS error at once, and the fix is minutes of panic for something a calendar alert would have prevented.
- • The controller pods being evicted or under-provisioned: all public traffic fails while the internal cluster is perfectly healthy.
- • A path prefix ordered wrongly so
/adminis swallowed by/, silently exposing or hiding a route. - • Mismatched timeouts: the load balancer cuts the connection at 60s while the application is configured for 120s, producing 504s that never appear in application logs.
- • A backend Service with no endpoints: the controller returns 503 for a route that looks correctly configured.
- • The controller is a proxy, so it scales with concurrent connections and TLS handshakes rather than with the number of routing rules.
- • TLS termination is CPU-bound; a traffic spike on a controller with a low CPU limit produces throttling and latency, not errors — see OOM Kills and CPU Throttling.
- • Very large numbers of routing objects slow reconfiguration, so rule changes propagate more slowly as the platform grows.
- • This is the deliberate public exposure of the system. A public load balancer on 443 in front of an ingress is the design; the finding would be a database or admin port reachable the same way — see Public Exposure, Read With Context.
- • TLS terminates at the controller, so traffic from there to pods is plaintext on the cluster network unless you add encryption in transit — decide this explicitly for regulated data.
- • The controller can read TLS Secrets across namespaces, which makes it one of the higher-privilege workloads in the cluster.
- • Source-address restrictions and authentication belong here for admin routes, but an ingress rule is a filter, not authentication — the application still authenticates for itself.
- • One provider load balancer instead of N is the direct saving, and it is the main financial argument for ingress.
- • The controller consumes cluster CPU and memory continuously, and TLS termination is the dominant driver of that.
- • Data processed through the external load balancer is metered, and egress to the internet is usually the largest edge line item — see Egress: Moving Data Costs Money, Not Just Storing It.
- • Request rate, error rate and latency at the controller, split by host and route — the highest-value edge dashboard there is.
- • Certificate expiry dates as a monitored metric with alerts weeks ahead, not a task someone remembers.
- • Upstream 502 and 503 rates, which distinguish "no healthy backend" from "backend returned an error".
- • Controller pod CPU throttling, which is the quiet cause of edge latency that looks like a network problem.
- • The signal that lies: backend pod health. Every pod can be healthy while the controller cannot route to them because a Service selector is wrong.
- • A managed application load balancer or CDN in front of the cluster, terminating TLS and applying WAF rules outside your failure domain.
- • A single LoadBalancer Service, when there is exactly one public service — an ingress controller for one route is pure overhead.
- • No public entry at all for internal workloads, which is the correct answer more often than the number of ingress objects in the average cluster suggests.
- • A PaaS or managed container service, which gives you routing and managed certificates without a controller to run.
- • Buys consolidation of entry points and certificates; costs a shared failure domain in front of every public service.
- • Buys routing rules that live and version with the application; costs a proxy workload you operate and upgrade under live traffic.
- • Buys a portable-looking abstraction; costs real portability, since the useful configuration is in controller-specific annotations.
What people believe, and what is true
Ingress is a component Kubernetes provides.
The object is; the controller is not. Without a controller installed and running, an Ingress object does absolutely nothing.
Ingress replaces the load balancer.
It sits behind one. There is still a provider load balancer holding the public address — ingress reduces how many of them you need to one.
Terminating TLS at the ingress means traffic is encrypted end to end.
It is encrypted to the controller. From there to the pod it is plaintext on the cluster network unless you configured otherwise.