Datenerfassung mit Prometheus
Prometheus1 sammelt Metriken von verschiedenen Quellen und bietet leistungsstarke Funktionen zur Analyse und Visualisierung dieser Metriken. Prometheus ermöglicht das Erkennen von Anomalien, das Überwachen von Service-Level-Agreements (SLAs) und das Generieren von Warnungen bei Abweichungen. Es bietet eine flexible Abfragesprache namens PromQL, zur Abfrage von Metriken und zur Durchführung komplexer Auswertungen. Es ist in der Cloud-Native- und DevOps-Welt weit verbreitet und wird häufig mit anderen Tools wie Grafana zur Visualisierung verwendet.
In dem Artikel Smarthomegeräte mit ESP Mikrokontrollern hatte ich ein Script für einen Temperatursensor vorgestellt, welches seine Messwerte an einen MQTT Broker sendet. Das ist alles gut und schön, aber ich möchte mich nicht ständig auf einem Server anmelden müssen und meine Temperaturwerte über kryptische Kommandozeilenbefehle auslesen. Außerdem interessiert mich ggf. auch gar nicht die aktuelle Temperatur, sondern der Wert von gestern oder generell der Wert im zeitlichen Verlauf.
Hier kommt prometheus
ins Spiel. Die Installation ist Debiantypisch einfach erledigt, da wir MQTT-Daten verarbeiten wollen, installieren wir direkt auch noch das entsprechende Zusatztool:
apt install prometheus prometheus-mqtt-exporter
Die Konfiguration von Prometheus erfolgt über die Datei /etc/prometheus/prometheus.yml
2.
# Sample config for Prometheus.
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Attach these labels to any time series or alerts when communicating with
# external systems (federation, remote storage, Alertmanager).
external_labels:
monitor: 'example'
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: 'prometheus'
# Override the global default and scrape targets from this job every 5 seconds.
scrape_interval: 5s
scrape_timeout: 5s
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ['localhost:9090']
- job_name: 'mqttesp'
static_configs:
- targets: ['localhost:9640']
#remote_write:
# - url: "http://localhost:8086/api/v1/prom/write?db=prometheus"
#remote_read:
# - url: "http://localhost:8086/api/v1/prom/read?db=prometheus"
Wichtig ist hier vor allem der Abschnitt scrape_configs
, hier Verbinden wir Prometheus mit seinen externen Datenquellen, den sog. Exportern3. In diesem Beispiel sind zwei Datenquellen angegeben, zum einen job_name: prometheus
. Über diesen Sammelt Prometheus Daten zu sich selbst. Dies ist nicht zwingend nötig, aber es gibt durchaus ein paar interessante Werte, zudem können wir so schnell prüfen, ob Prometheus überhaupt Daten verarbeitet.
Unser MQTT-Exporter wird im Abschnitt job_name: 'mqttesp'
konfiguriert. Wir werden diesen auf localhost
betrieben und hierfür den Port 9640
verwenden.
Für den MQTT-Exporter erstellen wir die Konfigdatei /etc/prometheus/mqtt-8266-exporter.yml
:
# Settings for the MQTT Client. Currently only these three are supported
mqtt:
# The MQTT broker to connect to
server: tcp://localhost:1883
# Optional: Username and Password for authenticating with the MQTT Server
# user: bob
# password: happylittleclouds
# The Topic path to subscribe to. Be aware that you have to specify the wildcard.
topic_path: v1/devices/me/+
# Optional: Regular expression to extract the device ID from the topic path. The default regular expression, assumes
# that the last "element" of the topic_path is the device id.
# The regular expression must contain a named capture group with the name deviceid
# For example the expression for tasamota based sensors is "tele/(?P<deviceid>.*)/.*"
# device_id_regex: "(.*/)?(?P<deviceid>.*)"
# The MQTT QoS level
qos: 0
cache:
# Timeout. Each received metric will be presented for this time if no update is send via MQTT.
# Set the timeout to -1 to disable the deletion of metrics from the cache. The exporter presents the ingest timestamp
# to prometheus.
timeout: 60m
# This is a list of valid metrics. Only metrics listed here will be exported
metrics:
-
# The name of the metric in prometheus
prom_name: temperature
# The name of the metric in a MQTT JSON message
mqtt_name: temperature
# The prometheus help text for this metric
help: DHT22 temperature reading
# The prometheus type for this metric. Valid values are: "gauge" and "counter"
type: gauge
# A map of string to string for constant labels. This labels will be attached to every prometheus metric
const_labels:
sensor_type: dht22
-
# The name of the metric in prometheus
prom_name: humidity
# The name of the metric in a MQTT JSON message
mqtt_name: humidity
# The prometheus help text for this metric
help: DHT22 humidity reading
# The prometheus type for this metric. Valid values are: "gauge" and "counter"
type: gauge
# A map of string to string for constant labels. This labels will be attached to every prometheus metric
const_labels:
sensor_type: dht22
-
# The name of the metric in prometheus
prom_name: heat_index
# The name of the metric in a MQTT JSON message
mqtt_name: heat_index
# The prometheus help text for this metric
help: DHT22 heatIndex calculation
# The prometheus type for this metric. Valid values are: "gauge" and "counter"
type: gauge
# A map of string to string for constant labels. This labels will be attached to every prometheus metric
const_labels:
sensor_type: dht22
-
# The name of the metric in prometheus
prom_name: state
# The name of the metric in a MQTT JSON message
mqtt_name: state
# Regular expression to only match sensors with the given name pattern
sensor_name_filter: "^.*-light$"
# The prometheus help text for this metric
help: Light state
# The prometheus type for this metric. Valid values are: "gauge" and "counter"
type: gauge
# A map of string to string for constant labels. This labels will be attached to every prometheus metric
const_labels:
sensor_type: ikea
# When specified, enables mapping between string values to metric values.
string_value_mapping:
# A map of string to metric value.
map:
off: 0
low: 0
# Metric value to use if a match cannot be found in the map above.
# If not specified, parsing error will occur.
error_value: 1
❯ cat /etc/prometheus/mqtt_8266_exporter.yaml
# Settings for the MQTT Client. Currently only these three are supported
mqtt:
# The MQTT broker to connect to
server: tcp://localhost:1883
# Optional: Username and Password for authenticating with the MQTT Server
user: bob
password: happylittleclouds
# The Topic path to subscribe to. Be aware that you have to specify the wildcard.
topic_path: home/+/SENSOR
# Optional: Regular expression to extract the device ID from the topic path. The default regular expression, assumes
# that the last "element" of the topic_path is the device id.
# The regular expression must contain a named capture group with the name deviceid
# For example the expression for tasamota based sensors is "tele/(?P<deviceid>.*)/.*"
device_id_regex: "home/(?P<deviceid>.*)/.*"
# The MQTT QoS level
qos: 0
cache:
# Timeout. Each received metric will be presented for this time if no update is send via MQTT.
# Set the timeout to -1 to disable the deletion of metrics from the cache. The exporter presents the ingest timestamp
# to prometheus.
timeout: 60m
# This is a list of valid metrics. Only metrics listed here will be exported
metrics:
# The name of the metric in prometheus
- prom_name: mqtt_esp_temperature
mqtt_name: "temperature"
help: "temperature in degrees celsius"
type: gauge
- prom_name: mqtt_esp_humidity
mqtt_name: "humidity"
help: "humidity in percent"
type: gauge
- prom_name: mqtt_esp_pressure
mqtt_name: "pressure"
help: "air pressure in hpa"
type: gauge
Zunächst geben wir dem Exporter die nötigen Daten, um sich mit unserem MQTT-Broker zu verbinden. In unserem Fall läuft er auf dem gleichen System (localhost
) auf Port 1883
. Wir müssen uns am Broker anmelden und nutzen dazu den User bob
mit dem Kennwort happylittleclouds
.
Unser ESP8266 sendet MQTT Daten im Format home/bed/SENSOR {"temperature": 28.35, "humidity": 35.15, "pressure": 1005.34}
wir müssen dem MQTT-Exporter dieses Format daher erklären. Der vordere Teil home/bed/SENSOR
ist der sog. MQTT Topic. Nur dieser eine Sensor meldet Daten an diesen Topic. Andere Sensoren können aber z.B. home/kitchen/SENSOR
oder home/living/SENSOR
nutzen. Der mittlere Part würde uns also jeweils mitteilen, in welchem Raum sich der Sensor befindet. Das ist eine praktische Information, wenn wir die Daten grafisch aufbereiten möchten. Daher konfigurieren wir den MQTT-Exporter in der Zeile topic_path
durch die Angabe des +
Zeichens an dieser Stelle des Topics diesen Wert gesondert als deviceid
zu verarbeiten: topic_path: home/+/SENSOR
Abschließend müssen wir dem MQTT Exporter noch mitteilen, welche Daten unser Sensor eigentlich sendet, die passiert im Abschnitt metrics
. Die Definition besteht immer aus einer eindeutigen ID prom_name
den Prometheus intern zur Identifikation des Messwerts nutzt. Es folgt ein menschenlesbarer Name mqtt_name
und optional eine Beschreibung des Messwerts help
. Außerdem müssen wir Prometheus mitteilen was für einen Datentyp wir hier an ihn übermitteln type
. Der Wert gauge
4 beschreibt hierbei eine Zahl, die größer oder kleiner werden kann. Andere Datentypen wären z.B. counter
5, histogram
6 oder summary
7.
Wir können den MQTT-Exporter mit beliebig vielen Konfigurationsdateien versorgen. Pro Konfigdatei starten wir einfach eine neue Instanz des Exporters. Ich nutze hierfür gern supervisord
, dieser ist mit apt install supervisor
schnell installiert.
Eine Konfigurationsdatei ist schnell erstellt und in /etc/supervisor/conf.d/mqtt-8266-exporter.conf
abgelegt:
[program:mqttespexporter]
user=prometheus
command=/usr/bin/prometheus-mqtt-exporter -config /etc/prometheus/mqtt_8266_exporter.yml -listen-port 9640
directory=/etc/prometheus
stdout_logfile=/var/log/supervisor/mqtt-esp.log
stderr_logfile=/var/log/supervisor/mqtt-esp.log
autostart=true
autorestart=true
Nach einem supervisorctl reread
zum Einlesen der Konfiguration sollte unser MQTT-Exporter nun starten. Wir können dies mit supervisorctl status mqttespexporter
prüfen:
mqttespexporter RUNNING pid 821487, uptime 0:00:04
Ob der Exporter auch wirklich Daten verarbeitet, können wir mit einer schnellen curl localhost:9640/metrics | grep received_messages
Abfrage klären:
# HELP received_messages received messages per topic and status
# TYPE received_messages counter
received_messages{status="success",topic="home/bed/SENSOR"} 2
received_messages{status="success",topic="home/kitchen/SENSOR"} 2
Bei der ersten Abfrage sollte beachtet werden, dass die ESPs ihre Daten mit meinem Script nur einmal pro Minute senden, es kann also etwas dauern, bis der MQTT Exporter mit entsprechenden Daten aufwarten kann.
Der Wert 2
gibt an, dass der Exporter bereits zwei Datensätze von den Sensoren bed
und kitchen
erhalten hat. Möchten wir die jeweils letzten Wert einsehen können wir dies mit curl localhost:9640/metrics | grep mqtt_esp
:
# HELP mqtt_esp_humidity humidity in percent
# TYPE mqtt_esp_humidity gauge
mqtt_esp_humidity{sensor="bed",topic="home/bed/SENSOR"} 37.81 1689321540610
mqtt_esp_humidity{sensor="kitchen",topic="home/kitchen/SENSOR"} 35 1689321550725
# HELP mqtt_esp_pressure air pressure in hpa
# TYPE mqtt_esp_pressure gauge
mqtt_esp_pressure{sensor="bed",topic="home/bed/SENSOR"} 1009.77 1689321540610
# HELP mqtt_esp_temperature temperature in degrees celsius
# TYPE mqtt_esp_temperature gauge
mqtt_esp_temperature{sensor="bed",topic="home/bed/SENSOR"} 25.79 1689321540610
mqtt_esp_temperature{sensor="kitchen",topic="home/kitchen/SENSOR"} 25.9 1689321550725
Wie man sieht sendet nur der Sensor bed
Daten zum Luftdruck. Dies ist so korrekt, da ich hier einen anderen Sensor verbaut habe, der DHT22 kann nur Temperatur und Luftfeuchte messen. Die restlichen Messwerte melden beide Sensoren.
Damit die Daten nun auch wirklich von Prometheus verarbeitet werden, müssen wir diesen noch mit systemctl start prometheus
starten. Bei mir mag Prometheus seit dem Update auf Debian Bookworm nicht mehr über systemd
starten und es war mir irgendwann zu blöd weiter nach dem Fehler zu suchen, seitdem starte ich Prometheus selbst ebenfalls über supervisord
. Hierfür nutze ich folgende /etc/supervisor/conf.d/prometheus.conf
:
[program:prometheus]
user=root
command=/usr/bin/prometheus --storage.tsdb.retention.time=3y --web.enable-admin-api --config.file="/etc/prometheus/prometheus.yml"
directory=/etc/prometheus/
stdout_logfile=/var/log/supervisor/prometheus.log
stderr_logfile=/var/log/supervisor/prometheus.log
autostart=true
autorestart=true
Hervorzuheben ist hier noch der Parameter --storage.tsdb.retention.time=3y
mit diesem wird Prometheus angewiesen seine Daten 3 Jahre lang zu speichern. Eigentlich sollte man dies so nicht machen, da Prometheus nicht wirklich für eine Langzeitspeicherung gedacht ist. Stattdessen sollte man die Prometheus Daten in eine InfluxDB8 übertragen. Neue Versionen von InfluxDB können Daten von Prometheus-Exportern auch direkt verarbeiten9, in diesem Fall kann die Einrichtung von Prometheus selbst entfallen. Ich mag jedoch die InfluxDB-Abfragesyntax nicht so gern, daher nutze ich Prometheus auch zur Langzeitspeicherung.
In systemd
habe ich Prometheus entsprechend mit systemctl disable prometheus
und systemctl mask prometheus
deaktiviert.
Prometheus sollte nach dem Start sofort damit beginnen Daten vom Exporter zu verarbeiten. Anschauen können wir uns das über die Weboberfläche von Prometheus, diese ist unter localhost:9090
erreichbar.
In der oberen Eingabezeile können wir jetzt nach unseren Datenquellen suchen:
Wenn wir auf den Button execute
klicken, liest Prometheus die angeforderten Daten aus:
Sobald wir auf den Tab Graph
wechseln, können wir uns die Werte im zeitlichen Verlauf ansehen:
Über den Button Add Graph
können wir weitere Abfragen durchführen und anzeigen lassen. Leider speichert Prometheus unsere Abfragen nicht dauerhaft, man müsste den Vorgang jedes Mal wiederholen, wenn man die Weboberfläche neu lädt. Daher nutzen viele Prometheus im Zusammenspiel mit Grafana10. Die Einrichtung von Grafana werde ich in einem späteren Artikel erläutern.
-
https://prometheus.io/docs/prometheus/latest/configuration/configuration/ ↩
-
https://prometheus.io/docs/instrumenting/exporters/#exporters-and-integrations ↩
-
https://prometheus.io/docs/tutorials/understanding_metric_types/#gauge ↩
-
https://prometheus.io/docs/tutorials/understanding_metric_types/#counter ↩
-
https://prometheus.io/docs/tutorials/understanding_metric_types/#histogram ↩
-
https://prometheus.io/docs/tutorials/understanding_metric_types/#summary ↩
-
https://docs.influxdata.com/influxdb/v1.8/supported_protocols/prometheus/ ↩
-
https://docs.influxdata.com/flux/v0.x/prometheus/scrape-prometheus/ ↩