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"]