Giving every web service its own LoadBalancer means one cloud load balancer, one IP and one bill per service. An Ingress describes HTTP routing rules (hostnames and paths) so a single entry point serves many Services, and can also terminate TLS.
Versions in use on this date. Kubernetes 1.22 (4 August 2021) serves Ingress only as networking.k8s.io/v1; the beta versions were removed. The community controller ingress-nginx is at 0.49, with a 1.0 release expected shortly that also targets the v1 API and IngressClass (covered in the next series entry). Ingress is a resource; it does nothing until an ingress controller is running.
Request flow
flowchart TB
U[Users] -->|app.example.com| DNS[DNS A or CNAME record]
DNS --> LB[Cloud load balancer or node ports<br/>one public entry]
LB --> IC[ingress-nginx controller pods<br/>watch Ingress objects]
IC -->|Host app.example.com path /| WEB[Service web]
IC -->|Host app.example.com path /api| API[Service api]
IC -->|Host admin.example.com| ADM[Service admin]
WEB --> P1[Web pods]
API --> P2[API pods]
ADM --> P3[Admin pods]
CERT[TLS Secret] -.-> IC
The controller turns Ingress rules into its own proxy configuration (an nginx config) and reloads it when they change.
Install the controller
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace
kubectl -n ingress-nginx get pods,svc
kubectl -n ingress-nginx get svc ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0]}'
On a cloud cluster the controller’s Service of type LoadBalancer gets an external address; point DNS there (a wildcard record such as *.example.com simplifies things). On kind, use the port-mapping configuration from the earlier post and the kind-specific manifest. Pin the chart version in real deployments and read the upgrade notes before moving between controller versions.
An Ingress with two paths and TLS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
namespace: shop
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts: [shop.example.com]
secretName: shop-tls
rules:
- host: shop.example.com
http:
paths:
- path: /api
pathType: Prefix
backend: { service: { name: api, port: { number: 8080 } } }
- path: /
pathType: Prefix
backend: { service: { name: web, port: { number: 80 } } }
kubectl -n shop apply -f ingress.yaml
kubectl -n shop get ingress
kubectl -n shop describe ingress shop
curl -H "Host: shop.example.com" http://<load-balancer-address>/
curl --resolve shop.example.com:443:<address> -k https://shop.example.com/api/health
pathType:Prefixmatches by path elements (/apimatches/apiand/api/orders),Exactmatches only that path,ImplementationSpecificleaves it to the controller. It is required in v1.- Longer, more specific paths win; keep rules readable.
- The Service ports refer to the Service port, and each Service must exist in the same namespace as the Ingress.
TLS
Create the certificate Secret from files, or let cert-manager issue and renew it automatically (the cert-manager post covers this):
kubectl -n shop create secret tls shop-tls --cert=fullchain.pem --key=privkey.pem
The Secret must be in the Ingress namespace. Without a tls section, ingress-nginx serves a default fake certificate for HTTPS.
Useful annotations (ingress-nginx)
| Annotation | Purpose |
|---|---|
nginx.ingress.kubernetes.io/rewrite-target: /$2 |
Strip a path prefix (with a regex path such as /app(/|$)(.*)) |
...proxy-read-timeout: "120" |
Longer upstream timeouts |
...whitelist-source-range: 10.0.0.0/8 |
IP allow-list |
...auth-url / auth-signin |
External authentication |
...limit-rps: "20" |
Basic rate limiting |
...backend-protocol: "HTTPS" |
Upstream speaks TLS |
Annotations are controller-specific; other controllers (AWS Load Balancer Controller, Traefik, Contour, HAProxy) use their own.
Troubleshooting
kubectl describe ingressshows the backends and any events;<error: endpoints not found>means the Service has no ready pods.- 404 from nginx: no rule matched the Host header or path; check
ingressClassNameand spelling. - 503: Service exists but has no endpoints.
- 502/504: backend unreachable or too slow; test with
kubectl port-forward svc/web 8080:80. - Controller logs show the decision:
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller --tail=50. - The Ingress shows no ADDRESS: the controller is not running, or watches a different class.
Design notes
Ingress handles HTTP(S) only; for TCP/UDP use a LoadBalancer Service. Keep one controller per class and separate public and internal entry points when needed. The Gateway API is the planned successor, still evolving, so use Ingress today and watch the project.