Runbook: USV-Metriken einrichten¶
Einmalige Einrichtung des Textfile-Collectors, der die Werte der CyberPower OR650ERM1U nach Prometheus bringt. Erstellt für homeserver#200, Teil 2.
Läuft auf dem NUC (10.10.10.3). Alle Schritte sind idempotent und lassen sich
wiederholen.
Warum¶
Bis jetzt wusste Prometheus über die USV nur, ob der Container existiert. Ladung,
Restlaufzeit und Netzstatus — die Werte, die im Ernstfall zählen — wurden gar nicht
erfasst. Ebensowenig die Staleness des Treibers selbst: Dass usbhid-ups 3–5×/Tag
verstummt, war nur durch Zählen im Journal sichtbar.
upsc scheitert mit Error: Data stale genau dann, wenn der Treiber still ist. Daraus
wird nuc_ups_scrape_success — und damit aus dem Problem aus Teil 1 eine Zeitreihe.
Voraussetzung¶
Der Code muss auf dem NUC liegen:
Schritt 1 — Ablageverzeichnis anlegen¶
Es gibt nur einen Schreiber (den Timer als root), deshalb reichen normale Rechte:
sudo mkdir -p /mnt/ssd/container-data/monitoring-stack/textfile
sudo chown root:root /mnt/ssd/container-data/monitoring-stack/textfile
sudo chmod 755 /mnt/ssd/container-data/monitoring-stack/textfile
Rechte sind hier schon einmal zur Falle geworden
Auf dem aiserver lief der Backup-Zeitstempel vom 14.08. bis 04.09.2026 ins Leere, weil
die Unit ohne CAP_DAC_OVERRIDE lief und root damit den normalen Rechte-Bits
unterliegt. Diese Unit hat aus demselben Grund CapabilityBoundingSet= (leer) — sie
schreibt als Eigentümer, nicht per Capability. Verzeichnis deshalb root lassen.
Schritt 2 — Skript ausführbar machen¶
Git bewahrt das Ausführbar-Bit, nach einem frischen clone aber sicherheitshalber prüfen:
test -x /opt/rpi-docker-compose/nut/scripts/ups-textfile-collector.sh \
|| sudo chmod +x /opt/rpi-docker-compose/nut/scripts/ups-textfile-collector.sh
Einmal von Hand laufen lassen, bevor der Timer scharf geschaltet wird:
sudo /opt/rpi-docker-compose/nut/scripts/ups-textfile-collector.sh
cat /mnt/ssd/container-data/monitoring-stack/textfile/ups.prom
Erwartet: rund 20 Zeilen, nuc_ups_scrape_success 1, nuc_ups_status_info{status="OL"} 1,
eine Ladung von 100 und eine Restlaufzeit um 1900 Sekunden.
Schritt 3 — Units installieren und starten¶
sudo cp /opt/rpi-docker-compose/nut/host-config/ups-textfile-collector.service \
/opt/rpi-docker-compose/nut/host-config/ups-textfile-collector.timer \
/etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now ups-textfile-collector.timer
Wirkung prüfen, nicht Dateiinhalt — eine Unit-Datei ohne Sektionskopf wird zeilenweise still verworfen und die Unit läuft unbemerkt mit Vorgabewerten weiter:
systemctl show ups-textfile-collector.timer -p TimersMonotonic
# erwartet: ... OnUnitActiveSec ... 30s ... (nicht leer)
systemctl list-timers ups-textfile-collector.timer
# erwartet: NEXT in unter 30 s
Nach einer Minute:
systemctl status ups-textfile-collector.service # erwartet: inactive (dead), Result: success
stat -c '%y' /mnt/ssd/container-data/monitoring-stack/textfile/ups.prom
# erwartet: Zeitstempel jünger als 30 s
Schritt 4 — node-exporter neu erzeugen¶
up -d genügt nicht immer: Nur weil die Compose-Datei neu ist, wird der Container
neu erzeugt — bei einem reinen restart bliebe die alte Konfiguration bestehen. Prüfen,
dass die Option wirklich angekommen ist:
docker inspect node-exporter --format '{{.Args}}' | tr ' ' '\n' | grep textfile
# erwartet: --collector.textfile.directory=/var/lib/node-exporter/textfile
Der Container muss danach auch wirklich laufen
Am 06.09. hat ein :ro an einem Verzeichnis-Mount das Entrypoint von nut-upsd
zerlegt und eine zweistündige Restart-Schleife ausgelöst — die USV-Überwachung war in
dieser Zeit tot. Dass ein Mount auflöst, heißt nicht, dass der Container damit
startet. Hier ist :ro gefahrlos (prom/node-exporter ist ein statisches Binary
ohne Entrypoint-Skript), aber geprüft wird trotzdem:
Schritt 5 — Prometheus die Regeln laden lassen¶
rule_files in der prometheus.yml ist ein Glob (/etc/prometheus/rules/*.yml), die neue
Datei wird also allein durch git pull gefunden. Nötig ist nur der Reload:
docker exec prometheus promtool check rules \
/etc/prometheus/rules/ups-alerts.yml # erwartet: SUCCESS: 7 rules found
docker exec prometheus wget --post-data="" -qO- http://localhost:9090/-/reload
# leere Antwort = erfolgreich (--web.enable-lifecycle ist gesetzt)
Alle Abfragen laufen über docker exec
Prometheus hat kein Port-Mapping auf den Host — curl localhost:9090 läuft ins
Leere. Erreichbar ist es nur containerintern oder über
prometheus.home.robinwerner.net. Im Image gibt es wget (busybox), aber weder curl
noch kill; jq läuft auf dem Host am Ende der Pipe.
Verifikation¶
# 1. Metriken kommen im Exporter an
docker exec prometheus wget -qO- http://node-exporter:9100/metrics | grep -c '^nuc_ups_'
# erwartet: 15
# 2. Der Textfile-Collector meldet keinen Parse-Fehler
docker exec prometheus wget -qO- http://node-exporter:9100/metrics \
| grep node_textfile_scrape_error
# erwartet: node_textfile_scrape_error 0
# 1 bedeutet: eine .prom-Datei war unparsbar und wurde KOMPLETT verworfen
# 3. Prometheus hat die Serie
docker exec prometheus wget -qO- \
'http://localhost:9090/api/v1/query?query=nuc_ups_battery_charge_percent' \
| jq -r '.data.result[0].value[1]'
# erwartet: 100
# 4. Die Regeln sind geladen
docker exec prometheus wget -qO- http://localhost:9090/api/v1/rules \
| jq -r '.data.groups[] | select(.name=="ups-alerts") | .rules[].name'
# erwartet: 7 Namen, beginnend mit Ups
Danach: Teil 1 auswerten¶
Sobald ein paar Tage Historie vorliegen, beantwortet diese Abfrage die eigentliche Frage aus #200 — ob die Stale-Ereignisse nach dem Reboot vom 06.09. weg sind:
# Ereignisse pro Tag (changes zählt beide Flanken, also durch 2 teilen)
changes(nuc_ups_scrape_success[24h]) / 2
Vergleichswert vor dem Reboot: rund 3,3 Ereignisse pro Tag über vier Monate.
Rückbau¶
sudo systemctl disable --now ups-textfile-collector.timer
sudo rm /etc/systemd/system/ups-textfile-collector.{service,timer}
sudo systemctl daemon-reload
sudo rm -f /mnt/ssd/container-data/monitoring-stack/textfile/ups.prom
Die Alarmregeln und die node-exporter-Option müssten zusätzlich per Git zurückgenommen
werden — sonst feuert UpsMetricMissing nach 30 Minuten.