# 11 — Ingress: many hostnames, one address So far, every app that needs to be reachable gets its own dedicated address from MetalLB's BGP pool (`docs/05-metallb.md`) — Argo CD at `10.10.60.10`, the demo app at `10.10.60.11`. That's fine for a handful of apps, but doesn't scale: every app eats another address out of a pool that's only so big, and there's no way to route by hostname (`blog.ahmza.com` vs `photos.ahmza.com` sharing one address on port 443). ## What Ingress actually is An **Ingress controller** is a reverse proxy that sits behind **one** MetalLB address and reads `Ingress` resources — small manifests that say "requests for this hostname go to that Service." Instead of every app declaring its own `type: LoadBalancer` Service, most apps go back to a plain internal `ClusterIP` Service, and get an `Ingress` resource pointing at it. One address, many hostnames. **ingress-nginx** is the controller this cluster uses — the most common, most documented choice, distributed as a Helm chart just like MetalLB and Longhorn (see [`apps/ingress-nginx/Chart.yaml`](../apps/ingress-nginx/Chart.yaml)). ## What's already running **`apps/ingress-nginx.yaml`** → one Application, pointing at `apps/ingress-nginx`, pulling in the upstream chart. [`apps/ingress-nginx/values.yaml`](../apps/ingress-nginx/values.yaml) gives its Service a fixed address from the MetalLB pool (`10.10.60.12` — see [`docs/network.md`](network.md)) and marks its `IngressClass` as the cluster default, so an `Ingress` resource that doesn't name a class explicitly still uses it — the same convenience Longhorn gives PVCs that don't name a `storageClassName`. Unlike MetalLB and Longhorn, ingress-nginx needed no Pod Security exemption: it only needs the `NET_BIND_SERVICE` capability to bind ports 80/443, which Kubernetes' `baseline` profile already permits (unlike `hostNetwork` or `hostPath`, which it forbids). ## Verify it's actually running ```sh argocd app get ingress-nginx ``` ```sh kubectl -n ingress-nginx get svc ``` `EXTERNAL-IP` should show `10.10.60.12`. ```sh kubectl get ingressclass ``` Should show one `IngressClass`, marked as the default via annotation. ## Routing a real app through it Converting an app from its own MetalLB address to Ingress means: 1. Change its Service from `type: LoadBalancer` back to the implicit default, `ClusterIP` — it no longer needs its own address. 2. Add an `Ingress` resource naming a real hostname and pointing at that Service. 3. Point that hostname at ingress-nginx's address, `10.10.60.12` — either a real DNS record (safe even for a LAN-only cluster: a public DNS record pointing at a private address just won't resolve to anything reachable from outside your LAN yet, which is fine until you actually expose this cluster publicly) or a local override for testing. This is exactly what `docs/12-cert-manager.md` builds on next — a real hostname is what a TLS certificate actually gets issued *for*. ## Checklist - [ ] `ingress-nginx` Application `Synced`/`Healthy` - [ ] `kubectl -n ingress-nginx get svc` shows `EXTERNAL-IP` `10.10.60.12` - [ ] `kubectl get ingressclass` shows the default class - [ ] (Optional, hands-on) Converted one app to route through Ingress with a real hostname instead of its own MetalLB address Continue to [`docs/12-cert-manager.md`](12-cert-manager.md) for real HTTPS certificates on that hostname.