Graphite in Docker

Diesen Beitrag schrieb ich 3 Jahre und 2 Monate zuvor; die nachfolgenden Ausführungen müssen heute weder genau so nach wie vor funktionieren, noch meiner heutigen Meinung entsprechen. Behalte das beim Lesen (und vor allem: beim Nachmachen!) bitte stets im Hinterkopf.

Geschätzte Lesezeit: 4 Minuten

Wer einmal das zweifelhafte Vergnügen hatte, graphite vollständig manuell installieren zu dürfen, behält das üblicherweise in Erinnerung Die Abhängigkeiten, die Versionen, der Compiler: hurz!

Noch schwieriger gestaltet sich das Ganze auf historisch gewachsenen Systemen, auf denen über pip, cpan und yum parallel graphite-relevante Pakete verschiedenster Versionen installiert wurden, die dann in keinerlei Zusammenhang stehen. Doch was tun, wenn die Anforderung „aktuelles graphite auf abgehangenem Betriebssystem“ lautet? Oder „aktuelles graphite auf einem System, für welches keine Pakete bereitgestellt werden und auf dem pip nicht erlaubt ist“? Oder „testweise mal ein graphite an Icinga 2 rantackern und schauen, wie das so ist“? Nicht die schönste, aber eine mögliche Antwort darauf lautet: Docker.

Installation von Docker

Bei dem mir vorliegenden System handelt es sich um ein Debian 10. Doch auch auf anderen Systemen sind die nötigen Schritte nicht signifikant anders. Ich starte also mit der Installation der Docker-Engine und orientiere mich dabei vollständig an deren Online-Dokumentation – das solltet ihr auch tun, weshalb ich den Inhalt hier jetzt mal nicht repliziere. Für Distributionen ungleich Debian sind ebenfalls Anleitungen vorhanden. Und sollte eure Distribution nicht gelistet sein gehe ich davon aus, dass euch bekannt ist, auf welchem Weg ihr Docker draufwerfen könnt.

$ systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: active (running) since Tue 2021-02-09 10:23:21 CET; 3h 23min ago
     Docs: https://docs.docker.com
 Main PID: 24276 (dockerd)
    Tasks: 44
   Memory: 74.9M
   CGroup: /system.slice/docker.service
           ├─24276 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
           ├─24461 /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 9101 -container-ip 172.24.0.2 -container-port 8080
           └─24494 /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 9093 -container-ip 172.24.0.3 -container-port 9093
...

Installation von docker-compose

Analog hierzu bringe ich docker-compose auf meinem System ein. Das mag ich eigentlich ganz gerne inzwischen, und es erleichtert mir die Arbeit beträchtlich.

$ docker-compose --version
docker-compose version 1.28.2, build 67630359

Für welche Wege ihr euch auch immer entscheidet - ob pip oder nicht pip oder von Hand oder per Paket oder wie auch immer - am Ende der Installation solltet ihr ein grundsätzlich lauffähiges Docker-Environment nebst docker-compose vorfinden. Und nun können wir damit beginnen, ihm Leben einzuhauchen.

Container und Volumes

Ich organisiere meine docker-compose-Dateien gerne unterhalb von /etc/docker. Desweiteren möchte ich die WSGI-Daten, die beim Betrieb entstehen werden, nicht in Docker-Volumes, sondern im Dateisystem sehen. Das vereinfacht mir das Backup, ist aber insbesondere auch hilfreich, wenn ein bestehendes altes graphite-Setup auf Docker umgestellt werden soll. Analog verfahre ich mit den Konfigurationsdateien. „Geschmackssache“, wie der Affe so schön sagt – für mich bewährt sich der Ansatz ganz gut.

$ mkdir -p /srv/graphite/{conf,data}
## file: "/etc/docker/graphite.yml"
version: "3.8"
services:
  graphite:
    image: graphiteapp/graphite-statsd:1.1.7-9
    container_name: graphite
    hostname: graphite
    restart: always
    ports:
      # Carbon Plain Text
      - "2003-2004:2003-2004"
      # Carbon Relay
      - "2013-2014:2013-2014"
      # Carbon Aggregator
      - "2023-2024:2023-2024"
      # Carbon Statsd
      - target: 8125
        published: 8125
        protocol: udp
        mode: host
      - "8126:8126"
      # Graphite Webapp
      - "127.0.0.1:8081:80"
    volumes:
      - /srv/graphite/conf:/opt/graphite/conf:Z
      - /srv/graphite/data:/opt/graphite/storage:Z
    environment:
      - GRAPHITE_DEBUG=0
      - COLLECTD=1
      - GRAPHITE_TIME_ZONE=Europe/Berlin
      - RELAY=1

