13.6 Annotations for Controller-Specific Features
Alright, let’s get our hands dirty with annotations. Forget the dry, academic definition. Think of annotations as the little sticky notes you slap onto your Kubernetes YAML to give the Ingress controller very specific, often non-standard, instructions. They’re how you access the real power—and the real quirks—of your chosen controller (NGINX, Traefik, HAProxy, etc.).
The crucial thing to remember, the hill I will die on explaining, is this: Annotations are controller-specific. An annotation that works magic with the NGINX Ingress Controller will be completely ignored by Traefik, and vice versa. This is the biggest “gotcha” in the entire Ingress ecosystem. It means your manifests are subtly (or not so subtly) tied to your implementation choice. It’s not portable, and we all just have to live with that.
The Canonical Example: Rewrites
Let’s start with a common headache: your application lives at a different path than the one users hit. Maybe your backend service is at /api, but you want users to hit /v1. This is where the nginx.ingress.kubernetes.io/rewrite-target annotation comes to the rescue (or, more accurately, implements a basic web server feature that should be simpler).
The rewrite-target annotation tells the NGINX controller to rewrite the path before forwarding the request. The catch is the capture groups. You use a regex in your path to capture the part you want, and the rewrite-target uses that capture.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-api-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- host: api.my-domain.com
http:
paths:
- path: /v1(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: api-service
port:
number: 8080
Why the /$2? Because the regex (/|$)(.*) creates two capture groups. $1 is the first slash (or end-of-string), and $2 is everything after it. So a request to api.my-domain.com/v1/users/123 gets rewritten to /users/123 before being sent to the api-service. It’s powerful, but the syntax is, frankly, a bit obtuse. I’ve seen more than one engineer stare at this for an hour before the penny drops.
SSL Redirection: Forcing the Good Stuff
You always want HTTPS. Let’s not have that debate. Forcing HTTP to HTTPS is a classic use case, and the NGINX controller makes it dead simple with an annotation that is a clear improvement over the old way of managing this.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- my-domain.com
secretName: my-tls-secret
rules:
- host: my-domain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
Setting ssl-redirect: "true" does exactly what you think: it sends a 308 permanent redirect for any HTTP request to the HTTPS version. Note the quotes around "true"—this is a YAML quirk. The value is a string, not a boolean. Forgetting those quotes is a rite of passage; consider it your initiation into the club.
Configuration Snippets: The Power and the Danger
Sometimes the baked-in annotations aren’t enough. You need to drop in a raw snippet of NGINX configuration. This is where you go from user to wizard, but the power comes with immense responsibility. You can seriously break things.
The server-snippet or configuration-snippet annotations inject blocks of code directly into the generated NGINX config.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: snippet-demo
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Custom-Header: Value";
if ($host = 'special.my-domain.com') {
more_set_headers "X-Special: true";
}
spec:
rules:
- host: my-domain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
WARNING: See that if statement? NGINX if statements are notoriously tricky and can have unintended side-effects. Use snippets sparingly, test relentlessly, and only when no standard annotation exists. This is your “break glass in case of emergency” tool.
The Other Players: Traefik and Others
It would be unfair to only pick on NGINX. Traefik uses its own annotations, prefixed with traefik.ingress.kubernetes.io/. Their approach is often more structured, using labels on the Service object as well. For example, to define a middleware for rate limiting, you might use an annotation on the Ingress to apply the middleware, while the middleware itself is a separate CRD. It’s a different philosophy—more declarative and Kubernetes-native, but also more complex.
The lesson is the same: read your controller’s documentation. There is no standard. The ingress-nginx project has one set of annotations, Traefik has another, HAProxy yet another. Assuming otherwise is a one-way ticket to frustration city. This is the rough edge the designers gave us, and we just have to be smart enough to navigate it.