Den Linux-Agenten überwachen
Wie Sie den Agenten aus Zabbix, Nagios, Icinga, Checkmk oder Prometheus überwachen: was eine Prüfung
abdecken sollte, das JSON, das status --json ausgibt, die nötigen Rechte, ein fertiges
Zabbix-Template und kurze Einrichtungen für die anderen Systeme. Verfügbar ab Version 0.3.40.
Was eine Prüfung abdecken sollte
Der Agent schützt einen Host auf zwei Wegen, die auf verschiedene Weise ausfallen. Die Listen im Kernel blocken weiter, auch wenn der Agent weg ist. Ein gestoppter Agent sieht von außen darum nicht kaputt aus: die Firewall verwirft die bekannten Adressen weiter, und nichts Neues wird erkannt, gesperrt oder gemeldet. Eine brauchbare Prüfung umfasst deshalb mehr als den Prozess.
| Frage | Wo die Antwort steht | Gesund |
|---|---|---|
| Läuft der watch-Daemon? | daemon.running, oder systemctl is-active reportedip-agent | true, active |
| Läuft der Sync-Timer? | sync.last_attempt_age_s | unter 5400 (90 Minuten; ein Host holt je nach Tarif alle 15 oder alle 60 Minuten) |
| Steht die Kette im Paketpfad? | chain_ok | true, auf einem Host, der blockt |
| Sonst etwas nicht in Ordnung? | Exit-Code, status, problems | 0, ok, leer |
Der Daemon schreibt alle 30 Sekunden einen Heartbeat. status hält ihn für tot, wenn dieser
Heartbeat älter als zwei Minuten ist. Ein gestoppter Daemon zeigt sich also in der nächsten Prüfung, die
mindestens zwei Minuten nach dem Stopp läuft. Sonst ändert sich in status nichts, wenn der
Daemon stoppt, und genau dafür gibt es diese Zeile.
status --json
reportedip-agent status --json führt dieselben Prüfungen aus wie status und gibt
das Ergebnis als ein JSON-Dokument aus. Der Exit-Code folgt denselben Regeln, mit einem Unterschied: der Textmodus fragt die API live und ist eingeschränkt, wenn das scheitert, --json fragt nicht und ist es aus diesem Grund also nie. Es geht keine Anfrage
an die API: der Kontoteil ist die Antwort, die der letzte Sync zwischengespeichert hat, und
account.cached_age_s sagt, wie alt sie ist. Eine Prüfung alle paar Minuten kostet darum
nichts und kann nicht am Netz hängen bleiben.
{
"schema": 1,
"status": "degraded",
"code": 1,
"problems": [
{ "key": "daemon", "text": "the watch daemon is not running, last heartbeat 6m0s ago" }
],
"version": "v0.3.40",
"generated_at": "2026-09-29T20:40:00Z",
"daemon": { "running": false, "heartbeat_age_s": 361 },
"update": { "latest": "0.3.40", "behind": false, "last_check_age_s": 5120, "last_error": "" },
"backend": "nftables",
"mode": "drop",
"chain_ok": true,
"disk": { "free_mb": 18234, "min_mb": 200 },
"sync": { "running": false, "last_attempt_age_s": 412, "last_ok_age_s": 412 },
"lists": { "ssh": { "ipv4": 21873, "ipv6": 1204, "last_ok_age_s": 412, "fails": 0, "last_error": "" } },
"bans": { "enabled": true, "kernel": 3, "records": 3, "missing": 0 },
"sources": { "/var/log/auth.log": { "state": "ok", "last_line_age_s": 12, "hits": 58 } },
"queue": { "entries": 0, "max": 5000, "oldest_age_s": -1, "sent_total": 311, "sender_paused": false },
"account": { "role": "reportedip_professional", "reports_today": 214, "report_limit": 1000,
"feed": true, "license": "licensed", "reputation_listed": false,
"group": "", "cached_age_s": 14320 },
"rules": { "total": 20, "operator": 0, "disabled": 0, "problems": 0 },
"health": {}
}
| Feld | Bedeutung |
|---|---|
schema | Die Version dieses Formats. Felder kommen unter derselben Nummer nur hinzu; ein Feld wird nie umbenannt, umgetypt oder entfernt, ohne sie zu erhöhen. |
status, code | ok und 0, degraded und 1, error und 2. Dasselbe wie der Exit-Code. |
problems | Ein Eintrag je Grund für degraded, jeweils mit einem kurzen key (daemon, chain, lists, sources, queue, bans, disk, update, license, reputation, backend, state) und einem Satz. Leer, wenn der Host gesund ist. |
*_age_s | Alter in ganzen Sekunden. -1 heißt nie oder unbekannt, zum Beispiel vor dem ersten Sync. |
backend | ipset, nftables, oder none auf einem Host, der nur meldet. Ein solcher Host hat keine Kette und keinen Sync, chain_ok ist dort also absichtlich false. |
lists, sources | Ein Objekt je Liste und je Log-Quelle, mit dem Namen als Schlüssel. |
health | Die offenen Zustände, dieselben, um die es in der Zustandsmail geht. |
error | Nur bei code 2: die Konfiguration oder das State-Verzeichnis ist nicht nutzbar. Das Dokument wird trotzdem ausgegeben, damit die Prüfung einen Wert bekommt und nicht nichts. |
Das Dokument enthält nur Zähler, Alter und Zustände: keine Adresse, keinen Key und keine Logzeile. Ein Monitoring-Server kann es also ohne weiteres Nachdenken speichern.
Rechte
status braucht root. Die Konfiguration enthält Ihren API-Key und ist nur für root lesbar,
das State-Verzeichnis hat 0700, und die Firewall-Sets lassen sich nur mit root-Rechten lesen.
Ein Monitoring-Agent läuft als eigener Benutzer und braucht darum eine sudo-Regel für genau diesen einen
Befehl und sonst nichts:
# /etc/sudoers.d/reportedip-monitoring, mode 0440
# Replace zabbix with the user your monitoring agent runs as (nagios, icinga, ...).
zabbix ALL=(root) NOPASSWD: /usr/local/bin/reportedip-agent status --json
# Check the file before it is used, then test as that user:
visudo -cf /etc/sudoers.d/reportedip-monitoring
sudo -u zabbix sudo -n /usr/local/bin/reportedip-agent status --json
Zabbix
Für Zabbix 7.0 und neuer, mit Zabbix agent oder Zabbix agent 2. Ein UserParameter holt das Dokument alle fünf Minuten, und jedes andere Item nimmt seinen Wert aus diesem einen Aufruf. Der Agent läuft also einmal je Intervall und nicht einmal je Item.
Die Prüfung einrichten
# Zabbix agent 2: /etc/zabbix/zabbix_agent2.d/reportedip.conf
# Zabbix agent: /etc/zabbix/zabbix_agentd.d/reportedip.conf
UserParameter=reportedip.status,sudo -n /usr/local/bin/reportedip-agent status --json 2>/dev/null || true
# then the sudo rule from the section above, and:
systemctl restart zabbix-agent2 # or zabbix-agent
zabbix_agent2 -t reportedip.status # prints the document
Das || true verhindert, dass ein Host im Zustand degraded zu einem nicht unterstützten Item wird:
der Exit-Code steht als code im Dokument, und das Item muss das Dokument in jedem Fall
bekommen.
Das Template importieren
Zwei Varianten desselben Templates, je nachdem, wie Ihre Agenten mit dem Server sprechen. Importieren Sie eine davon unter Data collection, Templates, Import und verknüpfen Sie sie mit jedem Host, auf dem der Agent läuft.
- reportedip_agent_zabbix7.yaml: Items vom Typ Zabbix agent (passiv)
- reportedip_agent_zabbix7_active.yaml: Items vom Typ Zabbix agent (aktiv)
Das Template setzt am Status-Item ein Timeout von 20 Sekunden. Ein Agent älter als 7.0 ignoriert das und
nimmt sein eigenes Timeout, standardmäßig 3 Sekunden; stellen Sie es auf solchen Hosts in der
Agent-Konfiguration auf 20.
Was das Template überwacht
| Trigger | Schwere | Löst aus, wenn |
|---|---|---|
| watch daemon is not running | High | daemon.running false ist oder kein Prozess reportedip-agent watch läuft. Die Prozesszählung braucht keinen UserParameter und funktioniert darum auch, wenn die Statusprüfung es nicht tut. |
| firewall chain is not in place | High | chain_ok auf einem Host mit Backend false ist. |
| configuration error, the agent cannot run | High | code 2 ist. |
no sync for {$RIP.SYNC.MAXAGE} | Average | Die letzte Feed-Anfrage älter ist als das Makro (90 Minuten) oder es keine gab, auf einem Host mit Backend. Der Trigger hängt vom Lizenz-Trigger ab, denn ohne Lizenz geht keine Anfrage hinaus. |
no data for {$RIP.NODATA} | Average | Das Status-Item 15 Minuten lang nichts bekommen hat. |
report queue over {$RIP.QUEUE.PCT}% | Warning | Die Queue voller ist als das Makro (80). |
| host has no server licence, feed paused | Warning | account.license unlicensed ist. |
| reporting address is listed | Warning | Die Adresse, von der dieser Host meldet, in der Community-Datenbank steht. |
| agent degraded | Warning | Jeder andere Grund für code 1. Er hängt von den Triggern darüber ab, ein Problem löst also einen Alarm aus und nicht zwei. Das Item Problems nennt den Grund. |
| newer agent version available | Info | Es gibt ein neueres Release. Das Selbst-Update installiert es innerhalb von sechs Stunden, der Trigger bleibt also nur offen, wenn der Update-Weg kaputt ist oder auto_update aus ist. |
Zwei Discovery-Regeln legen je Liste (IPv4- und IPv6-Einträge, Fehlschläge in Folge) und je Log-Quelle (Zustand, Treffer, Alter der letzten Zeile) eigene Items an. Jede Schwelle ist ein Makro am Template und lässt sich je Host ändern.
Wenn die Items leer bleiben
Value of type "string" is not suitable for value type "Numeric" oder ein Item, das nie einen Wert
bekommt, heißt fast immer, dass der Befehl als Zabbix-Benutzer nicht laufen konnte: die sudo-Regel fehlt,
hat den falschen Modus (sie muss 0440 haben, sonst ignoriert sudo die Datei) oder nennt einen
anderen Pfad. Führen Sie die Testzeile aus dem Abschnitt Rechte als Zabbix-Benutzer aus. Ein aktiver
Agent holt seine Item-Liste nur alle RefreshActiveChecks Sekunden, der erste Wert kann nach
dem Verknüpfen des Templates also ein paar Minuten dauern.
Nagios und Icinga
Die Exit-Codes von status sind bereits die Plugin-Codes: 0 OK, 1 WARNING, 2 CRITICAL. Ein
Plugin sollte aber eine Zeile ausgeben, darum macht ein kurzer Wrapper aus dem Dokument eine Zeile mit
Performance-Daten. Er braucht jq.
#!/bin/sh
# /usr/local/lib/nagios/plugins/check_reportedip
out=$(sudo -n /usr/local/bin/reportedip-agent status --json 2>/dev/null)
rc=$?
[ -n "$out" ] || { echo "REPORTEDIP UNKNOWN: no status document, check the sudo rule"; exit 3; }
line=$(printf '%s' "$out" | jq -r '"REPORTEDIP " + (.status | ascii_upcase) + ": "
+ (if (.problems | length) > 0 then ([.problems[].text] | join("; ")) else "daemon running, chain ok" end)
+ " | queue=\(.queue.entries) bans=\(.bans.kernel) sync_age=\(.sync.last_attempt_age_s)s"') || {
echo "REPORTEDIP UNKNOWN: no status document"; exit 3; }
echo "$line"
[ "$rc" -le 2 ] && exit "$rc" || exit 3
# NRPE: /etc/nagios/nrpe.d/reportedip.cfg
command[check_reportedip]=/usr/local/lib/nagios/plugins/check_reportedip
# Icinga 2 with the agent: a CheckCommand that runs the same script
object CheckCommand "reportedip" {
command = [ "/usr/local/lib/nagios/plugins/check_reportedip" ]
}
Die sudo-Regel aus dem Abschnitt Rechte gilt auch hier, mit dem Benutzer, unter dem NRPE oder der Icinga-Agent läuft.
Checkmk
Ein Local Check läuft als root im Checkmk-Agenten und braucht darum keine sudo-Regel. Legen Sie das Skript in das Local-Verzeichnis des Agenten und lassen Sie die Services des Hosts einmal neu erkennen.
#!/bin/sh
# /usr/lib/check_mk_agent/local/reportedip, mode 0755
out=$(/usr/local/bin/reportedip-agent status --json 2>/dev/null)
rc=$?
[ -n "$out" ] || { echo "3 ReportedIP_Agent - no status document"; exit 0; }
[ "$rc" -gt 2 ] && rc=3
printf '%s' "$out" | jq -r --argjson rc "$rc" '"\($rc) ReportedIP_Agent queue=\(.queue.entries)|bans=\(.bans.kernel)|sync_age=\(.sync.last_attempt_age_s) "
+ (if (.problems | length) > 0 then ([.problems[].text] | join(", ")) else "daemon running, chain ok" end)' \
|| echo "3 ReportedIP_Agent - no status document"
Prometheus
Der Textfile-Collector des Node Exporters liest Metriken aus Dateien. Ein Timer, der alle fünf Minuten
eine Datei schreibt, reicht also. Das Verzeichnis ist das, worauf --collector.textfile.directory
auf Ihren Hosts zeigt.
#!/bin/sh
# /usr/local/sbin/reportedip-prom, run as root every 5 minutes (cron or a systemd timer)
dir=/var/lib/prometheus/node-exporter
out=$(/usr/local/bin/reportedip-agent status --json 2>/dev/null)
[ -n "$out" ] || exit 1 # keep the old file; its age raises the alert
printf '%s' "$out" | jq -r '
"reportedip_status_code \(.code)",
"reportedip_daemon_running \(if .daemon.running then 1 else 0 end)",
"reportedip_heartbeat_age_seconds \(.daemon.heartbeat_age_s)",
"reportedip_sync_attempt_age_seconds \(.sync.last_attempt_age_s)",
"reportedip_chain_ok \(if .chain_ok then 1 else 0 end)",
"reportedip_bans_active \(.bans.kernel)",
"reportedip_queue_entries \(.queue.entries)",
(.lists | to_entries[] | "reportedip_list_entries{list=\"\(.key)\",family=\"ipv4\"} \(.value.ipv4)",
"reportedip_list_entries{list=\"\(.key)\",family=\"ipv6\"} \(.value.ipv6)")
' > "$dir/reportedip.prom.tmp" && mv "$dir/reportedip.prom.tmp" "$dir/reportedip.prom"
Das Umbenennen am Ende ist wichtig: der Collector darf nie eine halb geschriebene Datei lesen. Alarmieren
Sie auf reportedip_status_code > 0, auf reportedip_daemon_running == 0 und über
node_textfile_mtime_seconds auf das Alter der Datei selbst, das fängt einen stehengebliebenen
Timer ab.
Ohne Monitoring-System
Der Agent schreibt Ihnen selbst eine Mail, wenn etwas nicht stimmt, und noch eine, wenn es behoben ist,
einmal je Zustand, sofern notify.email gesetzt ist; siehe
Konfiguration. Diese Mail kommt aus
dem Sync-Lauf. Sie deckt also einen kaputten Feed, eine kaputte Kette, Quelle oder Lizenz ab, kann aber
nicht mitteilen, dass der ganze Host ausgefallen ist. Diesen Teil übernimmt eine einfache Prüfung von außen,
ein Ping oder eine Prüfung des SSH-Ports. Für einen schnellen Blick von Hand reichen
reportedip-agent status und sein Exit-Code.
Zuletzt aktualisiert: · Betreut vom ReportedIP-Team