Sign in
← Kubernetes: networking
10 min · medium · Lesson 7 of 8

Ingress: routing by domain and path

One entry point on 80/443, with rules that route traffic to Services by domain and path.

One entry point on 80/443, with rules that fan traffic out to Services by domain and path.

Why Ingress exists

A Service can expose an app outside the cluster. NodePort gives you a port on every node, LoadBalancer gives you a public IP. Both work while there's one Service. With ten of them, it falls apart: ten different ports or ten IPs, TLS set up by hand on each, no routing by domain at all.

Ingress solves a different problem. One controller listens on 80/443, reads rules of the form "this host or path goes to this Service", and fans the traffic out. One public address, many apps behind it.

Fan-out by host: one controller at the front, then the request goes to the right Service based on the Host header.

                     /-- api.example.com   --> Service api
client --> Ingress --+-- web.example.com   --> Service web
                     \-- shop.example.com  --> Service shop

Ingress is a declaration, not a controller

IngressClass in the cluster name: traefik (kubectl get ingressclass) Ingress · spec ingressClassName: traefik rules: helloapp.example.com string match
The same string in two objects: if it diverges, no one picks up the rule.

The Ingress object itself does nothing. It's a record in etcd: "here are my rules, someone go act on them." The work is done by the Ingress Controller: a separate Pod that reads Ingress objects through the API and configures itself as an HTTP proxy.

An Ingress in a cluster with no controller just sits there, pointless: the API accepts it, kubectl get ingress shows it, but no traffic goes anywhere and the ADDRESS column stays empty. And in a cluster with two controllers (nginx and traefik), you need to say explicitly which one takes the rule.

That's what the ingressClassName field does. It points to an IngressClass object, which describes a controller at the cluster level:

kubectl get ingressclass
NAME      CONTROLLER                      PARAMETERS   AGE
traefik   traefik.io/ingress-controller   <none>       42d

The name in ingressClassName is compared to the NAME column character for character. The API doesn't check that such a class exists: an Ingress with a typo in the class name is created without an error, it just never gets picked up by any controller.

If one IngressClass has the annotation ingressclass.kubernetes.io/is-default-class: "true", an Ingress without ingressClassName gets picked up by it. Don't rely on this behavior; set the class explicitly.

The examples below all use ingressClassName: traefik. The manifest itself doesn't depend on the controller: only the class name changes, the rules, host and pathType fields stay the same.

ingress-nginx is unmaintained, but it's almost everywhere

For years the most common controller was ingress-nginx, with class nginx. Its commands, kubectl apply -f .../ingress-nginx/..., are what sit in half of all repos, articles and old courses.

On March 24, 2026, the kubernetes/ingress-nginx repository was archived. The last release is controller-v1.15.1, with support claimed up to cluster version 1.35. There will be no new releases, bugfixes, or security patches. The reason is simple: one or two maintainers working in their spare time, and accumulated tech debt that made safe maintenance unworkable. The InGate project, meant as a successor, was also shelved.

It's easy to overstate the scale of this. This is about the controller, not the Ingress API. The kind: Ingress object in group networking.k8s.io/v1 stays GA and isn't going anywhere. Kubernetes docs call its API frozen: stable, but no new features are coming; they go into the Gateway API instead. One specific implementation lost support.

Practice didn't change overnight because of this. According to an estimate in the Kubernetes Steering Committee's statement, ingress-nginx runs in roughly half of cloud-native environments, and as of September 2026 it's still running in production for a huge number of teams. You'll almost certainly run into it at a new job. You need to be able to read its annotations (nginx.ingress.kubernetes.io/*).

What this means in practice:

Check whether it's in a given cluster:

kubectl get pods --all-namespaces \
  --selector app.kubernetes.io/name=ingress-nginx

A separate trap in learning clusters: minikube addons enable ingress still deploys ingress-nginx specifically. kind's docs were rewritten: ingress there works through cloud-provider-kind (since v0.9.0), no third-party controller needed.

Minimal manifest

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: helloapp
spec:
  ingressClassName: traefik
  rules:
    - host: helloapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: helloapp
                port:
                  number: 8080

Breakdown:

host is compared to the Host header's value as a string, with no DNS involved. The controller doesn't care where a DNS record points: curl -H 'Host: helloapp.example.com' http://<controller address>/ hits this rule, but a request to the same address by IP with no header doesn't. A wildcard on one label is allowed: *.example.com catches foo.example.com, but not foo.bar.example.com or example.com.

pathType: three options

- path: /api
  pathType: Prefix

When several rules match, the longest match wins. /api/users beats /api, and /api beats /. If the length is the same, Exact beats Prefix. Order in the YAML doesn't matter.

The imperative way

kubectl create ingress saves time:

kubectl create ingress helloapp \
  --class=traefik \
  --rule="helloapp.example.com/*=helloapp:8080" \
  --dry-run=client -o yaml

The output is ready-made YAML that you just finish and apply. Rule syntax: host/path=service:port. A trailing asterisk on the path gives pathType: Prefix, without it you get Exact. The --rule flag can be repeated to describe several paths in one Ingress.

TLS

A certificate attaches directly to the Ingress:

spec:
  tls:
    - hosts: [helloapp.example.com]
      secretName: helloapp-tls
  rules: [...]

secretName points to a Secret of type kubernetes.io/tls with keys tls.crt and tls.key. The controller pulls the certificate and terminates TLS itself. The tls block isn't always needed: if a proxy in front of the cluster already decrypts HTTPS, plain HTTP is what reaches the Ingress. It's still worth knowing the syntax: it shows up often in production clusters.

Commands for investigating

kubectl get ingressclass
kubectl get ingress
kubectl describe ingress helloapp

describe shows the class, the rules, and each path's backend. Next to the backend, kubectl prints the Pod addresses it leads to, or an error:

Rules:
  Host        Path  Backends
  ----        ----  --------
  shop.test
              /   webapp:80 (<error: services "webapp" not found>)

If a rule wasn't picked up by a working controller, the reason is usually in the Events section of the same describe.

What usually breaks

Ingress and the Gateway API

Ingress stays GA and runs in most clusters, but it has a limit: all the configuration (host, path, TLS, fine-grained settings) lives in one object and gets extended through vendor annotations. Its successor is the Gateway API, a newer standard. It splits routing across several objects with different owners and supports TCP, gRPC, and traffic weighting out of the box. For a new project, it's worth looking at. Ingress is taught here because it's everywhere and you need to be able to read it. The difference is covered further along in the track.

Homework

Practice the theory on your own cluster: 1 step with a check. Set up the cluster with the Kubernetes locally track, the tasks live in the k8s-practice repository.

./run.sh start k8s-ingress-basics     # set up the starting state
./run.sh verify k8s-ingress-basics    # check the current step

In short

What you'll need for the tasks

The labs in this lesson have no controller. You write and fix rules, and check them against what the API shows: class, namespace, backend, and its addresses.

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-network-07-ingress