Skip to content

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 / linkerd gateways - if you're running a service mesh.

You install one controller (cluster-wide), then write Ingress resources.

Install ingress-nginx (local cluster)

For minikube:

minikube addons enable ingress

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:

kubectl get pods -n ingress-nginx

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:

127.0.0.1 example.local

(On minikube/kind you may need to use the controller's IP or port; check the controller's docs.)

Test:

curl http://example.local/         # → web
curl http://example.local/api/x    # → api

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

  1. Install an ingress controller (your local cluster's chosen method).
  2. Apply two Deployments + Services (web and api, both nginx).
  3. Apply an Ingress that routes / to web and /api to api.
  4. Add example.local to /etc/hosts pointing at your cluster's ingress (often 127.0.0.1).
  5. 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.

Next: Helm →

Comments