Beyond Annotation Soup: Declarative Middleware with Traefik IngressRoute CRDs
Standard Kubernetes Ingress annotations hit a ceiling when you need rate limits and header rewrites. Traefik's IngressRoute CRD moves middleware into first-class Kubernetes resources—here's how it works and what it costs.
17 Mar 2026, 16:19 UTC

You need to rate-limit /api, strip a version prefix from /v2, and add security headers everywhere else. The standard Kubernetes Ingress resource gives you host and path matching, but anything beyond that lands in annotations. Those annotations are controller-specific strings, impossible to validate with kubectl apply --dry-run=server, and often undocumented. When your routing logic outgrows the Ingress spec, you either push middleware into application code or deploy a second proxy. Traefik's IngressRoute CRD offers a third path: declare routing and middleware as native Kubernetes objects.
The thesis is simple. IngressRoute, a Custom Resource Definition (CRD) introduced in Traefik v2.x, replaces the generic Ingress resource with Traefik-specific routing objects. It exposes middleware—rate limiting, header manipulation, path rewriting—as separate Middleware resources that routes reference by name. Because Traefik watches the Kubernetes API directly, applying a new route or middleware takes effect without restarting the proxy pod.
The annotation ceiling
Standard Ingress is deliberately minimal. The spec covers hosts, paths, and backend services. Everything else—SSL redirects, basic auth, rate limits—is left to annotations. That design keeps Ingress portable, but it creates a practical ceiling. Annotations are unstructured strings. A typo in a rate-limit value fails silently at the controller, not at the cluster API. Worse, there is no standard way to say "apply this middleware only to POST requests on /api" without controller-specific syntax that breaks when you switch providers.
Middleware as a Kubernetes object
IngressRoute moves middleware out of annotation strings and into first-class resources. You define a Middleware object once, then attach it to any route by name and namespace. The same rate-limit object can protect multiple services. Because these are normal Kubernetes resources, they appear in kubectl get, respect RBAC, and can be generated by Helm or Kustomize like any other manifest. Traefik v2.x also lets a single IngressRoute span multiple entrypoints and TLS hostnames, so you do not need separate Ingress objects for HTTP, HTTPS, and TCP traffic.
Worked example: rate limiting /api
Assume Traefik v2.x is running in the traefik namespace with the kubernetesCRD provider enabled. The manifests below assume the traefik.io/v1alpha1 API group. Some Traefik v2.x releases use traefik.containo.us/v1alpha1; confirm the group with kubectl get crd middlewares.traefik.io -o yaml and adjust accordingly.
First, create a reusable rate-limit middleware:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: api-rate-limit
namespace: traefik
spec:
rateLimit:
average: 100
burst: 50
Then reference it from an IngressRoute:
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: api-route
namespace: traefik
spec:
entryPoints:
- web
routes:
- match: PathPrefix(`/api`)
kind: Rule
services:
- name: api-service
port: 8080
middlewares:
- name: api-rate-limit
namespace: traefik
Run kubectl apply -f middleware.yaml and kubectl apply -f ingressroute.yaml from your workstation or CI pipeline with cluster-admin or equivalent namespace write permissions. Confirm registration with kubectl get ingressroute -n traefik, then inspect the applied middleware chain with kubectl describe ingressroute api-route -n traefik. To verify behavior, send a burst of requests to /api using a tool such as hey or wrk. Once traffic exceeds the configured average and burst, responses should begin returning HTTP 429. If they do not, check that the kubernetesCRD provider is active and that the middleware namespace matches the route namespace. When you are finished, remove the objects with kubectl delete -f ingressroute.yaml -f middleware.yaml.
What it costs you
IngressRoute is not free. It locks your manifests to Traefik; other ingress controllers will ignore the CRDs. In large clusters, hundreds of Middleware and IngressRoute objects increase the size of the Kubernetes API server's etcd store—the key-value database behind the cluster state—and can add watch latency. The apiVersion for these CRDs has also shifted between Traefik v2 minor versions, so CI/CD pipelines that pin an old version may fail after an upgrade. Finally, you lose the broad ecosystem of tools that assume standard Ingress.
Where to start
Pick one pain point—likely rate limiting or header injection—and migrate a single non-critical route to IngressRoute. Keep the standard Ingress for the rest of your fleet until the middleware model proves out. Before you upgrade Traefik, diff the installed CRD versions against your manifests. If you operate at scale, monitor etcd size after adding the first fifty Middleware objects; the growth is linear, but the operational surprise does not have to be.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.