Asterrr's Handbook

Ingress

Ingress resources and controllers, IngressClass and the default class, host and path rules, Exact vs Prefix vs ImplementationSpecific path types, TLS Secrets, and kubectl create ingress.

Exam tasks: 3.5 (know how to use Ingress controllers and Ingress resources), with 5.5 when an Ingress returns 404 or 503

The decision: which controller (IngressClass) should serve this Ingress, and which host, path and pathType send each request to the right Service port?

Resource vs controller

  • An Ingress is only configuration: HTTP(S) hosts and paths mapped to Services. Kubernetes ships no controller.
  • An Ingress controller (a proxy running in Pods) watches Ingresses of its class and does the routing. It's itself exposed through a NodePort or LoadBalancer Service.
  • An IngressClass links the two: spec.controller names the controller implementation. An Ingress picks one with spec.ingressClassName.
  • Ingresses with no class use the class annotated ingressclass.kubernetes.io/is-default-class: "true". If there's no default, they're ignored.
k get ingressclass                        # NAME, CONTROLLER; the default is marked in the annotation
k get pods -A | grep -i -E 'ingress|proxy' # is a controller actually running?

Legacy: use another Ingress controller or Gateway API instead

The community Ingress NGINX controller (kubernetes/ingress-nginx) was retired in March 2026 and no longer gets releases or security fixes. Existing installs keep running, and its annotations still appear in older manifests. New work should use a maintained controller or Gateway API. The Ingress API itself is GA and frozen: it isn't being removed, but it gets no new features.

Rules and path types

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: quill
  namespace: quill
spec:
  ingressClassName: quill-proxy
  defaultBackend:                 # anything no rule matches
    service:
      name: storefront
      port:
        number: 80
  rules:
  - host: shop.quill.test
    http:
      paths:
      - path: /cart
        pathType: Prefix
        backend:
          service:
            name: cart
            port:
              number: 80
      - path: /healthz
        pathType: Exact
        backend:
          service:
            name: storefront
            port:
              name: http          # a named Service port also works
pathType/cart matchesDoesn't match
Exact/cart only/cart/, /cart/items
Prefix/cart, /cart/, /cart/items (split on /, element by element)/carts, /cartography
ImplementationSpecificWhatever the controller decidesNot portable; avoid unless told to use it
  • pathType is required in networking.k8s.io/v1.
  • When several paths match, the longest wins; on a tie, Exact beats Prefix.
  • host can be a wildcard for one DNS label: *.quill.test matches api.quill.test but not a.b.quill.test or quill.test. A rule without host matches every host.
  • The backend Service must be in the same namespace as the Ingress, and the port is the Service port.

Prefix is not a string prefix

path: /api with Prefix doesn't match /apiv2, because matching is per path element. And a trailing slash in the rule is ignored: /api/ with Prefix still matches /api. If a task's sample URLs mix these, test each one.

TLS

k -n quill create secret tls quill-tls --cert=shop.crt --key=shop.key   # type kubernetes.io/tls
spec:
  tls:
  - hosts:
    - shop.quill.test
    secretName: quill-tls        # same namespace as the Ingress
  • The Secret must hold tls.crt and tls.key and live in the Ingress's namespace.
  • TLS terminates at the controller. Traffic to the backend is plain HTTP unless the controller is told otherwise (controller-specific annotation).
  • HTTP-to-HTTPS redirects and path rewrites are annotations that depend on the controller. That lack of portability is the main reason Gateway API exists.

Imperative shortcut

# rule syntax: host/path=service:port ; a trailing * makes it Prefix, otherwise Exact
k -n quill create ingress quill --class=quill-proxy \
  --rule="shop.quill.test/cart*=cart:80" \
  --rule="shop.quill.test/healthz=storefront:80" \
  --rule="shop.quill.test/*=storefront:80,tls=quill-tls" \
  --default-backend=storefront:80

Exam signal

kubectl create ingress ... --dry-run=client -o yaml is the quickest way to get valid v1 YAML with correct nesting. Check the generated pathType values: forgetting the * gives you Exact where the task wanted Prefix.

Verifying

k -n quill get ingress quill               # ADDRESS filled in means a controller picked it up
k -n quill describe ingress quill          # rules, backends and their endpoints, events
curl -s -H 'Host: shop.quill.test' http://<controller-node-ip>:<nodeport>/cart
curl -sk --resolve shop.quill.test:443:<lb-ip> https://shop.quill.test/cart
  • 404 from the controller: no rule matches (wrong host header, wrong path or pathType, wrong class).
  • 503: the rule matched but the Service has no ready endpoints.
  • No ADDRESS: no controller for that class, or the controller isn't running.

Scenarios

Scenario
Ingress docs in namespace wiki has no ingressClassName. The cluster has two IngressClasses, edge-a and edge-b, neither marked as default. The Ingress has no ADDRESS after ten minutes and requests return 404 from both controllers. What should you do?
Scenario
An Ingress has one rule: host api.pine.test, path /v1, pathType Prefix, backend api-v1:80. Which request reaches api-v1?
Scenario
An Ingress for shop.kite.test is configured with tls secretName kite-tls. The controller serves its own self-signed default certificate instead. kite-tls exists in namespace certs; the Ingress is in namespace shop. What is wrong?

Drill

kubectl config use-context lab-ing

An Ingress controller with IngressClass quill-proxy is installed. In namespace quill, Services cart and storefront listen on port 80.

Create Ingress quill for host shop.quill.test so that /cart and everything under it goes to cart, all other paths go to storefront, and HTTPS uses the existing Secret quill-tls.

Further reading

On this page