Ingress controllers and Ingress resources

How an Ingress controller turns Ingress objects into HTTP routing, how hosts, paths and pathTypes match, and how to terminate TLS with a Secret.

Networking · 20%40 min2 tasks

After this lesson

  • 01You can explain the split between the Ingress controller, the IngressClass and the Ingress resource, and find all three on a cluster.
  • 02You can write host- and path-based Ingress rules with the right pathType, imperatively or in YAML, and test them with curl.
  • 03You can terminate TLS on an Ingress with a kubernetes.io/tls Secret and prove which certificate is served.

This lesson covers Know how to use Ingress controllers and Ingress resources. A typical task gives you two Services and asks for one Ingress that routes by path or by host, sometimes with TLS. The YAML is short. The points are lost on details: a missing ingressClassName, Exact where Prefix was meant, or a backend port that does not exist on the Service.

Controller, class, resource

An Ingress is configuration only. Nothing routes until an Ingress controller runs: a Deployment (nginx, Traefik, HAProxy, a cloud load balancer's controller) that watches Ingress objects, renders them into its proxy config and receives traffic through its own Service. An IngressClass ties the two together. An Ingress names a class in spec.ingressClassName, and the class names the controller that owns it in spec.controller.

Find the controller on any clusterbash
kubectl get ingressclass
# NAME    CONTROLLER             PARAMETERS   AGE
# nginx   k8s.io/ingress-nginx   <none>       1h
kubectl get ingressclass -o yaml | grep is-default-class     # a default class is used when ingressClassName is omitted
kubectl -n ingress-nginx get deploy,pods,svc -o wide

# Lab: the controller runs on cka-control-plane with hostPorts 80/443,
# which kind maps to localhost:8080 and localhost:8443 on your machine.
curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/      # 404 from the controller's default backend

If no IngressClass is marked default and an Ingress has no ingressClassName, no controller picks it up. It sits there with an empty ADDRESS column and no events. The old kubernetes.io/ingress.class annotation is deprecated; use the field.

Anatomy of an Ingress

Host rules, two paths, a default backend and TLSyaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop
  namespace: nw-ing
spec:
  ingressClassName: nginx
  defaultBackend:              # requests that match no rule (optional)
    service:
      name: shop-web
      port:
        number: 80
  tls:
    - hosts:
        - shop.local
      secretName: shop-tls     # type kubernetes.io/tls, same namespace as the Ingress
  rules:
    - host: shop.local         # omit host to match every host; "*.shop.local" matches one label
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: shop-api
                port:
                  number: 80   # or  name: http  for a named Service port
          - path: /
            pathType: Prefix
            backend:
              service:
                name: shop-web
                port:
                  number: 80
pathTypeMatchesExample: path /api
ExactThe URL path exactly, case-sensitive/api only; not /api/ or /api/v1
PrefixWhole path elements split on //api, /api/, /api/v1; not /apiv1
ImplementationSpecificWhatever the controller decides (regexes in some)Avoid unless the question asks for it

When several paths match, the longest one wins, and Exact beats Prefix at equal length. That is why / with Prefix works as a catch-all next to /api. The backend must be a Service in the same namespace as the Ingress, and the port must be a Service port (port), not the container's targetPort.

kubectl create ingress

bash
# --rule="host/path=service:port[,tls=secret]"; a trailing * means Prefix, no * means Exact
kubectl -n nw-ing create ingress shop --class=nginx \
  --rule="shop.local/*=shop-web:80" \
  --rule="shop.local/api*=shop-api:80"

kubectl -n nw-ing create ingress shop --class=nginx --rule="shop.local/api=shop-api:80"   # Exact /api only!
kubectl -n nw-ing create ingress catch-all --class=nginx --default-backend=shop-web:80
kubectl -n nw-ing create ingress secure --class=nginx --rule="secure.shop.local/*=shop-web:80,tls=secure-tls"
kubectl -n nw-ing create ingress rewrite --class=nginx --rule="shop.local/v2*=shop-api:80" \
  --annotation nginx.ingress.kubernetes.io/rewrite-target=/

# Always look at what you generated before applying it
kubectl -n nw-ing create ingress shop --class=nginx --rule="shop.local/*=shop-web:80" --dry-run=client -o yaml

TLS termination

bash
# A self-signed certificate for one host (use the files you are given on the exam)
openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
  -keyout tls.key -out tls.crt -subj "/CN=secure.shop.local" -addext "subjectAltName=DNS:secure.shop.local"

kubectl -n nw-tls create secret tls secure-tls --cert=tls.crt --key=tls.key   # type kubernetes.io/tls
kubectl -n nw-tls get secret secure-tls -o jsonpath='{.type}{"\n"}'

# Test without touching /etc/hosts: --resolve pins the name to the lab's port
curl -sk --resolve secure.shop.local:8443:127.0.0.1 https://secure.shop.local:8443/hostname
# Which certificate is served? A wrong or missing Secret gives the controller's fake certificate
echo | openssl s_client -connect 127.0.0.1:8443 -servername secure.shop.local 2>/dev/null | openssl x509 -noout -subject

Once a host has TLS, ingress-nginx answers plain HTTP for it with a 308 redirect to HTTPS. That is expected, and it is why a curl on port 8080 suddenly returns nothing useful. The Secret must hold tls.crt and tls.key, and it must live in the Ingress's namespace. A certificate whose names do not cover the host is ignored, and you get the fake certificate.

When an Ingress does not routebash
kubectl -n nw-ing describe ingress shop          # rules, backends with their endpoints, events
kubectl -n nw-ing get endpointslices -l kubernetes.io/service-name=shop-api   # no endpoints = 503
curl -s -H 'Host: shop.local' localhost:8080/api/orders                       # Host header selects the rule
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller --tail=20       # one access-log line per request
# 404 from nginx: no rule matched (wrong host, Exact vs Prefix, missing class)
# 503: a rule matched, but the Service has no ready endpoints

In the docs · the tab you get on exam day

Tasknw-ing-1
Networking6%~6 min

context · kubectl config use-context kind-cka

Route one host to two Services by path

In namespace nw-ing, create an Ingress named shop using IngressClass nginx for host shop.local. Requests for /api and every path below it must go to Service shop-api on port 80; all other paths must go to Service shop-web on port 80. On this lab, curl -s -H 'Host: shop.local' localhost:8080/hostname must be answered by a shop-web Pod and curl -s -H 'Host: shop.local' localhost:8080/api/orders by a shop-api Pod.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-ing-1/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-ing-1/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-ing-1/cleanup | bash
Tasknw-ing-2
Networking5%~6 min

context · kubectl config use-context kind-cka

Terminate TLS on an Ingress

A certificate and key for secure.shop.local are in /tmp/cka/nw-tls/tls.crt and /tmp/cka/nw-tls/tls.key. In namespace nw-tls, store them in a TLS Secret named secure-tls. Then create an Ingress named secure with IngressClass nginx that routes all paths of host secure.shop.local to Service secure-web on port 80 and terminates TLS for that host with the Secret. On this lab, https://secure.shop.local:8443 (resolved to 127.0.0.1) must serve that certificate.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-ing-2/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-ing-2/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-ing-2/cleanup | bash

Finished reading?

0 of 2 tasks passed. Readiness counts tasks, so run them before you move on.