Konfiguration von Traefik mit CRDs

15.12.2022 | Tobias Wolter in howto

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:

  1. Ein TLS-Zertifikat für whoami.example.com bei LetsEncrypt anfordern.
  2. 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:

  1. Traefik mit der Kommunikation mit LetsEncrypt beauftragen.
  2. Den Weg über cert-manager beschreiten.

Per Traefik

  1. 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.
  2. 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.

Tobias Wolter
Tobias Wolter
towo macht seit dem letzten Jahrtausend Dinge mit Linux und verdient seit 2013 bei der B1 damit sein Brot. Für einen Wechsel zu was mit Holz ist es zu spät, deswegen passiert das nur in der Freizeit.

 


Haben Sie Anmerkungen oder Nachfragen? Melden Sie sich unter blog%b1-systems.de
Col 2