Die Traefik-Labels im Einzelnen, samt der Dollarfalle

Inhalt
Traefik wird eingerichtet, indem Labels an einen Container geschrieben werden – was nach einer Liste von Einstellungen klingt und in Wahrheit ein Namensraum ist. Die Schlüssel sind Pfade durch einen Baum, und was wie sechs unabhängige Zeilen aussieht, sind drei Gebilde, die nur deshalb voneinander wissen, weil ein Abschnitt mitten im Schlüssel zufällig gleich geschrieben ist.
Diese Bauart macht die Einrichtung kurz und ihre Fehler leise. Nichts prüft einen Namen gegen irgendetwas, ein Vertippen erzeugt also ein zweites, leeres Gebilde statt einer Beschwerde.

Drei Gebilde, ein Name
Ein Router entscheidet, welche Anfragen zu diesem Container gehören, ein Dienst sagt, wohin sie geschickt werden, und eine Middleware verändert sie unterwegs. Jedes lebt unter einem eigenen Zweig von traefik.http, und jedes wird über einen frei gewählten Namen bezeichnet – der Label-Schlüssel ist die Erklärung.
labels:
- "traefik.enable=true"
- "traefik.http.routers.blog.rule=Host(`blog.example.com`)"
- "traefik.http.routers.blog.entrypoints=websecure"
- "traefik.http.routers.blog.tls.certresolver=le"
- "traefik.http.routers.blog.middlewares=blog-auth@docker"
- "traefik.http.middlewares.blog-auth.basicauth.users=admin:$$2y$$05$$e6zX…"
- "traefik.http.services.blog.loadbalancer.server.port=8080"
Router und Dienst sind gebunden, weil beide blog heißen. Es gibt keine Zeile, die das sagt. Wer traefik.http.services.blogg.loadbalancer.server.port schreibt, erzeugt einen Dienst namens blogg, auf den nichts zeigt, und einen Router namens blog ohne Dienst – und Traefik beantwortet Anfragen an diesen Namen mit einer 404, statt zu sagen, was fehlt.
Belegt der Container genau einen Port, kann das Dienst-Label ganz entfallen, Traefik schließt es sich zusammen. Diese Bequemlichkeit zu kennen lohnt vor allem, um sie in fremden Dateien wiederzuerkennen – sobald ein zweiter Port auftaucht, endet das Schließen, und die Route bricht in dem Augenblick, in dem ein völlig unbeteiligter Port hinzukommt.
Der Zusatz für die Quelle
Middlewares werden über ihren Namen angesprochen, und der Name allein genügt nicht. Traefik führt je Quelle einen eigenen Namensraum – die Docker-Labels, eine statische Datei, eine Kubernetes-Ressource -, und ein bloßes blog-auth wird in der fragenden Quelle gesucht, mit einem Rückfall, der oft genug überrascht, um es auszusprechen: Der Verweis gehört als blog-auth@docker geschrieben.
Das falsch zu machen, ist der leiseste der drei Fehler. Der Router ist gültig, der Dienst ist gültig, die Route funktioniert – sie liefert die Anwendung bloß ohne die Anmeldung aus, die davor stehen sollte. Nichts im Protokoll erwähnt eine übersprungene Middleware, denn aus Traefiks Sicht wurde keine verlangt.
Die Dollarfalle
Ein bcrypt-Streuwert sieht aus wie $2y$05$e6zX... und besteht zu einem guten Teil aus Dollarzeichen. Docker Compose ersetzt in jedem gelesenen Wert Variablen, aus $2y wird also eine leere Zeichenkette, aus $05 ebenfalls, und das Passwort, das bei Traefik ankommt, ist ein Bruchstück, das auf nichts passt.
# erzeugen
htpasswd -nbB admin "passwort"
admin:$2y$05$e6zXx8Q0m2yYy1H4o1Yb1uJf2Rr7cKq5dW6vT8sN0pL3aG9hC1dS2
# in der docker-compose.yml wird jedes $ verdoppelt
admin:$$2y$$05$$e6zXx8Q0m2yYy1H4o1Yb1uJf2Rr7cKq5dW6vT8sN0pL3aG9hC1dS2
# in einer reinen Etikettendatei oder auf der Befehlszeile bleibt es einfach
Das Verdoppeln ist eine Compose-Regel und keine Traefik-Regel – deshalb muss dieselbe Zeichenkette je nach Aufenthaltsort verschieden geschrieben werden. Ein Streuwert in Traefiks eigener dynamischer Konfigurationsdatei behält seine einfachen Dollarzeichen, derselbe Streuwert in einer Compose-Datei braucht sie doppelt, und eine funktionierende Einrichtung von der einen in die andere zu übertragen, zerlegt sie genau auf diese Weise.
Das Symptom ist eine Anmeldeaufforderung, die jedes Passwort ablehnt, auch das richtige – was dazu führt, dass das Passwort geprüft wird und nicht die Datei, die es aufgefressen hat.
Wie sich jeder Fehler von außen zeigt
| Fehler | Symptom | Wo nachzusehen ist |
|---|---|---|
| Name in Router und Dienst verschieden | 404 für diesen Namen | die Übersicht: ein Router ohne Dienst |
Middleware ohne @docker |
die Seite öffnet ohne Anmeldung | die Übersicht: der Router führt keine Middleware |
| Einfache Dollarzeichen in Compose | 401 bei jedem Passwort | docker inspect: das Label ist kürzer als der Streuwert |
| Container nicht im Proxy-Netz | 502 oder 504 | der Dienst hat keinen erreichbaren Server |
traefik.enable=true fehlt |
404, es erscheint überhaupt nichts | die Übersicht: der Container ist nicht aufgeführt |
| Zwei Container, gleicher Routername | einer gewinnt, scheinbar zufällig | die Übersicht: ein Router, ein Ziel |
Fünf der sechs beantwortet die Übersichtsseite, was dafür spricht, sie auch auf einer kleinen Maschine an einem internen Eingang einzuschalten. Sie zeigt, was Traefik tatsächlich verstanden hat, und jeder dieser Fehler ist ein Unterschied zwischen dem Geschriebenen und dem Verstandenen.
Zwei Gewohnheiten, die das meiste verhindern
Die erste besteht darin, jeden Namen aus dem Dienst abzuleiten und ihn an allen drei Stellen unverändert zu verwenden – Container blog, Router blog, Dienst blog, Middleware blog-auth. Ein mechanisch gebildeter Name lässt sich nicht falsch erinnern, und ein Vergleich der Labels zweier Container zeigt dann nur noch die Zeilen, die sich wirklich unterscheiden.
Die zweite besteht darin, die Labels zurückzulesen statt die Datei. docker inspect zeigt die Werte so, wie der Dienst sie abgelegt hat, nachdem Compose mit dem Ersetzen fertig war – dort wird aus einem verdoppelten Dollarzeichen wieder ein einfaches, und dort ist einem überlebenden Streuwert anzusehen, dass er genauso lang ist wie der erzeugte.