Natürlich ist das ein sehr grundsätzliches Setup, und ihr müsst gegebenenfalls euren eigenen Kram unterbringen. Grundsätzlich funktionsfähig ist es aber auch so, also wird der Container jetzt mal gestartet.

$ docker-compose -f /etc/docker/graphite.yml up -d
Building with native build. Learn about native build in Compose here: https://docs.docker.com/go/compose-native-build/
Creating network "graphite\_default" with the default driver
Pulling graphite (graphiteapp/graphite-statsd:1.1.7-9)...
1.1.7-9: Pulling from graphiteapp/graphite-statsd
801bfaa63ef2: Already exists
dbcfd62d843e: Pull complete
...

Damit ist graphite nun sozusagen im Leerlauf. Wenn ihr nach /srv/graphite/data schaut, seht ihr, dass da erste Daten auftauchen. Analog hierzu findet ihr in /srv/graphite/conf die aktuelle Konfiguration.

Hinweis: stellt ihr eine bestehende Installation um müsst ihr an dieser Stelle darauf achten, den Verzeichnissen und Inhalten gegebenenfalls noch die richtigen Rechte zu geben!

Es ist ein guter Moment, Icinga 2 anzubinden.

Anbindung an Icinga 2

Die Anbindung an Icinga 2 erfordert drei Einzelschritte:

  • Erweitern der storage-schemas.conf,
  • Einschalten des Feature graphite,
  • Konfiguration des Feature graphite und
  • Restart von Icinga 2

Gemäß Dokumentation erweitern wir die Datei storage-schemas.conf um einen Abschnitt in Sachen Icinga 2.

## file: "/srv/graphite/conf/storage-schemas.conf"
[icinga2_default]
pattern = ^icinga2\.
retentions = 1m:2d,5m:10d,30m:90d,360m:4y

Bei einem Umzug eines bestehenden Systems ist darauf zu achten, dessen Konfiguration zu übernehmen - eventuelle Versionssprünge berücksichtigen!

Hernach starten wir graphite durch, um diese Änderung auch in den laufenden Betrieb zu übernehmen.

$ docker restart graphite

Und im nächsten Schritt weisen wir Icinga 2 an, Performance-Daten zukünftig an graphite zu senden.

$ icinga2 feature enable graphite
Enabling feature graphite. Make sure to restart Icinga 2 for these changes to take effect.
## file: "/etc/icinga2/features-enabled/graphite.conf"
object GraphiteWriter "graphite" {
  host = "127.0.0.1"
  port = 2003
  enable_send_thresholds = true
  enable_send_metadata = true
}
$ systemctl restart icinga2

In den Logs des Docker-Containers lässt sich beobachten, wie erste Metriken von Icinga 2 ins graphite hinein marschieren. Und in /srv/graphite/data/whisper erscheinen analog dazu whisper-Dateien.

$ docker logs --follow graphite
...
15/02/2021 17:57:32 :: [listener] MetricLineReceiver connection with 127.0.0.1:53302 established
15/02/2021 17:57:42 :: [listener] MetricLineReceiver connection with 127.0.0.1:53302 closed cleanly
15/02/2021 17:57:33 :: [tagdb] Tagging stats.timers.view.graphite.browser.views.header.GET.count
15/02/2021 17:57:33 :: [tagdb] Tagged stats.timers.view.graphite.browser.views.header.GET.count in 0.0067441463470458984
15/02/2021 17:57:34 :: [tagdb] Tagging carbon.relays.graphite-a.cpuUsage, collectd.graphite.disk-dm-6.disk_time.write, collectd.graphite.disk-sda.disk_io_time.io_time
15/02/2021 17:57:34 :: [tagdb] Tagged carbon.relays.graphite-a.cpuUsage, collectd.graphite.disk-dm-6.disk_time.write, collectd.graphite.disk-sda.disk_io_time.io_time in 0.006624460220336914
15/02/2021 17:57:43 :: [listener] MetricLineReceiver connection with 127.0.0.1:54720 established
15/02/2021 17:57:43 :: [listener] MetricLineReceiver connection with 127.0.0.1:54720 closed cleanly
15/02/2021 17:57:43 :: [tagdb] Tagging stats.timers.view.GET.count_ps
15/02/2021 17:57:43 :: [tagdb] Tagged stats.timers.view.GET.count_ps in 0.011634111404418945
15/02/2021 17:57:44 :: [tagdb] Tagging carbon.relays.graphite-a.destinations.127_0_0_1:2004:None.batchesSent, collectd.graphite.disk-dm-2.disk_time.read, collectd.graphite.cpu-2.cpu-nice
15/02/2021 17:57:44 :: [tagdb] Tagged carbon.relays.graphite-a.destinations.127_0_0_1:2004:None.batchesSent, collectd.graphite.disk-dm-2.disk_time.read, collectd.graphite.cpu-2.cpu-nice in 0.010526418685913086
15/02/2021 17:57:53 :: [listener] MetricLineReceiver connection with 127.0.0.1:54744 established
15/02/2021 17:57:53 :: [listener] MetricLineReceiver connection with 127.0.0.1:54744 closed cleanly
15/02/2021 17:57:53 :: [tagdb] Tagging stats.timers.view.graphite.tags.views.tagMultiSeries.POST.sum_squares, collectd.graphite.disk-dm-0.disk_io_time.io_time
15/02/2021 17:57:53 :: [tagdb] Tagged stats.timers.view.graphite.tags.views.tagMultiSeries.POST.sum_squares, collectd.graphite.disk-dm-0.disk_io_time.io_time in 0.009638309478759766
15/02/2021 17:57:53 :: [tagdb] Tagging collectd.graphite.cpu-0.cpu-user
15/02/2021 17:57:53 :: [tagdb] Tagged collectd.graphite.cpu-0.cpu-user in 0.007399559020996094
15/02/2021 17:57:59 :: [tagdb] Tagging icinga2.icinga2-master.services.network-eth0.nwc_health.perfdata.eth0_broadcast_out.min
...

