Etykiety Traefika po kolei, wraz z pułapką dolara

Spis treści
Traefika konfiguruje się, pisząc etykiety na kontenerze – co brzmi jak lista ustawień, a w rzeczywistości jest przestrzenią nazw. Klucze to ścieżki po drzewie, a to, co wygląda na sześć niezależnych wierszy, to trzy twory wiedzące o sobie poprzez swoje nazwy – poza usługą, którą Traefik sam dołącza do routera bez service=, dopóki kontener deklaruje dokładnie jedną.
Ta budowa czyni konfigurację krótką, a jej awarie cichymi. Literówka w kluczu tworzy drugi twór zamiast skargi, a nazwa zostaje sprawdzona dopiero tam, gdzie wskazuje na nią router – z wpisem w dzienniku i kodem 404 jako jedynymi sygnałami na zewnątrz.

Trzy twory, jedna nazwa
Router rozstrzyga, które żądania należą do tego kontenera, usługa mówi, dokąd je posłać, a middleware zmienia je po drodze. Każde żyje pod własną gałęzią traefik.http i każde oznacza się dowolnie wybraną nazwą – klucz etykiety jest deklaracją.
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"
Żaden wiersz nie mówi, że router i usługa należą do siebie, i nie wiąże ich wspólna nazwa blog. Gdy router nie wskazuje usługi, Traefik przypisuje mu jedyną usługę zadeklarowaną przez kontener; gdy kontener deklaruje ich kilka, Traefik odrzuca router z wpisem w dzienniku. Literówka w traefik.http.services.blogg.loadbalancer.server.port nie ma więc w tym przykładzie żadnych skutków. Poważnie robi się wtedy, gdy router wskazuje usługę wprost: przy traefik.http.routers.blog.service=blog router odwołuje się do usługi, której nie ma; Traefik oznacza router komunikatem „the service … does not exist” jako błędny i odpowiada na żądania do tej nazwy kodem 404.
Gdy kontener wystawia dokładnie jeden port, etykieta usługi odpada w całości, Traefik dopowie ją sobie. Warto o tym wygodnictwie wiedzieć głównie po to, by rozpoznać je w cudzych plikach – z chwilą pojawienia się drugiego portu Traefik bierze najniższy, a trasa pęka dokładnie wtedy, gdy dochodzi zupełnie niezwiązany, niższy port.
Przyrostek źródła
Middleware wskazuje się po nazwie, a Traefik prowadzi osobną przestrzeń nazw dla każdego źródła – etykiet Dockera, pliku konfiguracji, zasobu Kubernetesa. Samo blog-auth jest szukane w źródle routera, bez odwrotu do innych źródeł. Dla middleware z tych samych etykiet to więc wystarcza, a blog-auth@docker tylko wypisuje to, co i tak obowiązuje; przyrostek jest potrzebny dopiero przy odwołaniu ponad granicą źródła, na przykład blog-auth@file do middleware z pliku konfiguracji Traefika.
Błędne odwołanie – z literówką albo bez przyrostka do middleware z innego źródła – nie otwiera przy tym strony bez logowania. Traefik oznacza router komunikatem „middleware … does not exist” jako błędny, nie uruchamia go i odpowiada na żądania do tej nazwy kodem 404. Cichy pozostaje tylko brak odwołania: router, który nie wymienia middleware na swojej liście, wydaje aplikację bez logowania, które miało przed nią stać, i nic w dzienniku o tym nie wspomina, bo z punktu widzenia Traefika żadnego nie zażądano.
Pułapka dolara
Skrót bcrypt wygląda jak $2y$05$e6zX... i w sporej części składa się ze znaków dolara. Docker Compose podstawia zmienne w każdej odczytanej wartości, z $2y robi się więc pusty napis, z $05 również, a hasło docierające do Traefika jest ułomkiem, który do niczego nie pasuje.
# wygenerowanie
htpasswd -nbB admin "haslo"
admin:$2y$05$e6zXx8Q0m2yYy1H4o1Yb1uJf2Rr7cKq5dW6vT8sN0pL3aG9hC1dS2
# w docker-compose.yml każdy $ zostaje podwojony
admin:$$2y$$05$$e6zXx8Q0m2yYy1H4o1Yb1uJf2Rr7cKq5dW6vT8sN0pL3aG9hC1dS2
# w czystym pliku etykiet albo w wierszu poleceń zostaje pojedynczy
Podwajanie jest regułą Compose’a, a nie Traefika – dlatego ten sam napis trzeba zapisać inaczej zależnie od miejsca pobytu. Skrót we własnym pliku dynamicznej konfiguracji Traefika zachowuje pojedyncze dolary, ten sam skrót w pliku Compose potrzebuje ich podwójnie, a przeniesienie działającej konfiguracji z jednego do drugiego rozkłada ją dokładnie w ten sposób.
Objawem jest okienko logowania odrzucające każde hasło, również właściwe – co prowadzi do sprawdzania hasła, a nie pliku, który je zjadł.
Jak każdy z tych błędów wygląda z zewnątrz
| Błąd | Objaw | Gdzie zajrzeć |
|---|---|---|
| Router wskazuje usługę, której nie ma | 404 dla tej nazwy | panel: router z błędem „the service … does not exist” |
| Odwołanie do middleware z literówką albo bez przyrostka do innego źródła | 404 dla tej nazwy | panel: router z błędem „middleware … does not exist” |
| Pojedyncze dolary w Compose | 401 przy każdym haśle | docker inspect: etykieta krótsza niż skrót |
| Kontener poza siecią proxy | 502 albo 504 | usługa nie ma osiągalnego serwera |
Brak traefik.enable=true przy exposedByDefault=false |
404, nie pojawia się nic | panel: kontenera nie ma na liście |
| Dwa kontenery, ta sama nazwa routera, różne ustawienia | 404, Traefik odrzuca router | dziennik: „defined multiple times with different configurations” |
Cztery z sześciu rozstrzyga panel przeglądowy, co przemawia za włączeniem go również na małej maszynie, na wewnętrznym wejściu. Pokazuje, co Traefik faktycznie zrozumiał, a każdy z tych błędów jest różnicą między napisanym a zrozumianym.
Dwa nawyki, które zapobiegają większości
Pierwszy polega na wyprowadzeniu każdej nazwy z usługi i użyciu jej bez zmian we wszystkich trzech miejscach – kontener blog, router blog, usługa blog, middleware blog-auth. Nazwy utworzonej mechanicznie nie da się źle zapamiętać, a porównanie etykiet dwóch kontenerów pokazuje wtedy już tylko wiersze naprawdę się różniące.
Drugi polega na odczytywaniu etykiet, a nie pliku. docker inspect pokazuje wartości tak, jak odłożyła je usługa, po zakończeniu podstawiania przez Compose – tam z podwojonego dolara robi się z powrotem pojedynczy i tam po ocalałym skrócie widać, że jest dokładnie tak długi jak wygenerowany.
Pytania i odpowiedzi
Czy skrót da się przechować tak, żeby Compose w ogóle go nie widział?
Tak, na dwa sposoby, i w obu dolary zostają pojedyncze. Pierwszym jest plik użytkowników: zamiast basicauth.users etykieta basicauth.usersfile przyjmuje ścieżkę, a plik zawiera wiersze dokładnie w takiej postaci, w jakiej wypisuje je htpasswd. Compose odczytuje tylko ścieżkę, a nie zawartość, nie ma więc czego podstawiać. Plik otwiera jednak Traefik, a nie aplikacja, dlatego musi on być zamontowany w kontenerze Traefika, a nie w chronionym przez niego kontenerze.
Drugi sposób przenosi middleware w całości do własnego pliku dynamicznej konfiguracji Traefika, w którym skrót i tak ma pojedyncze dolary. Zmienia się przy tym jednak źródło, a tu wchodzi w grę przyrostek z drugiego rozdziału: router musi wtedy odwoływać się do blog-auth@file zamiast do blog-auth@docker. Odwołanie pozostawione przy przenosinach bez zmian wskazuje nazwę, której w tym źródle już nie ma, a Traefik wyłącza router z komunikatem „middleware … does not exist”, dopóki odwołanie nie będzie poprawne. Po przeniesieniu warto więc sprawdzić w panelu, czy router nie zgłasza błędu.
Czy middleware działa na każdej drodze prowadzącej do tej samej usługi?
Nie. Middleware należy do routera, a nie do usługi, i działa tylko na żądania, które ten router przyjmuje. Drugi router wskazujący tę samą usługę, na przykład dla kolejnej nazwy hosta albo przez inne wejście, wydaje aplikację bez logowania, dopóki middleware nie znajdzie się także na jego liście; panel pokazuje to osobno dla każdego routera.
Dlaczego Traefik przy kontenerze poza siecią proxy odpowiada kodem 502 albo 504, a nie 404?
Bo w tym przypadku wszystko, co napisano, jest poprawne. Router i usługa istnieją i do siebie pasują, żądanie zostaje więc przyjęte i przekazane dalej. Zawodzi dopiero przy przekazaniu: Traefik zna z Dockera adres kontenera, ale nie może go osiągnąć, bo nie łączy ich żadna wspólna sieć. Kod 404 powstaje, gdy Traefik nie znajduje dla żądania żadnego ważnego celu; 502 i 504 powstają, gdy cel znalazł, ale ten nie odpowiada.
Częstszym przypadkiem niż brak sieci jest kontener podłączony do kilku sieci. Traefik bierze wtedy jeden z jego adresów, a bez wyraźnego ustawienia nie wiadomo, który. Etykieta traefik.docker.network ustala sieć. Oczekuje nazwy, pod którą sieć zna Docker, a Compose zwykle poprzedza nazwy tworzonych przez siebie sieci nazwą projektu. Która nazwa obowiązuje, pokazuje, tak jak przy dolarach, docker inspect.