Grafana Monitoring Stack

Nachdem ich Im Bachelorprojekt gelernt habe verschiedene Metriken von Linux System 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.

GraMP Stack

Grafana + Mimir + Prometheus

Diverse Exporter laufen als Docker Container auf den Machinen die überwacht werden 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 untersuchende 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 Mimir, eine Erweiterung von Prometheus, um diese Daten entgegenzunehmen und langfristig zu speichern. Mimir muss dafür die Outposts nicht kennen und sie müssen nicht erreichbar sein. Die Mimir HTTP Schnittstelle 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 Mimir 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 Mimir Instanz die auf der gleichen Maschine läuft.

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 Mimir erreichbar. Grafana bringt eine eigene Userverwaltung mit und ist darauf ausgelegt öffentlich gehostet zu werden. Mimir muss von den Outposts aus erreichbar sein aber bietet von sich aus [keine Authorisierungsverfahren] (https://grafana.com/docs/mimir/latest/manage/secure/authentication-and-authorization/). Im Moment verwende ich für die Absicherung [Basic Auth] (https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/) ü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: grafana
services:
  grafana:
    image: grafana/grafana
    restart: unless-stopped
    ports:
      - '3000:3000'
    volumes:
      - grafana-storage:/var/lib/grafana
    environment:
      - TZ=Europe/Berlin
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=password
      - GF_SERVER_DOMAIN=<url>
      - GF_SERVER_PROTOCOL=http

  mimir:
    image: grafana/mimir:latest
    restart: unless-stopped
    command: ["-config.file=/etc/mimir.yaml"]
    ports:
      - "9009:9009"  # HTTP port for remote write
    volumes:
      - ./mimir.yaml:/etc/mimir.yaml
      - ./data:/data
    tmpfs:
      - /tmp:size=500m,mode=1700

  prometheus:
    image: prom/prometheus:latest
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - ./.mimir_password:/etc/.mimir_password

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

volumes:
  grafana-storage:

mimir.yml:

multitenancy_enabled: false

blocks_storage:
  backend: filesystem
  filesystem:
    dir: /data/fs
  bucket_store:
    sync_dir: /data/tsdb-sync
    sync_interval: 1m
  tsdb:
    dir: /data/tsdb

compactor:
  data_dir: /tmp/mimir/compactor
  sharding_ring:
    kvstore:
      store: memberlist

distributor:
  ring:
    instance_addr: 127.0.0.1
    kvstore:
      store: memberlist

ingester:
  ring:
    instance_addr: 127.0.0.1
    kvstore:
      store: memberlist
    replication_factor: 1

ruler_storage:
  backend: filesystem
  filesystem:
    dir: /tmp/mimir/rules

server:
  http_listen_port: 9009
  log_level: debug

store_gateway:
  sharding_ring:
    replication_factor: 1

limits:
  max_label_names_per_series: 35

prometheus.yml:

global:
  scrape_interval: 30s

remote_write:
  - url: "https://<mimir_url>/api/v1/push"
    basic_auth:
      username: "prometheus_remote_write"
      password_file: "/etc/.mimir_password"

scrape_configs:
  - job_name: 'blackbox'
    metrics_path: /probe
    params:
      module: [http_2xx]  # Look for a HTTP 200 response.
    static_configs:
      - targets:
        - https://<target1>.<domain.tld>
        - https://<target2>.<domain.tld>
        - https://<target3>.<domain.tld>
    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.

Outpost

compose.yaml:

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

  node-exporter:
    image: quay.io/prometheus/node-exporter:latest
    network_mode: host
    pid: host
    restart: unless-stopped
    volumes:
      - '/:/host:ro,rslave'
    command:
      - '--path.rootfs=/host'
      - '--web.listen-address=0.0.0.0:9100'

  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://<mimir_url>/api/v1/push"
    basic_auth:
      username: "prometheus_remote_write"
      password_file: "/etc/.mimir_password"

scrape_configs:
  - job_name: "node_outpost1"
    scheme: "http"
    static_configs:
      - targets: ["172.21.0.1:9100"] # node exporters network mode is host

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

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