Why ingress is a decision, not a default
Every cluster that serves users needs a way in. Many teams install whatever the first tutorial used, and then live with it: surprise load balancer bills, TLS certificates that expire on a Sunday, or a single proxy deployment that becomes the bottleneck for everything.
Choosing and configuring an ingress controller is a small piece of work with an outsized effect on reliability and security. It is part of our Kubernetes consulting service, usually done alongside cluster architecture so networking and edge design agree with each other.
Two families of controller
In-cluster proxies such as NGINX run as pods. A cloud load balancer (usually layer 4) forwards traffic to them, and they route by host and path. You operate scaling, upgrades and tuning, in return for portability and rich behaviour.
Cloud-integrated controllers such as the AWS Load Balancer Controller and Azure’s Application Gateway options translate Kubernetes objects into managed load balancer configuration. The cloud operates the data plane, and you get native WAF and certificate integration, in return for tighter cloud coupling and fewer exotic features.
| Concern | NGINX | AWS ALB controller | Azure Application Gateway |
|---|---|---|---|
| Who runs the data plane | You (pods) | AWS (ALB) | Azure |
| Portability across clouds | High | AWS only | Azure only |
| WAF integration | Add-on (ModSecurity) | AWS WAF | Built in (WAF tier) |
| Advanced rewrites and annotations | Extensive | Limited | Moderate |
| Typical cost shape | Compute plus one load balancer | Per ALB plus capacity units | Gateway plus capacity units |
| Direct to pod routing | Via proxy | IP target mode | Yes |
One caution before choosing NGINX: the community ingress-nginx project has been announced for retirement in 2026. Check the current state, and consider alternatives or Gateway API implementations for new work.
How we decide
We ask a short list of questions:
- Are you single cloud and likely to stay? If yes, native integration reduces what you operate.
- Do you need WAF, DDoS protection or private link style connectivity? Cloud controllers make these easier.
- Do you rely on rewrites, rate limiting, auth subrequests or canary routing that annotations make easy?
- How many load balancers will you create? Per-ingress ALBs add up, so grouping ingresses onto a shared ALB is an important setting.
- Does your team already know one of these?
The answer is often “ALB on EKS, Application Gateway on AKS, and NGINX where portability or behaviour matters”. We record the reasoning, so a later engineer understands why.
What a sound setup contains
- High availability. At least two replicas across zones, a pod disruption budget and sensible requests, because ingress pods restarting is customer visible downtime.
- Automated TLS. cert-manager issuing from an ACME provider, or the cloud’s certificate service.
- DNS automation. external-dns creates records from Ingress objects so hostnames do not depend on a ticket.
- Timeouts and body limits matching real application behaviour, since defaults surprise teams with file uploads and long polling.
- Security headers and TLS policy set deliberately, with old protocol versions disabled.
- Observability. Request rate, error rate, latency and certificate expiry exported to Prometheus.
A concrete example
An AWS Load Balancer Controller ingress sharing one ALB across services, with TLS from ACM:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/group.name: public
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:REGION:ACCOUNT_ID:certificate/CERT_ID
alb.ingress.kubernetes.io/healthcheck-path: /healthz
spec:
ingressClassName: alb
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
The group.name annotation lets multiple ingresses share one load balancer, which keeps cost down. The ARN is shown with obvious placeholders, as it would be filled from Terraform outputs in your repository.
On NGINX, the equivalent adds cert-manager through an annotation such as cert-manager.io/cluster-issuer and a tls block in the spec.
Worked example: planning a migration between controllers
Replacing an ingress controller on a live cluster is common, especially if you are moving off a controller that is being retired. The safe pattern is to run both side by side. Install the new controller with its own ingress class and its own load balancer, then recreate a low-risk route against it using a test hostname. Compare responses, headers, timeouts and TLS behaviour with the old path.
Cutover happens in DNS, not in the cluster: lower the record TTL ahead of time, move one hostname at a time, and keep the old route in place until traffic has drained. Rolling back is then a DNS change rather than an emergency redeploy. Annotations rarely translate one to one between controllers, so we list every annotation in use and decide for each whether the new setup supports it, replaces it or no longer needs it.
Trade-offs and failure modes
- One ALB per ingress. The default creates many load balancers. Grouping fixes it.
- Health check mismatch. The load balancer checks a path that returns an error until the app warms up, so new pods never receive traffic. Align readiness probes and load balancer health checks.
- Draining on deploy. Without a graceful shutdown delay, users see dropped connections during rollouts. A short
preStopsleep plus deregistration delay solves most cases. - Single controller as a shared fate. One bad annotation can break every team’s routing. Admission validation helps.
- Wide open backends. Ingress exposes services, network policy limits what else can reach them.
- Overbuilding. A single small app does not need a service mesh gateway and a WAF.
Delivery and handover
You get the controller installed as code, certificate and DNS automation, dashboards and alerts, a load test of the edge path, and a runbook for failures such as expired certificates and 502 or 504 errors. It is working when a new service gets a hostname and valid TLS from a pull request, rolling deployments cause no 5xx spikes, and certificate expiry is something dashboards track rather than people. See monitoring for the supporting metrics, or contact us to compare options for your stack.