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.controllernames the controller implementation. An Ingress picks one withspec.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 workspathType | /cart matches | Doesn't match |
|---|---|---|
Exact | /cart only | /cart/, /cart/items |
Prefix | /cart, /cart/, /cart/items (split on /, element by element) | /carts, /cartography |
ImplementationSpecific | Whatever the controller decides | Not portable; avoid unless told to use it |
pathTypeis required innetworking.k8s.io/v1.- When several paths match, the longest wins; on a tie,
ExactbeatsPrefix. hostcan be a wildcard for one DNS label:*.quill.testmatchesapi.quill.testbut nota.b.quill.testorquill.test. A rule withouthostmatches 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/tlsspec:
tls:
- hosts:
- shop.quill.test
secretName: quill-tls # same namespace as the Ingress- The Secret must hold
tls.crtandtls.keyand 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:80Exam 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
With no class and no default IngressClass, neither controller claims the Ingress. Naming the class (or marking one class as default) fixes it. Restarting controllers doesn't change ownership. pathType doesn't affect which controller picks the object up. Ingress backends must be in the Ingress's own namespace, so moving it breaks the backends.
Prefix matches whole path elements, so /v1/users matches and /v1beta/users doesn't. The host must match the
rule. Path matching is case-sensitive, so /V1 doesn't match.
Ingress TLS Secrets are looked up in the Ingress's namespace, so the controller can't find kite-tls and falls back to
its default certificate. The Secret should be type kubernetes.io/tls. pathType and defaultBackend have nothing to do
with certificate selection.
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.
k -n quill create ingress quill --class=quill-proxy \
--rule="shop.quill.test/cart*=cart:80,tls=quill-tls" \
--rule="shop.quill.test/*=storefront:80" \
--dry-run=client -o yaml > quill-ing.yaml
grep -n pathType quill-ing.yaml # both should be Prefix
k apply -f quill-ing.yamlk -n quill get ingress quill # ADDRESS set
k -n quill describe ingress quill # both backends list endpoints
IP=$(k -n quill get ingress quill -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl -s -H 'Host: shop.quill.test' http://$IP/cart/items # cart
curl -s -H 'Host: shop.quill.test' http://$IP/about # storefront
curl -sk --resolve shop.quill.test:443:$IP https://shop.quill.test/cartIf the controller is exposed by NodePort instead of a load balancer, use a node IP and the controller Service's node port.
Further reading
Gateway API
GatewayClass, Gateway and HTTPRoute (gateway.networking.k8s.io/v1), listeners and allowedRoutes, path, header and method matching, filters, weighted traffic splitting, ReferenceGrant and migrating from Ingress.
CoreDNS
DNS names for Services and Pods, how a Pod's resolv.conf is built, dnsPolicy and dnsConfig, the CoreDNS Corefile and its plugins, forwarding a private zone, and a step-by-step for debugging resolution.