Skip to main contentSkip to footer

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.

QuestionOù se trouve la réponseSain
Le démon watch tourne-t-il ?daemon.running, ou systemctl is-active reportedip-agenttrue, active
Le timer de synchronisation tourne-t-il ?sync.last_attempt_age_smoins 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_oktrue, sur un hôte qui bloque
Autre chose ne va pas ?code de sortie, status, problems0, 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.

json
{
  "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":  {}
}
ChampSignification
schemaLa 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, codeok et 0, degraded et 1, error et 2. Identique au code de sortie.
problemsUne 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.
backendipset, 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, sourcesUn objet par liste et par source de journaux, avec le nom comme clé.
healthLes conditions de santé ouvertes, les mêmes que celles dont parle le courrier d'état.
errorSeulement 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 :

bash
# /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
Testez en tant qu'utilisateur de supervision, pas en root. Un test en root fonctionne toujours et ne prouve rien sur la règle. Seule la dernière ligne ci-dessus, lancée en root, montre ce que l'agent de supervision recevra.

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

bash
# 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.

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éclencheurGravitéSe déclenche quand
watch daemon is not runningHighdaemon.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 placeHighchain_ok vaut false sur un hôte avec backend.
configuration error, the agent cannot runHighcode vaut 2.
no sync for {$RIP.SYNC.MAXAGE}AverageLa 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}AverageL'élément de statut n'a rien reçu pendant 15 minutes.
report queue over {$RIP.QUEUE.PCT}%WarningLa file est plus remplie que la macro (80).
host has no server licence, feed pausedWarningaccount.license vaut unlicensed.
reporting address is listedWarningL'adresse depuis laquelle cet hôte signale figure dans la base communautaire.
agent degradedWarningToute 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 availableInfoUne 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.

bash
#!/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
bash
# 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.

bash
#!/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.

bash
#!/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

Security Focused
Conforme au RGPD
Made in Germany
Retour aux docs