Superviser l'agent Linux
Comment surveiller l'agent depuis Zabbix, Nagios, Icinga, Checkmk ou Prometheus : ce qu'une sonde doit
couvrir, le JSON que produit status --json, les droits nécessaires, un template Zabbix prêt
à l'emploi et une mise en place courte pour les autres systèmes. Disponible à partir de la version 0.3.40.
Ce qu'une sonde doit couvrir
L'agent protège un hôte de deux façons, et elles ne tombent pas en panne de la même manière. Les listes dans le noyau continuent de bloquer même quand l'agent n'est plus là. Un agent arrêté n'a donc pas l'air cassé vu de l'extérieur : le pare-feu rejette toujours les adresses connues, et plus rien de nouveau n'est détecté, banni ou signalé. Une sonde utile regarde donc plus loin que le processus.
| Question | Où se trouve la réponse | Sain |
|---|---|---|
| Le démon watch tourne-t-il ? | daemon.running, ou systemctl is-active reportedip-agent | true, active |
| Le timer de synchronisation tourne-t-il ? | sync.last_attempt_age_s | moins de 5400 (90 minutes ; selon l'offre, un hôte récupère les listes toutes les 15 ou toutes les 60 minutes) |
| La chaîne est-elle dans le chemin des paquets ? | chain_ok | true, sur un hôte qui bloque |
| Autre chose ne va pas ? | code de sortie, status, problems | 0, ok, vide |
Le démon écrit un battement de cœur toutes les 30 secondes. status le considère comme mort
quand ce battement date de plus de deux minutes. Un démon arrêté apparaît donc à la première sonde qui
tourne au moins deux minutes après l'arrêt. Rien d'autre ne change dans status quand le
démon s'arrête, et c'est pour cela que cette ligne existe.
status --json
reportedip-agent status --json effectue les mêmes contrôles que status et
affiche le résultat sous la forme d'un seul document JSON. Le code de sortie suit les mêmes règles, à une différence près : le mode texte interroge l'API en direct et passe en dégradé si cela échoue, --json n'interroge rien et ne l'est donc jamais pour cette raison.
Aucune requête ne part vers l'API : la partie compte est la réponse que la dernière
synchronisation a mise en cache, et account.cached_age_s indique son âge. Une sonde toutes
les quelques minutes ne coûte donc rien et ne peut pas rester bloquée sur le réseau.
{
"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": {}
}
| Champ | Signification |
|---|---|
schema | La version de ce format. Sous un même numéro, des champs ne font que s'ajouter ; un champ n'est jamais renommé, retypé ou supprimé sans que ce numéro soit augmenté. |
status, code | ok et 0, degraded et 1, error et 2. Identique au code de sortie. |
problems | Une entrée par raison de degraded, chacune avec une courte key (daemon, chain, lists, sources, queue, bans, disk, update, license, reputation, backend, state) et une phrase. Vide quand l'hôte est sain. |
*_age_s | Âges en secondes entières. -1 signifie jamais ou inconnu, par exemple avant la première synchronisation. |
backend | ipset, nftables, ou none sur un hôte qui ne fait que signaler. Un tel hôte n'a ni chaîne ni synchronisation, chain_ok y vaut donc false par conception. |
lists, sources | Un objet par liste et par source de journaux, avec le nom comme clé. |
health | Les conditions de santé ouvertes, les mêmes que celles dont parle le courrier d'état. |
error | Seulement avec code 2 : la configuration ou le répertoire d'état est inutilisable. Le document est tout de même affiché, pour que la sonde reçoive une valeur plutôt que rien. |
Le document ne contient que des compteurs, des âges et des états : aucune adresse, aucune clé et aucune ligne de journal. Un serveur de supervision peut donc le stocker sans précaution particulière.
Droits
status a besoin de root. La configuration contient votre clé API et n'est lisible que par
root, le répertoire d'état est en 0700, et les ensembles du pare-feu ne se lisent qu'avec les
droits root. Un agent de supervision tourne sous son propre utilisateur ; il lui faut donc une règle sudo
pour cette seule commande et rien d'autre :
# /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
Pour Zabbix 7.0 et plus récent, avec Zabbix agent ou Zabbix agent 2. Un UserParameter récupère le document toutes les cinq minutes, et chaque autre élément tire sa valeur de cet appel unique. L'agent tourne donc une fois par intervalle et pas une fois par élément.
Installer la sonde
# 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
Le || true évite qu'un hôte dégradé devienne un élément non supporté : le code de sortie
figure dans le document sous code, et l'élément doit recevoir le document dans tous les cas.
Importer le template
Deux variantes du même template, selon la façon dont vos agents parlent au serveur. Importez l'une d'elles sous Data collection, Templates, Import et liez-la à chaque hôte sur lequel l'agent tourne.
- reportedip_agent_zabbix7.yaml : éléments de type Zabbix agent (passif)
- reportedip_agent_zabbix7_active.yaml : éléments de type Zabbix agent (actif)
Le template fixe un délai d'expiration de 20 secondes sur l'élément de statut. Un agent antérieur à 7.0 l'ignore et
utilise son propre Timeout, 3 secondes par défaut ; portez-le à 20 dans la configuration de
l'agent sur ces hôtes.
Ce que surveille le template
| Déclencheur | Gravité | Se déclenche quand |
|---|---|---|
| watch daemon is not running | High | daemon.running vaut false, ou aucun processus reportedip-agent watch ne tourne. Le comptage des processus n'a pas besoin de UserParameter, il fonctionne donc même quand la sonde de statut échoue. |
| firewall chain is not in place | High | chain_ok vaut false sur un hôte avec backend. |
| configuration error, the agent cannot run | High | code vaut 2. |
no sync for {$RIP.SYNC.MAXAGE} | Average | La dernière requête de flux est plus ancienne que la macro (90 minutes), ou il n'y en a eu aucune, sur un hôte avec backend. Il dépend du déclencheur de licence, car sans licence aucune requête ne part. |
no data for {$RIP.NODATA} | Average | L'élément de statut n'a rien reçu pendant 15 minutes. |
report queue over {$RIP.QUEUE.PCT}% | Warning | La file est plus remplie que la macro (80). |
| host has no server licence, feed paused | Warning | account.license vaut unlicensed. |
| reporting address is listed | Warning | L'adresse depuis laquelle cet hôte signale figure dans la base communautaire. |
| agent degraded | Warning | Toute autre raison pour code 1. Il dépend des déclencheurs ci-dessus, un problème donne donc une alerte et non deux. L'élément Problems en donne la raison. |
| newer agent version available | Info | Une version plus récente existe. La mise à jour automatique l'installe dans les six heures, il ne reste donc ouvert que si le chemin de mise à jour est cassé ou si auto_update est désactivé. |
Deux règles de découverte ajoutent des éléments par liste (entrées IPv4 et IPv6, échecs consécutifs) et par source de journaux (état, occurrences, âge de la dernière ligne). Chaque seuil est une macro du template et peut être modifié par hôte.
Quand les éléments restent vides
Value of type "string" is not suitable for value type "Numeric" ou un élément qui ne reçoit jamais
de valeur signifie presque toujours que la commande n'a pas pu tourner sous l'utilisateur Zabbix : la
règle sudo manque, a le mauvais mode (elle doit être en 0440, sinon sudo ignore le fichier)
ou nomme un autre chemin. Lancez la ligne de test de la section Droits en tant qu'utilisateur Zabbix. Un
agent actif ne récupère sa liste d'éléments que toutes les RefreshActiveChecks secondes, la
première valeur peut donc prendre quelques minutes après la liaison du template.
Nagios et Icinga
Les codes de sortie de status sont déjà ceux d'un plugin : 0 OK, 1 WARNING, 2 CRITICAL. Un
plugin doit toutefois afficher une seule ligne ; un court wrapper transforme donc le document en une
ligne avec des données de performance. Il a besoin de 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" ]
}
La règle sudo de la section Droits s'applique, avec l'utilisateur sous lequel tourne NRPE ou l'agent Icinga.
Checkmk
Un local check tourne en root dans l'agent Checkmk et n'a donc pas besoin de règle sudo. Placez le script dans le répertoire local de l'agent et relancez une fois la découverte des services de l'hôte.
#!/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
Le collecteur textfile du node exporter lit des métriques dans des fichiers ; un timer qui écrit un
fichier toutes les cinq minutes suffit donc. Le répertoire est celui vers lequel pointe
--collector.textfile.directory sur vos hôtes.
#!/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"
Le renommage final compte : le collecteur ne doit jamais lire un fichier à moitié écrit. Alertez sur
reportedip_status_code > 0, sur reportedip_daemon_running == 0, et sur l'âge
du fichier lui-même via node_textfile_mtime_seconds, ce qui détecte un timer arrêté.
Sans système de supervision
L'agent vous envoie lui-même un courrier quand quelque chose ne va pas, et à nouveau quand c'est réglé, une fois par
état, si notify.email est renseigné ; voir
Configuration. Ce courrier vient de la
synchronisation : il couvre un flux, une chaîne, une source ou une licence en panne, mais il ne peut pas
signaler que l'hôte entier est tombé. Une simple vérification depuis l'extérieur, un ping ou un contrôle
du port SSH, couvre cette partie. Pour un coup d'œil à la main, reportedip-agent status et
son code de sortie suffisent.
Dernière mise à jour: · Maintenu par l’équipe ReportedIP