Howto: cAdvisor mit Traefik einrichten

Zum Jahresabschluss noch ein kleines Howto, und zwar zur Einrichtung eines cAdvisor-Containers mit Traefik, wobei cAdvisor keinen eigenen Hostnamen erhält, sondern unter einem bestimmten Pfad zur Verfügung steht. cAdvisor (Container Advisor) ist ein Tool, das Daten zur Ressourcen-Nutzung von Docker-Containern sammelt und diese mehr oder minder hübsch aufbereitet in einer Web-UI zur Verfügung stellt. Ebenfalls ermöglicht cAdvisor den Export dieser gesammelten Daten, kann somit als Grundlage für ein Monitoring-System dienen, um beispielsweise Performance-Engpässe zu erkennen.

Tatsächlich ist diese Export-Funktionalität letztlich für mich interessanter als die angestaubt wirkende Web-UI, die auch nur einen Einblick in den aktuellen Zustand ermöglicht, die Daten jedoch nicht archiviert. Insofern dient cAdvisor als ein erster Baustein einer umfassenderen Lösung, doch da ich gerne Schritt für Schritt vorgehe und bei der Einrichtung auf ein paar Hürden gestoßen bin, wollte ich die Gelegenheit nutzen, das Ergebnis hier kurz darzustellen.

Ein erstes Ziel: cAdvisor

Zunächst zur Zieldefinition: cAdvisor soll auf einem Host, auf dem selbstverständlich Docker installiert ist und auf dem einige Docker-Container laufen, eingerichtet werden, so dass die Web-UI unter einer URL der Form “https://host.domain.tld/cadvisor/” erreichbar ist. Auf den jeweiligen Hosts läuft bereits Traefik Proxy und stellt die Edge-Router-Funktionalität für die Dienste dar, die per http auf Port 80 oder https auf Port 443 erreichbar sind. Insbesondere soll cAdvisor von außen nicht direkt über den Hostnamen und dem Port 8080 erreicht werden können, des Weiteren soll der Pfad von cAdvisor mittels HTTP-Basic-Authentication abgesichert werden. Die Service-Definition soll mittels Docker-Compose-File erfolgen.

cAdvisor-Service mit Docker-Compose

Das vollständige Docker-Compose-File sieht wie folgt aus:

version: '3.8'
services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    restart: always
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    privileged: true
    devices:
      - "/dev/kmsg:/dev/kmsg"
    command: --url_base_prefix=/cadvisor
    environment:
      - CADVISOR_HEALTHCHECK_URL=http://localhost:8080/cadvisor/healthz
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik-public4" 

      - "traefik.http.routers.cadvisor-04.service=cadvisor-04-secured"
      - "traefik.http.routers.cadvisor-04.rule=Host(`oc01.xyzcdn.xyz`) && PathPrefix(`/cadvisor`)"
      - "traefik.http.routers.cadvisor-04.priority=20"
      - "traefik.http.routers.cadvisor-04.entrypoints=http"

      - "traefik.http.middlewares.def-cadvisor-04.headers.customrequestheaders.X-Forwarded-Server=oc01.xyzcdn.xyz"
      - "traefik.http.middlewares.def-cadvisor-04.headers.referrerPolicy=origin"
      #- "traefik.http.middlewares.def-cadvisor-04-strip.stripprefix.prefixes=/cadvisor" # first idea, does not work
      - "traefik.http.middlewares.def-cadvisor-04-auth.basicauth.users=USERNAME:PASSWORD"
      - "traefik.http.routers.cadvisor-04.middlewares=https-redirect"

      - "traefik.http.routers.cadvisor-04-secured.service=cadvisor-04-secured"
      - "traefik.http.routers.cadvisor-04-secured.rule=Host(`oc01.xyzcdn.xyz`) && PathPrefix(`/cadvisor`)"
      - "traefik.http.routers.cadvisor-04-secured.priority=20"
      - "traefik.http.routers.cadvisor-04-secured.entrypoints=https"
      - "traefik.http.routers.cadvisor-04-secured.tls.certresolver=le-tls" 
      - "traefik.http.services.cadvisor-04-secured.loadbalancer.server.port=8080" 
      #- "traefik.http.routers.cadvisor-04-secured.middlewares=secHeaders@file,def-cadvisor-04-strip,def-cadvisor-04,def-compress" # first idea with stripprefix
      - "traefik.http.routers.cadvisor-04-secured.middlewares=secHeaders@file,def-cadvisor-04-auth,def-cadvisor-04,def-compress"
    networks:
      - "traefik-public4"


