Monitoring von Containern mit Prometheus und cAdvisor – mit Vorsicht zu geniesen

Monitoring ist ein wichtiges Instrument als DevOp, in der Regel bekommt man so schon mit, dass ein Service nicht ganz rund läuft, bevor es Probleme für den Kunden bedeutet. Genauso kündigen sich ausfallende oder knapper werdende Ressourcen in der Regel an und treten nicht „urplötzlich“ auf.

Eines der Standard-Werkzeuge für diesen Fall ist Prometheus, das ein recht ausgefeiltes Monitoring bietet. Häufig wird es im Einsatz mit Containern durch cAdvisor ergänzt, damit erhält man auch einen Einblick in die einzelnen Docker-Container, welche auf dem System laufen. Für das Host-System gibt es von Prometheus den node-exporter.

Was man aber auch immer auf dem Schirm haben sollte: Im Wesentlichen sind die Agenten (bzw. Exporter) einfache Transformationsmaschinen, welche aus der Vielzahl von Informationen, die ein jedes System bereit stellt, ein standardisiertes Format machen und es ausgeben. Im konkreten Fall erfolgt die Ausgabe per http bzw. hoffentlich per https. Womit wir auch schon beim Knackpunkt der Geschichte wären: Man kommt sich an dieser Stelle wieder ins „gute alte Internet“ zurück versetzt vor, in dem Sicherheit kein große Rolle gespielt hat und alles mehr oder weniger „frei“ zugänglich war. Das zeigt sich leider bereits in den Tutorials: dort wird erst einmal beschrieben, wie man einen Service generell aufsetzt, aber es fehlt der eindeutige Hinweis, wie man die Dinge hinterher auch so vernagelt, dass nur berechtigte Personen das mitlesen können bzw. überhaupt an die Informationen heran kommen können. Man möchte doch nicht gerade exponieren, was auf einem Server alles an Containern läuft, das sollte eigentlich mittlerweile jeder Hobby-Admin auf dem Schirm haben, im professionellen Bereich ist es auch eine Selbstverständlichkeit.

Schaut man sich die Dokumentation insbesondere der Exporter an, so bekomme zumindest ich schon wieder ein schlechtes Gefühl: Vielfach wird darauf verwiesen, das Abschirmen der Container per Reverse-Proxy zu lösen. Das ist sicherlich ein technisch gangbarer Weg, aber es zeigt auch wieder ein wenig die Einstellung der Macher dieser Software: „Not my business“ bzw. „not implemented here“. Es gibt in beiden Fällen zwar Möglichkeiten, TLS und auch zumindest Basic-Auth zu implementieren, aber das ist eher ein Tropfen auf den heißen Stein. Den Vogel (passenderweise ist das Maskottchen eine Eule) schießt cAdvisor ab: Man kann zwar Basic-Auth setzen, aber es bezieht sich nur auf die Endpunkte der GUI, die API und die maschinenlesbaren Metriken sind weiterhin ohne jeglichen Passwort-Schutz abrufbar, so lange man die URL kennt (oder durchprobiert). Hierzu gibt es einen interessanten Artikel von TrendMicro.

Die Lösung, die wieder häufig propagiert wird, ist: Lass uns eine Sicherheitssoftware davor schalten, in diesem Fall einen Reverse-Proxy, der kann dann auch die Authentifizierung und das TLS übernehmen. Das klingt im ersten Moment ja noch recht brauchbar, aber es bringt eine weitere Komponente ins Spiel, die man warten muss. Bei vielen Installationen, die sich einen Docker-Host teilen, ist ein Loadbalancer / Reverse-Proxy aber ohnehin installiert. Häufig wird in diesem Zusammenhang Traefik eingesetzt, das hat den Vorteil, dass es sich eigentständig über kommende und gehende Webservices / Container informiert und die Konfiguration dazu aus den Labels des Docker-Containers ableitet. So hat man immerhin den Teil der Reverse-Proxy-Konfiguration für den eigenen Service noch ein Stück weit im Griff.

Ein Compose file sieht dann beispielsweise wie folgt aus:

name: cadvisor
services:
  cadvisor:
    image: gcr.io/google-containers/cadvisor:latest
    container_name: cadvisor
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik
      - traefik.http.routers.cadvisor.entrypoints=web
      - traefik.http.routers.cadvisor.rule=Host(`cadvisor.example.org`)
      - traefik.http.services.cadvisor.loadbalancer.server.port=8080
      - "traefik.http.middlewares.cadvisor.basicauth.users=USER:PASSWORD"
      - traefik.http.routers.cadvisor.middlewares=cadvisor
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:rw
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    restart: always
    environment:
      - no_proxy="localhost,127.0.0.1"
      - HTTP_PROXY=
      - http_proxy=
    networks:
      - cadvisor
      - traefik
networks:
  cadvisor:
    name: cadvisor
  traefik:
    external: true

Dabei gibt es einige kleine Fallstricke, die man beachten muss:

Das Passwort muss als Hash eingetragen werden, damit es im YAML nicht zerbröselt wird beim Interpretieren, muss man die „$“ verdoppeln (Escaping). Sonst fühlt sich der YAML-Interpreter angesprochen und versucht die Variablen zu substituieren, was nicht funktioniert und im Zweifel auch erst einmal nicht ganz „eindeutige“ Fehlermeldungen nach sich zieht.

Sofern man einen Proxy einsetzt und diesen für den Dockerhost entsprechend konfiguriert hat, so muss man mit dem cAdvisor aufpassen, dieser hat einen Health-Check per „wget“ implementiert. Allerdings ist es kein Standard-Wget sondern das aus Busybox. Das ignoriert leider die Umgebungsvariable „no_proxy“ ziemlich gekonnt, vorsichtshalber kann man sie setzen, aber man muss auch den Proxy deaktivieren, indem man die Variablen explizit auf einen leeren String setzt.

 Hat man diese beiden Fallstricke gemeistert, erkennt Traefik auch einen cAdvisor-Container richtig und man exponiert nicht mehr einfach mal so die ganzen Metriken nach draußen.

Ich erspare mir hier jetzt weitere Erläuterungen, wie es um Ende-zu-Ende Kommunikation und insbesondere bei gerouteten IPv6-Containern aussieht: Leider nicht wirklich gut, auch da muss man sich einen Reverse-Proxy davor bauen, den man eigentlich mit IPv6 nicht mehr nötig hätte.

Insgesamt täte es vielen Container-Entwicklungen mittlerweile echt gut, wenn Authentifizierung und TLS nicht immer als ein „Problem anderer Leute“ (Erklärung: https://hitchhikers.fandom.com/wiki/Somebody_Else%27s_Problem_Field) betrachten würden, sondern es endlich zum guten Standard wird, den man bei jeder Entwicklung zeitnah mitdenken sollte. Nahezu jedes brauchbare Framework bringt heute die passenden Tools dazu mit, man muss sich nur ein wenig damit beschäftigen.