Arbeiten mit whisper-Dateien

Sollte es erforderlich sein, auf den whisper-Daten direkt zu arbeiten, so sollte das vom Docker-Container aus geschehen; denn dort finden sich die benötigten Binaries. Nachdem man sich mit dem Container verbunden hat ist das aber business as usual.

$ docker exec -it graphite sh
/ $ /opt/graphite/bin/whisper-fetch.py --pretty /opt/graphite/storage/whisper/icinga2/icinga2-master/services/load/load/perfdata/load1/value.wsp

Einbindung in Icinga Web 2

Was nun noch fehlt ist deren Darstellung in Icinga Web 2. Doch glücklicherweise gibt es hierzu ein fertiges Modul – etwas abgehangen, zugegebenermaßen, aber nichtsdestotrotz funktional.

$ cd /usr/share/icingaweb2/modules
$ wget https://github.com/Icinga/icingaweb2-module-graphite/archive/v1.1.0.tar.gz
$ mkdir graphite && tar xvfz v1.1.0.tar.gz -C graphite --strip-components=1
$ icingacli module enable graphite

Anschließend noch das Modul konfigurieren: hierzu in Icinga Web 2 nach → Modules → graphite → Backend navigieren und als erforderliche „Graphite Web URL“ http://127.0.0.1:8081 eintragen

Das entspricht dem, was wir in graphite.yml als „Graphite Webapp“ konfiguriert hatten.

Username und Passwort sind an dieser Stelle übrigens nicht erforderlich – das muss dann für alles, was über eine Teststellung hinausgeht, angepasst werden. Und auch „Advanced Settings“ können wir für den Moment außer acht lassen.

… that’s it!

Im Grunde genommen muss ich meine eingangs getroffene Aussage widerrufen: denn eigentlich ist Docker eben doch die schönste Art, „mal eben“ ein graphite an den Start zu bringen. Und das funktioniert auch im Produktivbetrieb und auch in größeren Umgebungen gar nicht schlecht – sofern nicht noch drölfzig andere Container parallel laufen. Wobei natürlich die üblichen Performance-Aussagen (so zum Beispiel dass graphite SSDs liebt; IO in größeren Umgebungen ein relevanter Faktor werden kann; dass Icinga 2 und graphite dann eher auf getrennten Hosts rennen sollten usw.) wie sonst auch zutreffen.

Aber das ist okay. Gerade in größeren Umgebungen vermeiden die Verantwortlichen ganz gerne Upgrades an einem einmal erfolgreich eingerichteten graphite-Setup, weil sie das Ganze am liebsten nicht einmal mit der Grillzange anfassen möchten. Und auch hier kann die Arbeit mit Docker-Containern die Situation entschärfen. Letztlich hängt es von vielen Faktoren ab – aber ihr habt jetzt gesehen, dass der Aufwand überschaubar bleibt.

Alle Bilder dieser Seite: © Marianne Spiller – Alle Rechte vorbehalten
Hintergrundbild: Bild genauer anschauen – © Marianne Spiller – Alle Rechte vorbehalten

Eure Gedanken zu „Graphite in Docker“

Ich freue mich über jeden Kommentar, es sei denn, er ist blöd. Deshalb behalte ich mir auch vor, die richtig blöden kurzerhand wieder zu löschen. Die Kommentarfunktion ist über GitHub realisiert, weshalb ihr euch zunächst dort einloggen und „utterances“ bestätigen müsst. Die Kommentare selbst werden im Issue-Tracker und mit dem Label „✨💬✨ comment“ erfasst – jeder Blogartikel ist ein eigenes Issue. Über GitHub könnt ihr eure Kommentare somit jederzeit bearbeiten oder löschen.