networks:
  traefik-public4:
    driver: bridge # if using overlay network in a swarm cluster, just delete this
    external: true

Die Service-Definition im Detail

Das Docker-Image wird nicht vom Docker-Hub bezogen, sondern die Zeile “image: ...” verweist auf die Google Container Registry, genau wie im README zu cAdvisor angegeben.

Ebenfalls sind die darauf folgenden Volumes-Definitionen eine Umsetzung der Parameter, die im Quick Start der cAdvisor-Dokumentation angegeben sind. Hierbei dürfte es keine Überraschungen geben. Die Zeile “privileged: true” ist analog zum Parameter “--privileged” aus dem Docker-CLI-Kommando. Eine Angabe eines veröffentlichten Ports ist hingegen nicht notwendig, da sich Traefik um die Erreichbarkeit kümmert. Das Pendant zum Parameter “--device” aus dem run-Kommando ist im Abschnitt “devices:” zu finden.

Pfad hin, Pfad zurück

Um die beiden folgenden Zeilen bzw. Abschnitte zu verstehen, muss ich ein wenig ausholen. Denn die übliche bzw. in der cAdvisor-Dokumentation beschriebene Installationsweise ist die Nutzung des Hostnamens in Verbindung mit einem speziellen Port, etwa 8080. Bei einem Request auf eine URL wie “http://host.domain.tld:8080“, erfolgt umgehend ein Redirect auf “http://host.domain.tld:8080/containers/“, diese URL stellt insofern die Startseite der Web-UI dar. Das Ziel lautete jedoch, eine URL “https://host.domain.tld/cadvisor/” bereitzustellen. Leider berücksichtigt cAdvisor jedoch keine Pfadangabe, der Request wird insofern schnurstracks weiterhin auf “https://host.domain.tld/containers/” umgeleitet, worunter sich jedoch kein cAdvisor zu finden ist, sondern nur der Nginx-Container, der sich für die Website des Hosts verantwortlich zeigt.

Netterweise bietet cAdvisor Runtime-Options, so kann mittels Parameter “--url_base_prefix=/cadvisor” ein Pfad angegeben werden, unter dem die Web-UI erreichbar sein soll. Nun nutzt das Docker-Image eine entrypoint-Definition, in der das cAdvisor-Binary mit einem einzigen Parameter aufgerufen wird. Es ist mir jedoch nicht gelungen, diese im Docker-Compose-File laut Spezifikation nachzubauen und dabei den zusätzlichen Parameter “--url_base_prefix” hinzuzufügen. Statt dessen wird die Zeile “command:” genutzt, denn falls im Dockerfile eine Entrypoint-Definition vorhanden ist, wird das, was bei “command:” angegeben ist, tatsächlich dem Inhalt von Entrypoint einfach hinzugefügt. Falls man sich den Spaß macht, in den Container hinein zu blicken, ist dieses Verhalten leicht erkennbar:

geschke@oc01:~/services/cadvisor$ docker exec -it cadvisor_cadvisor_1 sh
/ # ps ax
PID   USER     TIME  COMMAND
    1 root      0:02 /usr/bin/cadvisor -logtostderr --url_base_prefix=/cadvisor
   32 root      0:00 sh
   40 root      0:00 ps ax

Gesundheit!

Der nächste Abschnitt definiert die Environment-Variable “CADVISOR_HEALTHCHECK_URL“. Im cAdvisor-Dockerfile wird ein Health-Check definiert, somit lässt sich der Zustand des Containers überwachen. Das ist grundsätzlich prima, doch die Umsetzung wirkt leider etwas durchwachsen.

