Erste Schritte
Wir gehen davon aus, dass Traefik bereits in irgendeiner Weise installiert ist.
Um sie nutzen zu können, müssen die CRDs verfügbar gemacht werden. Zum Check reicht ein kubectl api-resources --api-group=traefik.containo.us
, das ungefährt so aussieht:
$ kubectl api-resources --api-group=traefik.containo.us
NAME SHORTNAMES APIVERSION NAMESPACED KIND
ingressroutes traefik.containo.us/v1alpha1 true IngressRoute
ingressroutetcps traefik.containo.us/v1alpha1 true IngressRouteTCP
ingressrouteudps traefik.containo.us/v1alpha1 true IngressRouteUDP
middlewares traefik.containo.us/v1alpha1 true Middleware
middlewaretcps traefik.containo.us/v1alpha1 true MiddlewareTCP
serverstransports traefik.containo.us/v1alpha1 true ServersTransport
tlsoptions traefik.containo.us/v1alpha1 true TLSOption
tlsstores traefik.containo.us/v1alpha1 true TLSStore
traefikservices traefik.containo.us/v1alpha1 true TraefikService
Sollte man hier nichts zurückbekommen, darf man nacharbeiten; bei einer Installation z.B. via Helm sollte das aber nicht mehr nötig sein. Zum Glück braucht’s nicht viel zu erledigen:
$ kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v2.9/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
Das v2.9
ist natürlich entsprechend zu ersetzen. Und wenn man außerhalb einer Entwicklungsumgebung arbeitet, ist es umso dringender, dass man hier nicht einfach zufälliges Zeug aus dem Internet herunterlädt – man sollte sich die YAML-Datei vor dem Herunterladen nochmal zu Gemüte führen. Kleiner Tipp: man sollte ausschließlich solche Objekte vorfinden:
kind: CustomResourceDefinition
spec:
group: traefik.containo.us
Zusätzlich sollte geprüft werden, dass die ClusterRole
für den bereits installierten Traefik auch auf die CRDs zugreifen kann… (wer einen nicht-schmerzhaften Weg findet, diese Abfrage mit jsonpath zu machen, möge sich melden.)
$ kubectl get clusterrole traefik -o json | jq '.rules[] | select(.apiGroups | index("traefik.containo.us"))'
{
"apiGroups": [
"traefik.containo.us"
],
"resources": [
"ingressroutes",
"ingressroutetcps",
"ingressrouteudps",
"middlewares",
"middlewaretcps",
"tlsoptions",
"tlsstores",
"traefikservices",
"serverstransports"
],
"verbs": [
"get",
"list",
"watch"
]
}
Das alles unter der Voraussetzung, dass die ClusterRole
traefik
heißt, sonst darf man sich mit kubectl get clusterrolebindings.rbac.authorization.k8s.io -l app.kubernetes.io/instance=traefik -o=jsonpath='{.items[].roleRef.name}
behelfen, um die hoffentlich richtige ClusterRole
zu finden.
Falls keine entsprechende Freigabe zu finden ist, kann man entweder den obigen Output mit in die ClusterRole
reinpatchen, oder sich per kubectl an der Upstream-Definition bedienen — das “wie” bleibt als Übung für den Leser.
So gewappnet, können wir nun eine erste Implementation wagen.
Einfacher Ingress mit TLS
Als Grundlage für unser erstes Beispiel nehmen wir folgendes Deployment an:
$ kubectl apply -f - <<EOF
kind: Deployment
apiVersion: apps/v1
metadata:
namespace: default
name: whoami
labels:
app: whoami
spec:
replicas: 2
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: traefik/whoami
ports:
- name: web
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami
spec:
ports:
- protocol: TCP
name: web
port: 80
selector:
app: whoami
EOF
Desweiteren wollen wir unter https://whoami.example.com/
erreichbar sein. (Für dieses Beispiel. In der Realität natürlich nicht, da LetsEncrypt keine Zertifikate für irgendeine der Beispieldomains ausstellen kann, aber wir halten uns hier an RFC 6761.)
Wir gehen davon aus, dass whoami.example.com
in irgendeiner Form auf den Cluster zeigt, damit wir uns hier nicht noch sinnlos mit DNS beschäftigen.
Folgende Ziele sollen also erreicht werden:
- Ein TLS-Zertifikat für
whoami.example.com
bei LetsEncrypt anfordern. - Einen
IngressRoute
(nota bene:Ingress***Route***
), das Äquivalent eines ‘HTTP router’ von Traefik.
TLS-Zertifikat
Wenn wir schon von Annotationen wegwollen, macht es Sinn, dies bei TLS zu machen. Während wir sonst über ein Ingress
-Label der Form traefik.ingress.kubernetes.io/router.tls.certresolver: le
darauf gewartet haben, dass alles läuft, müssen wir hier also einen anderen Weg gehen.
Es gibt folgende Optionen:
- Traefik mit der Kommunikation mit LetsEncrypt beauftragen.
- Den Weg über cert-manager beschreiten.
Per Traefik
- Klappt es nicht, sobald Traefik nicht mehr als einzelne Node, sondern hochverfügbar benutzt wird. Traefik hat hier eine Synchronisation unter den Nodes aus “Performancegründen” abgeschaltet, bietet sie aber im kostenpflichtigen Traefik Enterprise weiterhin an; somit kann die Open Source-Variante nicht sicherstellen, dass eine
HTTP-01
-Challenge von LE an den richtigen Pod durchgereicht wird. - Kann man unter Benutzung der
TLS-ALPN-01
-Challenge nicht deterministisch arbeiten; es gibt ein Henne-Ei-Problem mit der IngressRoute und dem Erstellen des TLS-Zertifikates. Zum Glück braucht die fast niemand.
Setzen wir also für unseren Setup-Fall voraus, dass wir hier mit einer HTTP-01
-Challenge arbeiten. Für Produktivsysteme würden wir eine DNS-01
-Challenge empfehlen, da dies komplett ohne solche Abhängigkeiten wie oben erledigt werden kann; aber die Dokumentation dessen sprengt den Rahmen dieses Artikels.
Als Voraussetzung ist für die HTTP-01
-Challenge sicherzustellen, dass in der “statischen Konfiguration” (lies: fast immer die Parameter im Deployment
app=traefik
) etwas wie
- "--entrypoints.web.address=:80"
- "--entrypoints.web.address=:443"
[...]
- "--certificatesresolvers.lehttp.acme.email=admin@example.com"
- "--certificatesresolvers.lehttp.acme.httpChallenge.entrypoint=web"
- "--certificatesresolvers.lehttp.acme.storage=lehttp.json"
- "--certificatesresolvers.lehttp.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory"
stehen muss. Der Name (hier: lehttp
) kann natürlich variieren.
Danach können wir folgende IngressRoutes definieren:
$ kubectl apply -f - <<EOF
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingress-whoami-plain
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`whoami.example.com`)
kind: Rule
services:
- name: whoami
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingress-whoami-tls
namespace: default
spec:
entryPoints:
- websecure
routes:
- match: Host(`whoami.example.com`)
kind: Rule
services:
- name: whoami
port: 80
tls:
certResolver: lehttp
...
EOF
Hier sollte dann nach kurzer Zeit zum Resolven der Anfrage alles ordentlich funktionieren.
Per cert-manager
Hierzu werde ich beizeiten noch was Informatives zusammenfassen; kurzer Spoiler: Hier kann man Zertifikate auch per CRD definieren.
Testen
Nun sollte mit haushaltsüblichen Mitteln eigentlich alles funktionieren:
$ curl https://whoami.example.com/
Hostname: whoami
IP: 127.0.0.1
IP: ::1
IP: 192.168.67.194
IP: fe80::386c:dff:fe6d:7ee
RemoteAddr: 127.0.0.1:58308
GET / HTTP/1.1
Host: whoami.example.com
User-Agent: curl/7.86.0
Accept: */*
Traefik Service
Traefik bietet mit seinen CRDs die Möglichkeit, Loadbalancing zu konfigurieren.
Zum Beispiel auf Basis des oben Erarbeiteten:
$ kubectl apply -f - <<EOF
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: whoami
namespace: default
spec:
weighted:
services:
- name: whoami-production
kind: TraefikService
weight: 99
- name: whoami-experimental
weight: 1
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: whoami-production
namespace: default
spec:
weighted:
services:
- name: whoami-vohburg
port: 80
weight: 1
- name: whoami-koeln
port: 80
weight: 1
EOF
Hier werden nun 99% der Requests aufgeteilt zwischen den Services whoami-vohburg
und whoami-koeln
, und 1% der Requests werden an einen Service whoami-experimental
geleitet, für z.B. Testdeployments. Man kann hier noch komplizierter machen: Requests können gespiegelt werden, es gibt granulare Optionen, die Stickyness zu konfigurieren, etc.
Middlewares
Traefiks Middlewares können auch als CRD verwendet werden. Als simples Beispiel können wir einen OIDC-Login vor einen Ingress packen:
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: oidc-authz
namespace: traefik-system
spec:
forwardedAuth:
address: 'https://auth.example.com/oauth2/sign_in' # assumed to be oauth2-proxy
trustForwardHeader: true
passHostHeader: false
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: whoami-oidc
spec:
entryPoints:
- websecure
routes:
- match: Host(`whoami.example.com`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: oidc-authz
namespace: traefik-system
tls:
certResolver: ledns
Weitere CRDs
Es gibt noch einige weitere CRDs, die hier erwähnt werden sollten:
IngressRouteTCP
,IngressRouteUDP
,MiddlewareTCP
: Unsere Beispiele von oben mit entsprechend anderen Grundansätzen als allgemeine TCP/UDP-Dienste, nicht HTTP.TLSOptions
,TLSStore
: Konfiguriert Details zu den TLS-Implemantation, wie z.B. verwendete Algorithmen.TLSStore
ist zum Zeitpunkt des Schreibens fast sinnlos, da es einem nur erlaubt, das Default-Zertifikat zu ändern; die Verwaltung mehrerer Stores macht Traefik allgemein noch nicht.ServersTransport
: Definitionen zur Backend-Verbindung; hier können TLS-Clientzertifikate, timeouts usw. festgelegt werden.
Die offizielle Übersicht über all die CRDs befindet sich in der Dokumentation.
Ausblick
Wie hier bereits schon angekündigt werden wir uns demnächst mal mit cert-manager
beschäftigen. Und zwar nicht nur im Kontext traefik
, sondern auch bei ingress-nginx
.