Ingress & routing
Your app is published through the platform's shared ingress controller (Traefik). You describe your hostnames and your routes in your repo; DNS and the TLS certificate follow automatically. Because every app shares that one controller, a few rules keep one app from touching another's traffic. A manifest that breaks one of them is rejected at sync time with a message naming the rule - nothing breaks, it just doesn't go live until fixed.
Ingress - the default
Copy ingress.yaml from the example repo. What matters:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod # TLS certificate
spec:
ingressClassName: traefik
tls:
- hosts: ["my-app.inf.ethz.ch"]
secretName: my-app-tls
rules:
- host: "my-app.inf.ethz.ch" # required on every rule
http:
paths:
- path: /
pathType: Prefix
backend:
service: {name: my-app, port: {number: 80}}
The DNS record is created for you - don't set an external-dns annotation.
The rules (Ingress, HTTPRoute, Gateway)
HOSTNAMES
- every Ingress rule needs a `host`; every HTTPRoute needs `hostnames`;
every Gateway listener needs a `hostname`
- no wildcards (`*.example.ch`) - list each hostname
- no `spec.defaultBackend` on an Ingress
- a hostname belongs to the first namespace that claims it (cluster-wide,
across Ingress, HTTPRoute and Gateway). Several objects in YOUR namespace
may share a hostname (e.g. to split paths).
INGRESS ANNOTATIONS
traefik.ingress.kubernetes.io/ only these four:
router.entrypoints web and/or websecure
router.middlewares Middlewares of your own namespace (see below)
router.pathmatcher
router.tls
(router.priority, router.tls.options, ... are rejected)
nginx.ingress.kubernetes.io/ the cluster does not run ingress-nginx.
These annotations are ignored; a chart that ships them still deploys.
Rejected outright: *-snippet, auth-url, server-alias,
from-to-www-redirect, client-body-buffer-size, proxy-buffer-size,
proxy-buffers-number.
SERVICE ANNOTATIONS
traefik.ingress.kubernetes.io/ only service.serversscheme,
service.passhostheader, service.nativelb, service.sticky.*
Middlewares
Redirects, header changes, path rewrites, basic auth, IP allow-lists and rate limits
are Traefik Middleware objects you create in your own namespace:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: security-headers
spec:
headers:
stsSeconds: 31536000
customResponseHeaders:
X-Frame-Options: DENY
Attach it on the Ingress as <namespace>_<name>@kubernetescrd - an underscore
between your namespace and the Middleware's name, several separated by commas:
metadata:
annotations:
traefik.ingress.kubernetes.io/router.middlewares: my-namespace_security-headers@kubernetescrd
ALLOWED Middleware kinds:
addPrefix, stripPrefix, stripPrefixRegex, replacePath, replacePathRegex,
redirectRegex, redirectScheme, headers, contentType, compress,
encodedCharacters, grpcWeb, basicAuth, digestAuth, passTLSClientCert,
ipAllowList, rateLimit (in-memory), inFlightReq, circuitBreaker, retry,
chain (Middlewares of your own namespace only)
NOT ALLOWED (they act with the shared controller's identity or resources):
forwardAuth, buffering, errors, plugin, rateLimit.redis
Need external authentication in front of your app? Run an auth proxy (e.g.
oauth2-proxy) as part of your app instead of forwardAuth.
Rate limiting
The platform only has a coarse safety net: one client IP gets at most 30 requests in
flight at a time, anything beyond that is answered 429. If you want real rate limiting -
e.g. to keep crawlers from hammering an expensive endpoint such as a search - do that in
your own Middlewares:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: search-ratelimit
spec:
rateLimit: # per client IP: 2 requests/s on average, bursts of 10
average: 2
period: 1s
burst: 10
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: search-concurrency
spec:
inFlightReq: # at most 5 requests in flight in total, from anyone
amount: 5
sourceCriterion:
requestHost: true
-
rateLimitstops a single client. Crawlers that come from many addresses pass it - theinFlightReqcap on the whole endpoint is what keeps the rest of your site fast then: excess requests get a429right away instead of queueing up in your app. -
A Middleware applies to every path of the Ingress it is attached to. To limit one path only, give that path its own Ingress (same host,
path: /search) and attach the Middlewares there:metadata: annotations: traefik.ingress.kubernetes.io/router.middlewares: >- my-namespace_search-ratelimit@kubernetescrd,my-namespace_search-concurrency@kubernetescrd -
Also list such endpoints in your
robots.txt(Disallow: /search) - well-behaved crawlers then never get there.
Gateway API (HTTPRoute)
If you prefer the Gateway API over Ingress (header/method matching, traffic weights, ...), ship a Gateway next to your HTTPRoutes - one listener per hostname:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-app
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
gatewayClassName: traefik
listeners:
- name: websecure
protocol: HTTPS
port: 443
hostname: my-app.inf.ethz.ch
allowedRoutes:
namespaces: {from: Same}
tls:
mode: Terminate
certificateRefs:
- {group: "", kind: Secret, name: my-app-tls} # cert-manager creates it
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
spec:
parentRefs:
- {group: gateway.networking.k8s.io, kind: Gateway, name: my-app}
hostnames: ["my-app.inf.ethz.ch"]
rules:
- backendRefs:
- {name: my-app, port: 80}
GATEWAY RULES
gatewayClassName traefik
listeners a literal `hostname` on each; HTTP on port 80 or HTTPS on
port 443 only; at most 16
allowedRoutes from: Same (your own namespace) - also the default
tls mode Terminate, certificateRefs to Secrets in your namespace
not allowed spec.addresses, spec.infrastructure, tls.options,
ReferenceGrant, GRPCRoute / TCPRoute / TLSRoute / UDPRoute
To use a Middleware from an HTTPRoute, reference it with an ExtensionRef filter
(group: traefik.io, kind: Middleware, name: <name>).
Apps that are ETH-internal only
If your app was onboarded as internal (reachable from the ETH network only), it is published on a separate controller:
- Ingress - nothing to do. Class, entrypoint and certificate issuer are set for you at sync time, whatever your manifest says.
- Gateway - state it yourself:
gatewayClassName: traefik-internal, only HTTPS listeners on port 443, and the issuerletsencrypt-prod-internal.
Requesting an exception
Need something from the "not allowed" lists, a wildcard hostname, or non-HTTP traffic? Open a ticket at support@inf.ethz.ch (what to include) - name the object and field, and why your app needs it.