Denn obwohl durch die Angabe von “--url_base_prefix” cAdvisor nun auf den Pfad “/cadvisor” gelenkt wurde, wird dies vom Health-Check, der fest im Dockerfile definiert wird, nicht berücksichtigt. Zwar läuft die cAdvisor-Instanz, nur bekommt der Health-Check davon nichts mit, weshalb der Container-Status im “unhealthy“-Zustand bleibt. Dies ist wiederum suboptimal für Traefik, da für einen derartigen Container keine Traefik-Komponenten (Router, Services, Middleware) erstellt werden. Ein Blick ins cAdvisor-Dockerfile jedoch verrät, dass die Angabe der URL für den Health-Check in einer Environment-Variable definiert ist, die durch eine gleichlautende Angabe im Environment-Bereich des Compose-Files überschrieben werden kann. Die URL wird insofern auf “http://localhost:8080/cadvisor/healthz” (inklusive Path-Prefix) gesetzt anstatt der Standard-Angabe “http://localhost:8080/healthz“. Tatsächlich stellten diese beiden Zeilen die größten Hürden bei der Einrichtung dar, auch weil in der Dokumentation von cAdvisor bzgl. der Runtime Options nichts zum Thema Health-Check zu finden war.

Traefik – es leben die Labels!

Der letzte Abschnitt der Service-Definition besteht aus den Labels zur Konfiguration von Traefik, wobei die meisten bereits aus den vorherigen Artikeln zum Thema Traefik bekannt sein dürften. Das auf dem Host vorab bzw. extern definierte Netzwerk trägt den Namen “traefik-public4“, daher wird es in der Definition entsprechend angegeben. Letztlich habe ich einfach die Hosts durchnummeriert, die insgesamt verwendet werden, daher findet sich diese Zahl auch in den Bezeichnungen der Router, Middleware etc..

Mit “traefik.http.routers.cadvisor-04.rule=Host(`oc01.xyzcdn.xyz`) && PathPrefix(`/cadvisor`)” werden Host und Pfad gesetzt, unter dem cAdvisor erreichbar sein soll. In diesem Fall habe ich auf “sprechende” Namen verzichtet, der Host ist somit einfach der FQDN des betreffenden Nodes. Dieselbe Regel findet sich nochmal im Router für https, dort unter dem Bezeichner “cadvisor-04-secured“.

Die nächste Zeile “traefik.http.routers.cadvisor-04.priority=20” sieht unscheinbar aus, ist jedoch immanent wichtig für das Routing, dasselbe gilt auch hier beim Pendant für https mit dem Namen “cadvisor-04-secured”. Denn ein weiterer Container ist dafür zuständig, einen Webserver zur Verfügung zu stellen, der bei einem Request auf den Hostnamen eine Dummy-Seite anzeigt. Auch dabei wird Traefik eingesetzt, somit existiert in einer anderen Service-Definition die Regel “traefik.http.routers.website_oc01.rule=Host(`oc01.xyzcdn.xyz`)“. Insgesamt ist die Konfiguration ähnlich wie im Artikel Ein Webserver-Service mit Traefik und ein paar Tipps am Rande beschrieben, dabei nimmt der Webserver angesichts der Traefik-Regel alle Requests für”oc01.xyzcdn.xyz” entgegen. In den dortigen Labels war zuvor keine Angabe der Priority enthalten, ganz einfach weil diese nicht notwendig war. Die ersten Versuche, den Pfad “/cadvisor/” zu erreichen, schlugen jedoch fehl, statt dessen wurde immer ein Fehler 404, also “nicht gefunden” angezeigt.

Ein Blick in die Traefik-Logs bestätigte den Verdacht – die Requests wurden nicht an den cAdvisor-Container weitergeleitet, sondern landeten im Webserver-Container des Hosts. Des Rätsels Lösung fand sich dann auch schnell in der Dokumentation, falls keine Priority in der Router-Definition angegeben wird, nutzt Traefik die Standard-Reihenfolge, die sich laut Beschreibung an der Länge der Regel orientiert, d.h. je länger die Rule, desto höher die Priorität. Tatsächlich hatte ich beim cAdvisor jedoch eine Priority angegeben, während beim Webserver-Dienst keine vorhanden war. Der Priority-Wert wird schlicht und einfach aus der Länge des Rule-Strings berechnet. Das ergibt im konkreten Fall genau 23 – ganz ohne Hintergedanken und Verschwörungen…

