Monitorizar el agente Linux
Cómo vigilar el agente desde Zabbix, Nagios, Icinga, Checkmk o Prometheus: qué debe cubrir una
comprobación, el JSON que imprime status --json, los permisos que necesita, una plantilla de
Zabbix lista para usar y una configuración breve para los demás sistemas. Disponible desde la versión
0.3.40.
Qué debe cubrir una comprobación
El agente protege un host de dos maneras, y fallan de forma distinta. Las listas del núcleo siguen bloqueando aunque el agente ya no esté. Un agente detenido no parece roto desde fuera: el cortafuegos sigue descartando las direcciones conocidas, y nada nuevo se detecta, se bloquea ni se notifica. Por eso una comprobación útil mira más allá del proceso.
| Pregunta | Dónde está la respuesta | Sano |
|---|---|---|
| ¿Funciona el demonio watch? | daemon.running, o systemctl is-active reportedip-agent | true, active |
| ¿Funciona el temporizador de sincronización? | sync.last_attempt_age_s | por debajo de 5400 (90 minutos; según el plan, un host descarga las listas cada 15 o cada 60 minutos) |
| ¿Está la cadena en el camino de los paquetes? | chain_ok | true, en un host que bloquea |
| ¿Algo más va mal? | código de salida, status, problems | 0, ok, vacío |
El demonio escribe un latido cada 30 segundos. status lo da por muerto cuando ese latido
tiene más de dos minutos. Un demonio detenido aparece, por tanto, en la primera comprobación que se
ejecute al menos dos minutos después de la parada. Nada más cambia en status cuando el
demonio se detiene, y por eso existe esta línea.
status --json
reportedip-agent status --json hace las mismas comprobaciones que status e
imprime el resultado como un único documento JSON. El código de salida sigue las mismas reglas, con una diferencia: el modo texto consulta la API en directo y queda degradado si falla, --json no consulta nada y por eso nunca lo está por ese motivo. No
envía ninguna petición a la API: la parte de la cuenta es la respuesta que guardó la última
sincronización, y account.cached_age_s indica su antigüedad. Una comprobación cada pocos
minutos no cuesta nada y no puede quedarse colgada en la red.
{
"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": {}
}
| Campo | Significado |
|---|---|
schema | La versión de este formato. Con el mismo número solo se añaden campos; un campo nunca se renombra, cambia de tipo ni se elimina sin subir el número. |
status, code | ok y 0, degraded y 1, error y 2. Igual que el código de salida. |
problems | Una entrada por cada motivo de degraded, cada una con una key corta (daemon, chain, lists, sources, queue, bans, disk, update, license, reputation, backend, state) y una frase. Vacío cuando el host está sano. |
*_age_s | Antigüedades en segundos enteros. -1 significa nunca o desconocido, por ejemplo antes de la primera sincronización. |
backend | ipset, nftables, o none en un host que solo notifica. Un host así no tiene cadena ni sincronización, así que chain_ok es false ahí a propósito. |
lists, sources | Un objeto por lista y por fuente de registros, con el nombre como clave. |
health | Las condiciones de salud abiertas, las mismas de las que trata el correo de estado. |
error | Solo con code 2: la configuración o el directorio de estado no se puede usar. El documento se imprime de todos modos, para que la comprobación reciba un valor y no nada. |
El documento contiene solo contadores, antigüedades y estados: ninguna dirección, ninguna clave y ninguna línea de registro. Un servidor de monitorización puede guardarlo sin más.
Permisos
status necesita root. La configuración contiene tu clave de API y solo root puede leerla, el
directorio de estado tiene 0700, y los conjuntos del cortafuegos solo se leen con permisos de
root. Un agente de monitorización se ejecuta con su propio usuario, así que necesita una regla de sudo
para este único comando y nada más:
# /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
Para Zabbix 7.0 y posteriores, con Zabbix agent o Zabbix agent 2. Un UserParameter recoge el documento cada cinco minutos, y todos los demás ítems toman su valor de esa única llamada. El agente se ejecuta una vez por intervalo y no una vez por ítem.
Instalar la comprobación
# 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
El || true evita que un host degradado se convierta en un ítem no soportado: el código de
salida está en el documento como code, y el ítem tiene que recibir el documento en todos los
casos.
Importar la plantilla
Dos variantes de la misma plantilla, según cómo hablen tus agentes con el servidor. Importa una de ellas en Data collection, Templates, Import y vincúlala a cada host en el que funcione el agente.
- reportedip_agent_zabbix7.yaml: ítems de tipo Zabbix agent (pasivo)
- reportedip_agent_zabbix7_active.yaml: ítems de tipo Zabbix agent (activo)
La plantilla fija un tiempo de espera de 20 segundos en el ítem de estado. Un agente anterior a 7.0 lo
ignora y usa su propio Timeout, de 3 segundos por defecto; súbelo a 20 en la configuración
del agente en esos hosts.
Qué vigila la plantilla
| Disparador | Gravedad | Salta cuando |
|---|---|---|
| watch daemon is not running | High | daemon.running es false, o no hay ningún proceso reportedip-agent watch. El recuento de procesos no necesita UserParameter, así que funciona aunque falle la comprobación de estado. |
| firewall chain is not in place | High | chain_ok es false en un host con backend. |
| configuration error, the agent cannot run | High | code es 2. |
no sync for {$RIP.SYNC.MAXAGE} | Average | La última petición del feed es más antigua que la macro (90 minutos), o no hubo ninguna, en un host con backend. Depende del disparador de licencia, porque sin licencia no sale ninguna petición. |
no data for {$RIP.NODATA} | Average | El ítem de estado no recibió nada durante 15 minutos. |
report queue over {$RIP.QUEUE.PCT}% | Warning | La cola está más llena que la macro (80). |
| host has no server licence, feed paused | Warning | account.license es unlicensed. |
| reporting address is listed | Warning | La dirección desde la que notifica este host figura en la base de datos de la comunidad. |
| agent degraded | Warning | Cualquier otro motivo para code 1. Depende de los disparadores de arriba, así que un problema genera una alerta y no dos. El ítem Problems indica el motivo. |
| newer agent version available | Info | Existe una versión más nueva. La actualización automática la instala en un plazo de seis horas, así que solo sigue abierto si la vía de actualización está rota o auto_update está desactivado. |
Dos reglas de descubrimiento añaden ítems por lista (entradas IPv4 e IPv6, fallos seguidos) y por fuente de registros (estado, aciertos, antigüedad de la última línea). Cada umbral es una macro de la plantilla y se puede cambiar por host.
Cuando los ítems quedan vacíos
Value of type "string" is not suitable for value type "Numeric" o un ítem que nunca recibe valor
significa casi siempre que el comando no pudo ejecutarse como usuario de Zabbix: falta la regla de sudo,
tiene el modo incorrecto (debe ser 0440, si no sudo ignora el archivo) o nombra otra ruta.
Ejecuta la línea de prueba de la sección Permisos como usuario de Zabbix. Un agente activo solo recoge su
lista de ítems cada RefreshActiveChecks segundos, así que el primer valor puede tardar unos
minutos después de vincular la plantilla.
Nagios e Icinga
Los códigos de salida de status ya son los de un plugin: 0 OK, 1 WARNING, 2 CRITICAL. Pero un
plugin debe imprimir una sola línea, así que un pequeño envoltorio convierte el documento en una línea
con datos de rendimiento. Necesita 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" ]
}
Se aplica la regla de sudo de la sección Permisos, con el usuario con el que se ejecuta NRPE o el agente de Icinga.
Checkmk
Un local check se ejecuta como root dentro del agente de Checkmk, así que no necesita regla de sudo. Coloca el script en el directorio local del agente y vuelve a descubrir una vez los servicios del host.
#!/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
El recolector textfile del node exporter lee métricas de archivos, así que basta con un temporizador que
escriba un archivo cada cinco minutos. El directorio es aquel al que apunta
--collector.textfile.directory en tus hosts.
#!/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"
El cambio de nombre final importa: el recolector nunca debe leer un archivo escrito a medias. Alerta con
reportedip_status_code > 0, con reportedip_daemon_running == 0 y, mediante
node_textfile_mtime_seconds, con la antigüedad del propio archivo, que detecta un
temporizador detenido.
Sin sistema de monitorización
El agente te envía él mismo un correo cuando algo va mal y otra vez cuando se ha resuelto, una vez por condición, si
notify.email está configurado; consulta
Configuración. Ese correo sale de la
sincronización: cubre un feed, una cadena, una fuente o una licencia rotos, pero no puede avisar de que el
host entero se ha caído. Esa parte la cubre una simple comprobación desde fuera, un ping o una prueba del
puerto SSH. Para un vistazo a mano bastan reportedip-agent status y su código de salida.
Última actualización: · Mantenido por el equipo de ReportedIP