Grafana Monitoring Stack

Nachdem ich im Bachelorprojekt gelernt habe verschiedene Metriken von Linux Systemen zu messen und die Daten mit gnuplot in Graphen zu Visualisieren, wollte ich eine allgemeine, einfach einzurichtende Lösung für das Messen und Überwachen von Systemen zusammenstellen. Angefangen mit einfachen Bash Skripten, einer Datenbank und gnuplot habe ich CPU-, RAM- und Festplattenauslastung, die Erreichbarkeit von Services und Verbindungsdaten vom Reverse-Proxy visualisiert.

Dann hat Grafana, mit interaktiven Graphen in denen die Zeitspanne angepasst werden kann und mehr Informationen zu einem Datenpunkt angezeigt werden kann, gnuplot ersetzt.
Die Daten kamen weiterhin aus Bash Skripten die in eine Postgresql Datenbank schreiben.

Als ich dann die Maschine, die Daten sammelt, von der Maschine, die die Daten speichert, trennen wollte und versucht habe Metriken zu verallgemeinern, um nicht für jede neue Metrik das Datenbankschema zu verändern, ist mir aufgefallen, dass ich immer mehr Ideen vom Grafana Stack neu implementiert habe.
Mit dieser Erkenntnis war ich motiviert mich mehr in die Grafana Welt einzulesen und sie für mich nutzbar zu machen.

Prometheus + Grafana

Grafana + Prometheus

Auf den Maschinen die überwacht werden sollen laufen diverse Exporter und stellen Metriken für Systemressourcen, Docker Containern oder Nginx über eine HTTP Schnittstelle bereit. Prometheus läuft ebenso auf diesen Maschinen und fragt diese Schnittstellen alle X Sekunden ab. Prometheus und die Exporter die auf zu überwachenden Maschinen laufen nenne ich Outposts.
Prometheus speichert die Daten zwischen und sendet sie dann über das remote_write Verfahren an eine zentrale Sammelstelle, das HQ (Headquarter). Auf dieser Maschine läuft eine weitere Prometheus Instanz um diese Daten entgegenzunehmen und langfristig zu speichern. Das HQ muss dafür die Outposts nicht kennen und sie müssen nicht öffentlich erreichbar sein. Die HTTP Schnittstelle vom HQ Prometheus ist dagegen öffentlich erreichbar und muss entsprechend geschützt werden.
Auf dem HQ laufen außerdem diverse HTTP Probe Jobs die den Statuscode von definierten HTTP Endpoints überprüfen und an die Prometheus Instanz senden. Die gleichen Exporter wie in den Outposts können auch im HQ verwendet werden um die Maschine zu überwachen.
In einer Grafana Instanz, die auch auf dem HQ gehostet ist, können alle Daten von allen Outposts visualisiert werden. Die einzige benötigte Datenquelle ist die Prometheus Instanz die auf der gleichen Maschine läuft.

Architektur Bild PromHQ

Sicherheit

Die einzelnen Komponenten sind als Docker Images verfügbar und werden in jeweils einem Compose Projekt für den Outpost und das HQ verwaltet. Die Container kommunizieren über das HTTP Protokoll miteinander. Innerhalb der Outposts existieren sie in einem isolierten Docker Netzwerk und keine Komponente ist von außerhalb erreichbar. Im HQ sind Grafana und Prometheus öffentlich erreichbar. Grafana bringt eine eigene Userverwaltung mit und ist darauf ausgelegt öffentlich gehostet zu werden. Prometheus muss von den Outposts aus erreichbar sein aber bietet von sich aus keine Authorisierungsverfahren. Im Moment verwende ich für die Absicherung Basic Auth über einen Nginx Reverse-Proxy.

Erweiterungen

In Grafanas LGTM-Stack wird zusätzlich zu Grafana und Mimir noch Loki für Log-Aggregation und Tempo für Traces, mit denen eine Anfrage durch die einzelnen Komponenten einer Anwendung verfolgt werden kann, verwendet.

Composefiles und Config

HQ

compose.yaml:

name: promhq
services:
  grafana:
    image: grafana/grafana
    restart: unless-stopped
    ports:
      - '3000:3000'
    volumes:
      - /var/opt/grafana:/var/lib/grafana
    environment:
      - TZ=Europe/Berlin
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=password
      - GF_SERVER_DOMAIN=ultrakalteseis.de
      - GF_SERVER_ROOT_URL=https://ultrakalteseis.de/grafana/
      - GF_SERVER_SERVE_FROM_SUB_PATH=false
      - GF_SERVER_PROTOCOL=http

  prometheus:
    image: prom/prometheus:latest
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - /var/opt/prometheus/data:/prometheus/data
    command: --web.enable-remote-write-receiver --config.file=/etc/prometheus/prometheus.yml


  node-exporter:
    image: quay.io/prometheus/node-exporter:latest
    restart: unless-stopped
    volumes:
      - '/:/host:ro,rslave'
    command:
      - '--path.rootfs=/host'

  nginx-exporter:
    image: nginx/nginx-prometheus-exporter
    command: --nginx.scrape-uri=https://ultrakalteseis.de/nginx_status


  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    command:
      - '-housekeeping_interval=10s'
      - '-docker_only=true'
    volumes:
    - /:/rootfs:ro
    - /var/run:/var/run:rw
    - /sys:/sys:ro
    - /var/lib/docker/:/var/lib/docker:ro

  blackbox-exporter:
    image: quay.io/prometheus/blackbox-exporter:latest
    volumes:
      - ./blackbox.yml:/config/blackbox.yml
    command:
      - "--config.file=/config/blackbox.yml"

prometheus.yml:

global:
  scrape_interval: 15s
  
scrape_configs:
  - job_name: "node_machine12"
    scheme: "https"
    static_configs:
      - targets: ["node-exporter:9100"]

  - job_name: "nginx_machine12"
    scheme: "http"
    static_configs:
      - targets: ["nginx-exporter:9113"]

  - job_name: "cadvisor-machine12"
    static_configs:
      - targets: ["cadvisor:8080"]

  - job_name: 'blackbox'
    metrics_path: /probe
    params:
      module: [http_2xx]  # Look for a HTTP 200 response.
    static_configs:
      - targets:
        - https://url1.de
        - https://url2.de/api/
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: blackbox-exporter:9115  # The blackbox exporter's real hostname:port.

  - job_name: 'blackbox_exporter'  # collect blackbox exporter's operational metrics.
    static_configs:                # this is probably not needed...
      - targets: ['blackbox-exporter:9115']

blackbox.yaml

# blackbox.yml
modules:
  http_2xx:
    prober: http
    http:
      preferred_ip_protocol: "ip4"

Outpost

compose.yaml:

services:
  prometheus:
    image: prom/prometheus:latest
    restart: unless-stopped
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - ./.remote_write_password:/etc/.remote_write_password

  node-exporter:
    image: quay.io/prometheus/node-exporter:latest
    restart: unless-stopped
    volumes:
      - '/:/host:ro,rslave'
    command:
      - '--path.rootfs=/host'

  nginx-exporter:
    image: nginx/nginx-prometheus-exporter
    restart: unless-stopped
    command: --nginx.scrape-uri=https://harbour.informatik.hs-bremerhaven.de/nginx_status


  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    restart: unless-stopped
    command:
      - '-housekeeping_interval=15s'
      - '-docker_only=true'
      - '--disable_metrics=disk,network,tcp,udp,percpu,sched,process,referenced_memory'
    volumes:
    - /:/rootfs:ro
    - /sys:/sys:ro
    - /var/run/user/11687:/var/run:rw
    - /var/run/user/11687/docker/containerd/containerd.sock:/var/run/containerd/containerd.sock:ro
    - /home/harbour2025/.local/share/docker/:/home/harbour2025/.local/share/docker/
    - /etc/machine-id:/etc/machine-id:ro

prometheus.yml:

global:
  scrape_interval: 15s

remote_write:
  - url: "https://prometheus.url.de/api/v1/write"
    basic_auth:
      username: "prometheus_remote_write"
      password_file: "/etc/.remote_write_password"

scrape_configs:
  - job_name: "node_machine17"
    scheme: "http"
    static_configs:
      - targets: ["node-exporter:9100"]

  - job_name: "nginx_machine17"
    scheme: "http"
    static_configs:
      - targets: ["nginx-exporter:9113"]

  - job_name: "cadvisor_machine17"
    static_configs:
      - targets: ["cadvisor:8080"]