Da die manuell gesetzte Priority im cAdvisor-Label nun zufälligerweise 20 war, konnte der cAdvisor-Service aus Gründen der Mathematik insofern nie genutzt werden. Wäre die Priority-Zeile hingegen nicht enthalten gewesen, wäre aufgrund der längeren Regel der cAdvisor-Container verwendet worden. Um erst gar nicht in die Verlegenheit derartiger Längen-Berechnungen zu gelangen, erscheint es mir sinnvoll, immer eine Priorität anzugeben, insofern wurde dieser Wert beim Webserver-Service auf 10 gesetzt, somit hat der cAdvisor mit 20 den höheren Wert, so dass bei einem Request auf den Pfad “/cadvisor/” auch der entsprechende Container angesprochen wird.

Die HTTP-Basic-Authentication wird in der Zeile “traefik.http.middlewares.def-cadvisor-04-auth.basicauth.users=USERNAME:PASSWORD” definiert. Der Username ist frei wählbar und wird einfach im Klartext eingetragen. Das Passwort kann mit einem openssl-Kommando erzeugt werden:

geschke@oc01:~/services/cadvisor$ openssl passwd -apr1
Password:
Verifying - Password:
$apr1$Pd20mz0o$lxG9g4CnVK5PmZ3ctYiG71

Die ausgegebene Zeichenkette kann nun in die Definition der Auth-Middleware eingetragen werden, wobei darauf geachtet werden muss, dass alle vorkommenden $-Zeichen escaped werden, somit gleich in doppelter Ausführung vorliegen müssen (“$$“).

Zu guter Letzt werden die Middlewares dem Router hinzugefügt, der Loadbalancer-Service wird auf den Port 8080 des Containers geleitet, zur Bereitstellung von Lets’s-Encrypt-Zertifikaten dient diesmal die TLS-Variante, und natürlich erfolgt auch hier eine Weiterleitung auf die transportverschlüsselte Variante, falls es doch einmal zu old-school http-Requests kommen sollte.

Zurück in die Zukunft

Nach dem Start des Services mit “docker-compose -f cadvisor.yml up -d” präsentiert sich die Web-UI nach der Eingabe der Authentifizierungsdaten in ihrer (gewohnten) Form – an dieser Stelle bitte ich um Nachsicht, nicht “schönsten” geschrieben zu haben. Denn meiner persönlichen Meinung zufolge gibt es absolut keine Daseinsberechtigung für stilisierte Rundinstrumente als Simulation einer analogen Zeit. Das gilt im Übrigen auch für derlei Anzeigen in ansonsten displaystrotzenden Auto-Cockpits, so viel Pseudo-Retro braucht wirklich kein Mensch. Doch cAdvisor sammelt wirklich so einige Daten, die mittels weiterer Tools in eine zeitgemäße Anzeige überführt werden können. Das wäre dann ein nächster Schritt, der möglicherweise auch wieder Thema eines weiteren Artikels wird. Die folgenden Impressionen sollen daher nur einen Zwischenschritt zeigen, die cAdvisor-Web-UI wie sie leibt und lebt.

Die cAdvisor-Web-UI, erreichbar über einen Pfad dank Traefik

 

Howto: cAdvisor mit Traefik einrichten 4

 

 

Howto: cAdvisor mit Traefik einrichten 5

Update 06.01.2021: cAdvisor gesehen, gelacht, gelöscht.

Das Tool cAdvisor ließ sich zwar prima mit Traefik einrichten, aber der eigentliche Sinn und Zweck der Installation war es, die Daten von cAdvisor als Quelle für das Monitoring per Prometheus zu nutzen. Dafür besitzt cAdvisor wiederum einen Pfad bzw. Endpoint, an dem die Daten so zur Verfügung stehen, dass Prometheus sie direkt verarbeiten kann. Dazu ist noch nicht einmal eine Änderung der Konfiguration notwendig, da cAdvisor standardmäßig unter dem Pfad “/metrics” alle Angaben zur Verfügung stellt.

Nun besagt dieser Blog-Eintrag, dass cAdvisor im Pfad “/cadvisor/” genutzt werden soll, demzufolge müssten die Metriken unter “/cadvisor/metrics” abrufbar sein. Als ich diesen Pfad auf einer produktiven VM, auf der sich bereits einige Docker-Container befanden, aufgerufen habe, wurden auch Daten zurückgeliefert, doch ich hatte den Eindruck, es handele sich um eine Endlosschleife. Der Browser lud und lud… und wurde nicht fertig. Statt dessen erschien irgendwann die Warnung von Chrome, dass das Browserfenster nicht mehr reagieren würde, somit folgte der Abbruch der Aktion. Im Normalfall sind die Metriken einige wenige KBytes groß, letztlich handelt es sich nur um Key-Value-Daten, die nahezu ohne Overhead übermittelt werden. Das, was von cAdvisor kam, war jedoch völlig anders…

