Skip to main contentSkip to footer

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.

PreguntaDónde está la respuestaSano
¿Funciona el demonio watch?daemon.running, o systemctl is-active reportedip-agenttrue, active
¿Funciona el temporizador de sincronización?sync.last_attempt_age_spor 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_oktrue, en un host que bloquea
¿Algo más va mal?código de salida, status, problems0, 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.

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":  {}
}
CampoSignificado
schemaLa 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, codeok y 0, degraded y 1, error y 2. Igual que el código de salida.
problemsUna 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_sAntigüedades en segundos enteros. -1 significa nunca o desconocido, por ejemplo antes de la primera sincronización.
backendipset, 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, sourcesUn objeto por lista y por fuente de registros, con el nombre como clave.
healthLas condiciones de salud abiertas, las mismas de las que trata el correo de estado.
errorSolo 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:

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
Prueba como usuario de monitorización, no como root. Una prueba como root siempre funciona y no demuestra nada sobre la regla. Solo la última línea de arriba, ejecutada como root, muestra lo que recibirá el agente de monitorización.

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

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

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.

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

DisparadorGravedadSalta cuando
watch daemon is not runningHighdaemon.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 placeHighchain_ok es false en un host con backend.
configuration error, the agent cannot runHighcode es 2.
no sync for {$RIP.SYNC.MAXAGE}AverageLa ú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}AverageEl ítem de estado no recibió nada durante 15 minutos.
report queue over {$RIP.QUEUE.PCT}%WarningLa cola está más llena que la macro (80).
host has no server licence, feed pausedWarningaccount.license es unlicensed.
reporting address is listedWarningLa dirección desde la que notifica este host figura en la base de datos de la comunidad.
agent degradedWarningCualquier 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 availableInfoExiste 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.

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" ]
}

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.

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

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.

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"

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

Security Focused
Conforme al RGPD
Made in Germany
Volver a la documentación