Bash-Skript-Metriken an Prometheus exportieren: Node Exporter Textfile Collector
AI generated
$_
#!/
Bash · Prometheus · Monitoring · Node Exporter
Bash-Skript-Metriken an Prometheus exportieren
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.

17 Min. Lesezeit Textfile Collector · .prom · atomarer Schreibvorgang Bash · Prometheus · Node Exporter

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.

11. FAQ: Bash-Skript-Metriken an Prometheus exportieren: Das Wichtigste auf einen Blick

1Was ist der Node Exporter Textfile Collector?
Eine Funktion des Node Exporters, die periodisch alle .prom-Dateien aus einem konfigurierten Verzeichnis einliest und deren Inhalt als zusaetzliche Prometheus-Metriken bereitstellt, ohne dass das erzeugende Skript einen eigenen HTTP-Server braucht.
2Warum muss ich die .prom-Datei atomar schreiben?
Weil der Node Exporter jederzeit lesen kann. Schreibt ein Skript direkt in die Zieldatei, kann der Node Exporter eine unvollstaendige Datei erwischen und die Metrikgruppe mit einem Parse-Fehler verwerfen.
3Wo muss die .prom-Datei liegen?
Im Verzeichnis, das dem Node Exporter mit --collector.textfile.directory uebergeben wurde. Dieses Verzeichnis muss fuer den Cronjob-Benutzer beschreibbar sein.
4Soll ich gauge oder counter fuer meine Cronjob-Metrik verwenden?
Fast immer gauge, weil ein Cronjob-Skript die Datei typischerweise bei jedem Lauf komplett neu schreibt statt einen persistenten Zaehler fortzufuehren. counter passt nur fuer Werte, die niemals ohne Reset sinken.
5Welche Einheit soll ich fuer Zeitwerte verwenden?
Immer Sekunden als Basiseinheit, mit dem Suffix _seconds im Metriknamen. Prometheus-Konventionen vermeiden Millisekunden oder andere abgeleitete Einheiten in Metriknamen.
6Was ist eine Label-Kardinalitaets-Explosion?
Wenn ein Labelwert aus einer sehr grossen oder unbegrenzten Menge stammt, etwa eine UUID oder ein Zeitstempel, erzeugt jede Kombination eine eigene Zeitreihe im Speicher von Prometheus und kann den Server ueberlasten.
7Wie erkenne ich, dass ein Cronjob komplett ausgeblieben ist?
Ueber einen expliziten _last_run_timestamp_seconds-Wert und eine Alarmierungsregel, die prueft, ob die Zeit seit dem letzten Lauf einen Schwellenwert ueberschreitet. Eine reine Erfolgs-Metrik ohne Zeitstempel erkennt ausbleibende Laeufe nicht.
8Wie exportiere ich auch fehlgeschlagene Laeufe als Metrik?
Mit einem trap auf EXIT, der die Metrik-Datei unabhaengig vom Exit-Code des eigentlichen Jobs schreibt, sodass auch ein Fehlschlag sichtbar wird statt komplett zu fehlen.
9Was ist der Unterschied zum Pushgateway?
Der Textfile Collector setzt einen lokal laufenden Node Exporter voraus und liest Dateien passiv. Das Pushgateway nimmt aktive HTTP-Pushes entgegen und eignet sich besser fuer kurzlebige Jobs ohne lokalen Node Exporter.
10Braucht der Node Exporter root-Rechte fuer den Textfile Collector?
Nein. Es reicht, wenn das Textfile-Verzeichnis fuer den Cronjob-Benutzer beschreibbar und fuer den Node-Exporter-Benutzer lesbar ist.