Ich hatte zunächst einen falschen Pfad im Verdacht, daher versuchte ich, den Parameter “--prometheus_endpoint=...” von cAdvisor dahingehend zu setzen, dass die Metriken unter dem geänderten Pfad abgerufen werden konnten. Doch das Ändern auf den Pfad “/cadvisor/metrics” quittierte cAdvisor mit einem Redirect auf “/cadvisor/containers“, somit erschien nur die Web-UI anstatt der Metriken. Und wurde der Parameter nicht gesetzt oder beim Standard “/metrics” belassen, kam es zu der vermuteten Endlosschleife. Mit Google, in den GitHub-Issues oder gar im Quellcode habe ich nichts gefunden, was in irgendeiner Form auf das Problem hinweisen würde, also habe ich zunächst cAdvisor ganz ohne Traefik und ebenfalls ohne Pfad-Änderungen auf einer anderen VM gestartet. Darauf befand sich ansonsten nur ein dauerhaft laufender Container, somit war diese Umgebung nicht allzu sehr beansprucht.

Beim Request auf die Metriken wurde der Browser dann auch fertig, aber die übertragene Datenmenge war immer noch sehr hoch, so dass wiederum der Verdacht aufkeimen konnte, dass es sich um eine Endlosschleife handelte, oder auch eine Rekursion, die irgendwann aufgrund eines Überlaufs abbricht. Immerhin konnten nun die weiteren Komponenten wie Traefik oder die Konfigurationsänderung des Pfades als Fehlerquelle ausgeschlossen werden.

Wieder zurück auf der produktiven VM ließ ich curl auf die Metrics-URL los, so dass die abgerufenen Daten in einer Datei gespeichert wurden. Das Ergebnis, d.h. die Datei war ca. 94 MB (sic!) groß! Selbst bei Einschränkung durch “--application_metrics_count_limit” oder “--disable_metrics” waren es immer zwischen 54 und 94 MB, die cAdvisor zurück lieferte! Da darf einem schon mal ein leises “WTF?!?” heraus rutschen.

An dieser Stelle habe ich mich entschlossen, eine andere Lösung zu suchen. Zwar wird cAdvisor häufig mit Docker-Monitoring und Prometheus in Verbindung gebracht, aber bei jedem Request eine derart hohe Datenmenge zu übertragen, wenn andererseits z.B. der Prometheus node_exporter, der alle Metriken zu einem Host in wenigen KBytes unterbringt, erschien mir kaum sinnvoll. Die Suche geht also noch ein wenig weiter.

Und da Prometheus inkl. Grafana inzwischen laufen und zunächst einmal Daten der Hosts mittels node_exporter sammeln, fiel mir folgendes auf:

Howto: cAdvisor mit Traefik einrichten 6

Tatsächlich hatte ich in der Nacht gegen 02:00 Uhr die Experimente mit cAdvisor beendet und den Container heruntergefahren. Während der Zeit wurde nichts anderes geändert, alle weiteren Dienste laufen wie zuvor, nur cAdvisor nicht mehr. Das Bild dürfte für sich sprechen. Tatsächlich hatte ich den hohen Ressourcenverbrauch von cAdvisor bzgl. CPU-Leistung bereits vor einigen Jahren auf einer früheren Docker-Installation beobachtet, auch gibt es zu dem Thema etliche Einträge bei GitHub bzw. generell Fundstellen im Netz. Anscheinend ist das Problem noch immer nicht gelöst worden.

Aber gerade ein Tool, das Metriken über den Systemzustand sammelt, sollte diesen doch nicht in dieser Art und Weise selbst beeinflussen! Insofern lautet die einzig logische Konsequenz, cAdvisor vom System zu verbannen und auf andere Art und Weise Auskunft über den Zustand der Docker-Container erhalten. Der node_exporter läuft bereits recht geräuschlos auf den Maschinen, natürlich agiert auch hier Traefik wieder als Proxy…

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Tags: