09 - Ingress¶
What this session is¶
About 30 minutes. Ingress - Kubernetes' HTTP/HTTPS routing layer. Lets you route external traffic to multiple services based on hostname or URL path, terminate TLS, all without provisioning a LoadBalancer per service.
The problem¶
A LoadBalancer Service gets one external IP per service. With 10 services, that's 10 cloud LBs - expensive and unwieldy. You also have no way to do "host-based routing" (route api.example.com to service A, dashboard.example.com to service B) or "path-based routing" (example.com/api → A, example.com/web → B).
Ingress solves this: one entrypoint, many routes.
How it works¶
Ingress requires an Ingress Controller - an actual reverse proxy (nginx, Traefik, HAProxy, etc.) running in the cluster. The Ingress resource you write is configuration the controller reads.
Common controllers:
ingress-nginx- official NGINX-based controller. Most common.traefik- modern, auto-discovery, also a popular default.istio/linkerdgateways - if you're running a service mesh.
You install one controller (cluster-wide), then write Ingress resources.
Install ingress-nginx (local cluster)¶
For minikube:
For kind:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
Wait until the controller pods are ready:
Your first Ingress¶
Assume you have two Deployments + Services: web and api.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
spec:
ingressClassName: nginx
rules:
- host: example.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
Apply. Add to /etc/hosts:
(On minikube/kind you may need to use the controller's IP or port; check the controller's docs.)
Test:
Host-based routing¶
Multiple hosts on the same Ingress:
rules:
- host: web.example.local
http:
paths:
- path: /
pathType: Prefix
backend: {service: {name: web, port: {number: 80}}}
- host: api.example.local
http:
paths:
- path: /
pathType: Prefix
backend: {service: {name: api, port: {number: 80}}}
Both hosts hit the same controller; the controller routes by Host: header.
TLS¶
Terminate HTTPS at the Ingress:
spec:
tls:
- hosts:
- example.local
secretName: example-tls # a TLS-type Secret with cert + key
rules:
- host: example.local
http:
paths: [...]
You provide the cert as a Secret:
apiVersion: v1
kind: Secret
metadata:
name: example-tls
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded PEM>
tls.key: <base64-encoded PEM>
For real certs, use cert-manager (popular K8s add-on; auto-provisions Let's Encrypt certs via DNS or HTTP-01 challenges). Cert-manager + ingress-nginx + DNS = "automatic HTTPS for any new Ingress." Way beyond beginner; mentioned because every production setup uses it.
Path types¶
| Type | What it means |
|---|---|
Prefix |
URL starts with the path |
Exact |
URL matches exactly |
ImplementationSpecific |
Controller-defined (often "regex" for nginx) |
Prefix is the common choice.
Annotations: controller-specific behavior¶
Most controllers extend Ingress via annotations. For ingress-nginx:
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: 50m
Each annotation tweaks one behavior. The /api/foo → /foo rewrite, the redirect HTTP → HTTPS, raising body-size limits. Refer to the controller's docs.
The newer Gateway API (a separate, more structured resource family) is the long-term replacement for these annotations. It's stable but adoption is incremental. Recognize the name.
A typical app's Ingress¶
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts: [api.example.com, www.example.com]
secretName: example-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend: {service: {name: api, port: {number: 80}}}
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend: {service: {name: web, port: {number: 80}}}
That's a real-world shape: host-based routing, automatic TLS via cert-manager, HTTPS redirect.
Debugging¶
kubectl get ingress
kubectl describe ingress app
kubectl logs -n ingress-nginx <controller-pod> # see what the controller is doing
Usually issues are: wrong ingressClassName, no controller installed, DNS not pointing at the controller, backend service has no endpoints (page 04/07).
Exercise¶
- Install an ingress controller (your local cluster's chosen method).
- Apply two Deployments + Services (
webandapi, both nginx). - Apply an Ingress that routes
/toweband/apitoapi. - Add
example.localto/etc/hostspointing at your cluster's ingress (often127.0.0.1). - Curl both paths, see different responses.
If you have time:
- Set up cert-manager and get a self-signed cert into a Secret.
- Add a TLS section to the Ingress; curl https://example.local.
What you might wonder¶
"Ingress vs LoadBalancer vs NodePort?" - NodePort: dev-only. - LoadBalancer: one external IP per service; cloud-only. - Ingress: one entrypoint, many routes. The right answer for HTTP/HTTPS in production.
"What about gRPC?" ingress-nginx supports HTTP/2 (which includes gRPC) but you need annotations to enable it. Or use a gateway controller that handles gRPC natively.
"What's a 'Gateway API'?" A successor design for ingress, with cleaner abstractions (Gateway, HTTPRoute, etc.). Stable as of recent K8s versions. Migration is gradual. Recognize.
Done¶
- Install an ingress controller.
- Write an Ingress resource.
- Use host- and path-based routing.
- Recognize how TLS termination works.
- Use annotations for controller-specific behavior.