Cronjobs mit dem Node Exporter Textfile Collector sichtbar machen
Ein Cronjob, der nachts laeuft, verschwindet fuer Prometheus normalerweise spurlos: Kein laufender Prozess bedeutet keinen abfragbaren HTTP-Endpunkt. Der Node Exporter Textfile Collector loest dieses Problem, indem er einfache .prom-Dateien im Dateisystem einliest, die ein Bash-Skript selbst schreibt, und macht Cronjob-Dauer, Erfolg oder Fehlschlag damit ohne zusaetzlichen Dienst fuer Prometheus abfragbar.
Inhaltsverzeichnis
- 1. Warum Cronjob-Metriken ohne Textfile Collector unsichtbar bleiben
- 2. Das .prom-Dateiformat und wie der Textfile Collector es einliest
- 3. Eigene Metriken aus einem Cronjob-Skript atomar schreiben
- 4. Metrik-Typen richtig waehlen: gauge, counter und HELP/TYPE-Kommentare
- 5. Naming-Konventionen: snake_case, Einheit-Suffix und Basiseinheiten
- 6. Fallstricke bei Label-Kardinalität in Cronjob-Metriken
- 7. Timestamps und Staleness: veraltete Metriken erkennen
- 8. Fehlerbehandlung: auch fehlgeschlagene Laeufe als Metrik exportieren
- 9. Textfile Collector im Vergleich zu anderen Export-Wegen
- 10. Zusammenfassung
- 11. FAQ
1. Warum Cronjob-Metriken ohne Textfile Collector unsichtbar bleiben
Prometheus funktioniert nach dem Pull-Prinzip: Ein Prometheus-Server fragt in regelmaessigen Abstaenden einen HTTP-Endpunkt ab, der aktuelle Metrikwerte im Prometheus-Textformat liefert. Ein Bash-Cronjob, der einmal pro Nacht ein Backup erstellt und danach beendet, hat aber genau in dem Moment, in dem Prometheus ihn abfragen wuerde, laengst keinen laufenden Prozess mehr, der einen solchen Endpunkt bedienen koennte. Ohne zusaetzliches Werkzeug bleibt der Erfolg oder Misserfolg dieses Jobs fuer das Monitoring-System komplett unsichtbar.
Der Node Exporter loest dieses strukturelle Problem mit dem sogenannten Textfile Collector: Er liest beim regulaeren Scrape einfach alle Dateien mit der Endung .prom aus einem konfigurierten Verzeichnis ein und stellt deren Inhalt als eigene Metriken bereit, zusaetzlich zu den ueblichen System-Metriken wie CPU und Speicher. Ein Bash-Skript muss dafuer keinen eigenen HTTP-Server implementieren, sondern nur eine einfache Textdatei im richtigen Format an der richtigen Stelle ablegen.
2. Das .prom-Dateiformat und wie der Textfile Collector es einliest
Eine .prom-Datei folgt exakt demselben Textformat, das auch ein regulaerer Prometheus-Metrik-Endpunkt liefert: pro Metrik eine Zeile mit Metrikname, optionalen Labels in geschweiften Klammern und dem numerischen Wert, getrennt durch ein Leerzeichen. Vor jeder Metrik gehoeren zwei Kommentarzeilen mit # HELP und # TYPE, die den Metriknamen dokumentieren und den Metriktyp (gauge, counter oder summary) festlegen, damit Prometheus und nachgelagerte Tools wie Grafana die Werte korrekt interpretieren.
Der Node Exporter muss mit dem Flag --collector.textfile.directory=/var/lib/node_exporter/textfile_collector gestartet werden, damit er ueberhaupt weiss, wo er nach .prom-Dateien suchen soll. Dieses Verzeichnis muss fuer den Cronjob-Benutzer beschreibbar sein, aber der Node Exporter selbst braucht dafuer keine root-Rechte, was das Sicherheitsmodell gegenueber einem eigenen HTTP-Endpunkt pro Skript deutlich vereinfacht.
# /var/lib/node_exporter/textfile_collector/backup_job.prom
# HELP backup_job_last_success_timestamp_seconds Unix timestamp of the last successful backup
# TYPE backup_job_last_success_timestamp_seconds gauge
backup_job_last_success_timestamp_seconds 1754470800
# HELP backup_job_duration_seconds Duration of the last backup run in seconds
# TYPE backup_job_duration_seconds gauge
backup_job_duration_seconds 342.6
# HELP backup_job_size_bytes Size of the last backup archive in bytes
# TYPE backup_job_size_bytes gauge
backup_job_size_bytes 1073741824
3. Eigene Metriken aus einem Cronjob-Skript atomar schreiben
Weil der Node Exporter jede Datei im Textfile-Verzeichnis in periodischen Abstaenden liest, entsteht ein Race Condition-Risiko, wenn ein Skript direkt in die Zieldatei schreibt: Liest der Node Exporter genau in dem Moment, in dem das Skript nur die Haelfte der Zeilen geschrieben hat, bekommt Prometheus eine unvollstaendige oder kaputte Datei und verwirft die gesamte Metrikgruppe mit einem Parse-Fehler, der im Node-Exporter-Log sichtbar wird.
Die korrekte Loesung ist ein atomarer Schreibvorgang: Das Skript schreibt zuerst in eine temporaere Datei im selben Verzeichnis und benennt sie danach mit mv auf den finalen Dateinamen um. Da mv innerhalb desselben Dateisystems eine atomare Operation ist, sieht der Node Exporter entweder die alte, vollstaendige Datei oder die neue, vollstaendige Datei, aber niemals einen unvollstaendigen Zwischenzustand.
#!/usr/bin/env bash
set -euo pipefail
readonly METRICS_DIR="/var/lib/node_exporter/textfile_collector"
readonly METRICS_FILE="${METRICS_DIR}/backup_job.prom"
readonly TMP_FILE="${METRICS_FILE}.$$.tmp"
start_ts=$(date +%s)
./run-backup.sh
exit_code=$?
end_ts=$(date +%s)
duration=$(( end_ts - start_ts ))
{
echo "# HELP backup_job_last_run_timestamp_seconds Unix timestamp of the last run"
echo "# TYPE backup_job_last_run_timestamp_seconds gauge"
echo "backup_job_last_run_timestamp_seconds ${end_ts}"
echo "# HELP backup_job_duration_seconds Duration of the last run in seconds"
echo "# TYPE backup_job_duration_seconds gauge"
echo "backup_job_duration_seconds ${duration}"
echo "# HELP backup_job_success Whether the last run succeeded (1) or failed (0)"
echo "# TYPE backup_job_success gauge"
echo "backup_job_success $([[ $exit_code -eq 0 ]] && echo 1 || echo 0)"
} > "$TMP_FILE"
mv "$TMP_FILE" "$METRICS_FILE" # atomic within the same filesystem
4. Metrik-Typen richtig waehlen: gauge, counter und HELP/TYPE-Kommentare
Ein gauge ist der richtige Typ fuer Werte, die beliebig steigen und fallen koennen, etwa die Dauer des letzten Laufs oder den aktuellen Belegungsgrad eines Verzeichnisses. Ein counter passt dagegen nur fuer Werte, die niemals sinken, ausser bei einem expliziten Reset, etwa die Gesamtzahl aller jemals durchgefuehrten Backup-Laeufe. Weil ein Cronjob-Skript aber typischerweise bei jedem Lauf die Datei komplett neu schreibt statt einen persistenten Zaehler fortzufuehren, ist gauge in der Praxis fuer die meisten Textfile-Collector-Metriken die richtige und einfachere Wahl.
Die # HELP- und # TYPE-Kommentare sind kein optionales Beiwerk, sondern Teil des offiziellen Formats: Fehlt # TYPE, interpretiert Prometheus die Metrik standardmaessig als untyped, was einige PromQL-Funktionen wie rate() ablehnen. Eine konsistente, klar dokumentierte Metrik mit korrektem Typ erspart spaeter im Grafana-Dashboard viel Ratearbeit, welche mathematische Operation auf dem Wert ueberhaupt sinnvoll ist.
5. Naming-Konventionen: snake_case, Einheit-Suffix und Basiseinheiten
Prometheus hat eine etablierte Namenskonvention, die auch fuer selbst geschriebene Textfile-Metriken gilt: Metriknamen bestehen aus snake_case-Woertern, tragen als Praefix den Namen des Skripts oder Dienstes und enden mit einem Suffix, das die Einheit klar benennt, etwa _seconds, _bytes oder _total fuer Counter. Ein Name wie backup_job_duration_seconds ist selbsterklaerend, waehrend backup_time weder Einheit noch Herkunft verraet und in einem gemeinsamen Dashboard mit hunderten anderen Metriken schnell mehrdeutig wird.
Bei Einheiten gilt die Prometheus-Konvention, konsequent die Basiseinheit zu verwenden, also Sekunden statt Millisekunden und Bytes statt Kilobytes, selbst wenn das fuer Menschen im ersten Moment weniger intuitiv wirkt. Der Grund ist, dass PromQL-Abfragen und Grafana-Dashboards Einheiten-Suffixe automatisch erkennen und umrechnen koennen, aber nur, wenn konsequent dieselbe Basiseinheit ueber alle Metriken hinweg genutzt wird, statt in einem Skript Millisekunden und im naechsten Sekunden zu exportieren.
6. Fallstricke bei Label-Kardinalität in Cronjob-Metriken
Labels machen eine Metrik in Prometheus mehrdimensional filterbar, etwa backup_job_duration_seconds{database="orders"}, aber jede eindeutige Kombination aus Labelwerten erzeugt eine vollstaendig eigene Zeitreihe im Speicher von Prometheus. Ein Cronjob-Skript, das versehentlich eine Datei-ID, einen Zeitstempel oder eine generierte UUID als Labelwert verwendet, erzeugt bei jedem Lauf eine neue, niemals wieder verwendete Zeitreihe, was als Kardinalitaets-Explosion bezeichnet wird und den Prometheus-Server im schlimmsten Fall in die Knie zwingt.
Die Faustregel lautet: Ein Labelwert muss aus einer kleinen, ueberschaubaren Menge stammen, die sich ueber die Zeit kaum aendert, etwa ein Datenbankname, eine Umgebung (prod, staging) oder ein Hostname. Werte mit hoher Variabilitaet wie IDs, Dateinamen oder Zeitstempel gehoeren niemals in ein Label, sondern hoechstens in den Metrikwert selbst oder in ein separates Log-System, das fuer hochkardinale Daten gebaut ist.
7. Timestamps und Staleness: veraltete Metriken erkennen
Ein haeufig uebersehenes Problem ist, dass eine .prom-Datei nach einem erfolgreichen Lauf einfach liegen bleibt, auch wenn der zugehoerige Cronjob spaeter dauerhaft ausfaellt, etwa weil ein Server umgezogen oder der Cronjob-Eintrag versehentlich geloescht wurde. Der Wert in der Datei bleibt dann konstant und sieht fuer ein einfaches Dashboard weiterhin 'gruen' aus, obwohl in Wahrheit seit Tagen kein neuer Lauf mehr stattgefunden hat.
Die Loesung ist, immer einen expliziten _last_run_timestamp_seconds-Wert mit auszugeben und in Prometheus eine Alarmierungsregel zu definieren, die auf time() - backup_job_last_run_timestamp_seconds > 90000 prueft, also alarmiert, wenn der letzte Lauf laenger als etwa 25 Stunden zurueckliegt. Diese Art von Alarm erkennt zuverlaessig sowohl fehlgeschlagene als auch komplett ausgebliebene Laeufe, waehrend eine reine Erfolgs-Metrik ohne Zeitstempel einen ausbleibenden Cronjob niemals meldet.
# Example alerting rule (prometheus.rules.yml)
- alert: BackupJobStale
expr: time() - backup_job_last_run_timestamp_seconds > 90000
for: 10m
labels:
severity: critical
annotations:
summary: "Backup job has not run successfully in over 25 hours"
8. Fehlerbehandlung: auch fehlgeschlagene Laeufe als Metrik exportieren
Ein Cronjob-Skript, das bei einem Fehler mit set -e sofort abbricht, bevor es seine Metrik-Datei schreibt, hinterlaesst genau in dem Moment, in dem Monitoring am wichtigsten waere, gar keine Information. Das richtige Muster ist, die Metrik-Datei in einem trap auf EXIT zu schreiben, sodass sie garantiert auch bei einem fehlgeschlagenen Lauf mit dem korrekten Exit-Code und der bis dahin gemessenen Dauer entsteht, statt komplett zu fehlen.
So laesst sich in Prometheus zuverlaessig zwischen drei Zustaenden unterscheiden: erfolgreicher Lauf, fehlgeschlagener Lauf mit sichtbarer Fehlermetrik, und komplett ausgebliebener Lauf, erkennbar am veralteten Timestamp aus dem vorherigen Abschnitt. Nur alle drei Zustaende zusammen ergeben ein vollstaendiges Bild der tatsaechlichen Zuverlaessigkeit eines Cronjobs.
#!/usr/bin/env bash
set -euo pipefail
readonly METRICS_FILE="/var/lib/node_exporter/textfile_collector/backup_job.prom"
readonly TMP_FILE="${METRICS_FILE}.$$.tmp"
start_ts=$(date +%s)
exit_code=0
write_metrics() {
local end_ts duration
end_ts=$(date +%s)
duration=$(( end_ts - start_ts ))
{
echo "# TYPE backup_job_success gauge"
echo "backup_job_success $([[ $exit_code -eq 0 ]] && echo 1 || echo 0)"
echo "# TYPE backup_job_duration_seconds gauge"
echo "backup_job_duration_seconds ${duration}"
echo "# TYPE backup_job_last_run_timestamp_seconds gauge"
echo "backup_job_last_run_timestamp_seconds ${end_ts}"
} > "$TMP_FILE"
mv "$TMP_FILE" "$METRICS_FILE"
}
trap write_metrics EXIT
./run-backup.sh || exit_code=$?
9. Textfile Collector im Vergleich zu anderen Export-Wegen
Neben dem Textfile Collector bietet Prometheus mit dem Pushgateway einen zweiten Weg fuer kurzlebige Jobs, bei dem das Skript seine Metriken aktiv per HTTP an einen zentralen Dienst pusht, statt sie lokal in eine Datei zu schreiben. Der Textfile Collector eignet sich besser fuer Cronjobs, die ohnehin auf demselben Host wie ein Node Exporter laufen, waehrend das Pushgateway sinnvoll ist, wenn kurzlebige Jobs in einer Umgebung laufen, in der kein lokaler Node Exporter existiert, etwa in kurzlebigen CI-Runnern oder Serverless-Funktionen.
| Methode | Voraussetzung | Push oder Pull | Typischer Einsatz |
|---|---|---|---|
| Textfile Collector | Node Exporter auf demselben Host | Pull, Node Exporter liest Datei | Cronjobs auf bestehenden Servern |
| Pushgateway | Zentraler Pushgateway-Dienst | Push per HTTP vom Skript | Kurzlebige Jobs, CI-Runner, Serverless |
| Eigener HTTP-Endpunkt | Dauerhaft laufender Prozess | Pull, direkter Scrape | Langlebige Dienste und Daemons |
| StatsD-Exporter | StatsD-kompatibler Sender | Push per UDP/TCP | Anwendungen mit vorhandener StatsD-Anbindung |
Mironsoft
Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur
Shell-Skripte, die in der Produktion zuverlässig laufen?
Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.
Code-Review
ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.
Refactoring
Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.
CI-Integration
ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.
10. Zusammenfassung
Bash-Skript-Metriken an Prometheus exportieren: Das Wichtigste auf einen Blick
Grundprinzip
Der Textfile Collector liest .prom-Dateien aus einem konfigurierten Verzeichnis ein, ohne dass das Skript einen HTTP-Server braucht.
Atomares Schreiben
Erst in eine temporaere Datei schreiben, dann mit mv auf den finalen Namen umbenennen, um kaputte Zwischenzustaende zu vermeiden.
Naming
snake_case mit Einheiten-Suffix wie _seconds oder _bytes, konsequent in Basiseinheiten statt Millisekunden oder Kilobytes.
Kardinalitaet
Nur Labelwerte mit kleiner, stabiler Wertemenge verwenden, niemals IDs, Zeitstempel oder Dateinamen als Label.