Volver al blog

Monitorizar tareas cron: el patrón heartbeat en la práctica

· 6 min de lectura

La monitorización HTTP no te dirá que un cron nocturno ha fallado. El patrón heartbeat invierte el sentido: el cron se reporta a la monitorización, y si no llama a tiempo, recibes una alerta.

Monitorizar tareas cron: el patrón heartbeat en la práctica

El problema: tareas en segundo plano sin endpoint HTTP

Un backend típico tiene varias rutas:

  • Peticiones web (HTTP/HTTPS al servidor) - las monitorizas con un uptime check.
  • Tareas cron (backup diario, facturación mensual, sincronización horaria) - no tienen endpoint HTTP, la monitorización externa no puede seguirlas.
  • Workers (Celery, BullMQ, Sidekiq) que procesan una cola - también sin HTTP.

Cuando un cron falla (una errata en el crontab, un disco lleno, una variable de entorno (environment variable) ausente o un error de importación tras actualizar una dependencia (dependency upgrade)), nadie te avisa. Te enteras el lunes por la mañana, cuando notas que no salió ninguna factura durante el fin de semana.

El patrón heartbeat: el cron hace ping a la monitorización

El principio es inverso al de la monitorización normal:

  1. En el servicio de monitorización creas un monitor heartbeat con un intervalo esperado (por ejemplo «cada 60 minutos»).
  2. Obtienes una URL heartbeat única: https://epulz.io/heartbeat/abc123xyz.
  3. En tu tarea cron, al final de una ejecución exitosa, llamas a esa URL (HTTP GET o POST).
  4. Si el ping no llega dentro del tiempo esperado (más un periodo de gracia), la monitorización te alerta.

Ejemplo práctico: heartbeat desde una tarea cron

# /etc/crontab
0 3 * * * www-data /usr/local/bin/backup.sh && curl -fsS -m 10 \
  https://epulz.io/heartbeat/abc123xyz > /dev/null

La clave es el operador &&: el heartbeat se envía solo cuando backup.sh termina con código de salida 0. Si el script falla, el ping no llega y en una hora recibes una alerta.

Consejo: Para una cobertura más completa, añade también un heartbeat «start»:

curl -fsS -m 10 https://epulz.io/heartbeat/backup-start-xyz > /dev/null
/usr/local/bin/backup.sh && \
  curl -fsS -m 10 https://epulz.io/heartbeat/backup-done-xyz > /dev/null

La monitorización podrá entonces distinguir «empezó pero no terminó» (el script se colgó) de «nunca empezó» (la tarea cron no se ejecutó).

Python: requests + try/except

import os, requests
HEARTBEAT_URL = os.environ["HEARTBEAT_URL"]

def sync_data():
    # ... your logic ...
    pass

try:
    sync_data()
    requests.get(HEARTBEAT_URL, timeout=10)
except Exception as e:
    # El heartbeat no se envía - la monitorización te alerta
    raise

Node.js: async / await

const HEARTBEAT_URL = process.env.HEARTBEAT_URL;

async function nightlyJob() {
  await processInvoices();
  await fetch(HEARTBEAT_URL, { signal: AbortSignal.timeout(10000) });
}

nightlyJob().catch(err => {
  console.error(err);
  process.exit(1);
});

Periodo de gracia: cuánto tiempo dar antes de alertar

Un monitor heartbeat necesita cierta tolerancia. El cron a veces tarda más de lo habitual, la red tiene latencia y la sincronización NTP puede estar ligeramente desfasada. El periodo de gracia es el tiempo, tras agotarse el intervalo esperado, durante el cual la monitorización aún espera antes de disparar una alerta.

Valores prácticos:

  • Cron horario: intervalo 60 min + gracia 10 min
  • Backup diario (20 min de media): intervalo 1440 min + gracia 60 min
  • Reporte semanal: intervalo 10080 min + gracia 360 min (6 h)

Una gracia demasiado ajustada = falsas alertas (falsos positivos). Demasiado holgada = una alerta tardía justo cuando la tarea falla de verdad.

Dónde ayuda más el patrón heartbeat

  • Backups de BD nocturnos
  • Sincronización con API externas (CRM, contabilidad, pagos)
  • Cálculos de reportes
  • Tareas de cleanup (borrado de sesiones antiguas, logs, archivos temporales)
  • El ciclo de healthcheck de workers de larga duración
  • E-mails, newsletters y facturación programados

Conclusión

Las tareas en segundo plano (background) suelen ser más críticas que el propio sitio, pero siguen siendo un punto ciego de la monitorización. El patrón heartbeat solo requiere cinco minutos de implementación (basta con añadir curl al final de la línea cron) y ofrece la misma garantía que la monitorización uptime del frontend.

Recordatorio rápido: sintaxis de cron

La mayoría de los errores de heartbeat son en realidad errores de cron - el ping nunca se dispara porque la planificación estaba mal. Los cinco campos son:

┌── minuto (0-59)
│ ┌── hora (0-23)
│ │ ┌── día del mes (1-31)
│ │ │ ┌── mes (1-12)
│ │ │ │ ┌── día de la semana (0-7, 0 y 7 = domingo)
│ │ │ │ │
* * * * *  comando
Expresión Se ejecuta
*/5 * * * * cada 5 minutos
0 * * * * al inicio de cada hora
0 3 * * * cada día a las 03:00
0 3 * * 1 cada lunes a las 03:00
30 2 1 * * a las 02:30 del primer día del mes

La trampa clásica: */5 en el campo hora no significa «cada 5 horas desde ahora», sino horas divisibles por cinco (0, 5, 10, 15, 20). Si el intervalo esperado del monitor y la planificación real del cron no coinciden, recibes alertas fantasma.

El principio del «dead man's switch»

El patrón heartbeat es un dead man's switch (interruptor de hombre muerto): la alarma es el silencio. Esto invierte el modo de fallo habitual. La monitorización normal puede fallar en silencio - si el propio servidor de monitorización cae, simplemente deja de enviar alertas y nunca te enteras. El heartbeat es lo contrario: el disparador de una alerta es la ausencia de una señal esperada, así que una tarea que deja de ejecutarse por completo (todo el servidor apagado, el demonio cron deshabilitado, la máquina dada de baja y olvidada) es exactamente el caso que mejor detecta.

Más allá de cron: otros planificadores

El patrón es el mismo sin importar qué dispara la tarea: - timers de systemd - añade curl a la cadena ExecStart o a ExecStartPost=. Empareja el timer OnCalendar= con un intervalo de heartbeat correspondiente. - Kubernetes CronJob - el último paso del contenedor hace ping a la URL heartbeat. Si el pod falla o nunca se planifica (mal node selector, fallo de image pull), el ping falta y recibes una alerta. - Windows Task Scheduler - termina la tarea con el comando PowerShell Invoke-WebRequest -Uri $env:HEARTBEAT_URL -TimeoutSec 10. - CI / pipelines programados - un GitHub Actions nocturno o un schedule de GitLab puede, como último paso, hacer ping al heartbeat para confirmar que el pipeline se ejecutó y pasó.

Anti-patrones comunes

  • Hacer ping al principio, no al final. Un heartbeat enviado antes del trabajo solo prueba que la tarea empezó. Ponlo tras el trabajo, condicionado al éxito (&&), para que un fallo a mitad de ejecución también retenga el ping.
  • Ignorar el código de salida. backup.sh; curl ... (punto y coma) envía el ping aunque el backup falle. Usa siempre &&.
  • Tragarse en silencio los fallos de curl. -fsS hace que curl falle ruidosamente ante un error HTTP, de modo que los logs de tu tarea muestren que el heartbeat no pudo entregarse (un problema de red en la máquina).
  • Un heartbeat para diez tareas. Si cinco crons comparten una URL, no sabes cuál dejó de ejecutarse. Da a cada tarea programada su propio heartbeat. Si además expones un endpoint HTTP para el mismo servicio, empareja el heartbeat con un uptime check normal y una comprobación de puerto en el puerto del worker, para cubrir tanto «el proceso está corriendo» como «la ejecución programada ocurrió».

Escenario concreto: el fallo silencioso del backup

El caso de manual para un heartbeat es así. Un pg_dump nocturno escribe en un volumen montado. Funciona durante meses. Luego el volumen se llena, pg_dump termina con código distinto de cero, y como la línea cron usaba un punto y coma simple, el heartbeat se envía igualmente y el dashboard sigue verde. Tres semanas después la base de datos está corrupta y no hay backup utilizable, porque cada backup «exitoso» desde que el disco se llenó fue un archivo de cero bytes. El patrón heartbeat detecta esto la primera noche, pero solo si está bien conectado: condiciona el ping con && para que una salida distinta de cero lo retenga, y considera comprobar la salida, no solo el código de salida. Un script de backup puede terminar con código 0 y aun así producir un archivo truncado. Una línea más defensiva verifica que el dump no es trivial antes de señalar el éxito:

/usr/local/bin/backup.sh && \
  test "$(stat -c%s /backups/db.sql.gz)" -gt 1000000 && \
  curl -fsS -m 10 https://epulz.io/heartbeat/abc123xyz > /dev/null

Ahora el heartbeat solo llega si el script tuvo éxito y el archivo resultante supera 1 MB. Cualquier cosa menor y el ping se retiene, y recibes una alerta dentro del periodo de gracia en lugar de tres semanas después.

Empieza a monitorizar tus tareas cron

ePulz.io admite heartbeat checks con un periodo de gracia configurable. 7 días gratis.

Iniciar monitorización →

Relacionados

Compartir: Enlace copiado

Prueba ePulz.io gratis - 7 días sin tarjeta de crédito.

Crear cuenta