Monitores Heartbeat
Los monitores Heartbeat invierten la dirección: tu tarea nos dice que está viva. Dos modos: Programado (intervalo esperado) y Receptor de eventos (inactivo; se dispara solo cuando los disparadores coinciden con el payload).
MODOS
- Programado: si no llega ningún latido dentro del intervalo configurado, el estado pasa a MISS y se disparan las alertas.
- Fallo explícito programado: llama a la misma URL con ?status=fail cuando la tarea se ejecutó pero detectó un mal resultado. Esto registra un latido KO y activa el manejo de caída inmediatamente.
- Receptor de eventos: el estado permanece inactivo. Cada latido almacena su payload; los disparadores con condiciones lo evalúan. ?status=fail se ignora en el modo Receptor de eventos porque las condiciones de los disparadores deciden qué importa.
OK beat:
curl -fsS https://keepitalive.dev/heartbeat/{token}
Explicit failed beat:
curl -fsS "https://keepitalive.dev/heartbeat/{token}?status=fail"RECETAS PROGRAMADAS
*/5 * * * * curl -fsS https://keepitalive.dev/heartbeat/{token} >/dev/null0 2 * * * /usr/bin/backup.sh && curl -fsS https://keepitalive.dev/heartbeat/{token}curl -fsS "https://keepitalive.dev/heartbeat/{token}?status=fail&payload=disk+full"*/1 * * * * if ping -c 1 -W 2 192.168.1.50 >/dev/null 2>&1; then \
curl -fsS "https://keepitalive.dev/heartbeat/{token}"; \
else \
curl -fsS -X POST "https://keepitalive.dev/heartbeat/{token}?status=fail" \
-d "unreachable"; \
fi:: C:\scripts\keepalive_heartbeat.bat
@echo off
curl -fsS https://keepitalive.dev/heartbeat/{token} >nul 2>&1
:: schtasks /create /tn "KEEPitALIVE" /tr C:\scripts\keepalive_heartbeat.bat /sc minute /mo 5PROGRAMACIONES CRON
Un heartbeat simple espera una senal cada N segundos, lo que no encaja con un trabajo que corre por calendario: una copia de seguridad solo en dias laborables parece llevar 65 horas de retraso cada domingo. Define una expresion cron y el plazo pasa a ser la proxima ejecucion programada mas la tolerancia: nada llega tarde hasta que algo se esperaba. Elige la zona horaria en la que corre el trabajo: la programacion se evalua ahi, y eso mantiene un trabajo de las 03:00 a las 03:00 cuando cambia la hora. La expresion tambien fija el coste, porque determina cuantas ejecuciones por hora esperamos. La URL de ingesta no cambia; solo cambia cuando esperamos la senal.
# Give the monitor the same schedule the job runs on.
0 3 * * 1-5 # 03:00, weekdays only
*/15 * * * * # every 15 minutes
0 3 1 * * # 03:00 on the 1st of the month
@daily # midnight
# Pick the zone the job runs in, not the server's: that is
# what keeps 03:00 at 03:00 when the clocks change.CÓDIGOS DE SALIDA
Añade el código de salida de tu tarea a la URL y el beat se informa solo: 0 es éxito, cualquier otro valor es un fallo. Es mejor que encadenar con && , que no envía nada cuando la tarea falla, de modo que el monitor solo cae más tarde, al perderse la ventana, en lugar de en el momento de la avería. El código se guarda en el beat, así que una condición de disparador puede enrutar según el tipo de fallo: payload.exit_code == 137 (terminado por memoria) puede abrir una interrupción mientras payload.exit_code == 1 solo notifica. Los códigos deben estar entre 0 y 255; si no tienes un código a mano, también puedes usar /fail.
# $? is the exit status of the job that just ran
0 2 * * * /usr/bin/backup.sh; curl -fsS https://keepitalive.dev/heartbeat/{token}/$?
# Send the job output too, so the alert says why it failed.
# Capture it first: in a pipeline $? is the last command's status,
# not the job's.
0 2 * * * out=$(/usr/bin/backup.sh 2>&1); \
curl -fsS --data-binary "$out" https://keepitalive.dev/heartbeat/{token}/$?DURACIÓN DE LA EJECUCIÓN
Haz ping a /start cuando el trabajo empieza y envía una señal normal al terminar, y el tiempo transcurrido se guarda en la señal como duration_ms, de modo que una condición puede alertar de una ejecución lenta - payload.duration_ms > 300000 detecta una copia que tardó más de cinco minutos aunque acabara bien. /start es totalmente opcional: una sola señal final ya es un informe completo, y la detección de fallos y los códigos de salida funcionan sin él. Enviarlo aporta exactamente dos cosas: la duración registrada y la detección de una ejecución que nunca termina. Un /start por sí solo no informa de nada ni cambia el estado del monitor: un trabajo que arranca y se cuelga debe seguir incumpliendo su ventana. Un segundo /start mientras hay una ejecución abierta se rechaza, porque dos inicios sin un final entre ellos significan que el primero nunca informó; el rechazo se levanta cuando esa ejecución excede su plazo. Las ejecuciones abiertas más de 24 horas se descartan.
# /start is optional. Without it you still get miss detection
# and the exit status - just no duration and no stuck-run alert:
0 2 * * * /usr/bin/backup.sh; curl -fsS https://keepitalive.dev/heartbeat/{token}/$?
# With it, the run is timed and a job that never finishes is caught:
0 2 * * * curl -fsS https://keepitalive.dev/heartbeat/{token}/start; \
/usr/bin/backup.sh; \
curl -fsS https://keepitalive.dev/heartbeat/{token}/$?EJECUCIONES ATASCADAS
Si una tarea llama a /start y luego se cuelga, todavía no hay nada retrasado: el inicio no refrescó nada, así que el monitor parece correcto hasta que tocara el siguiente beat. Ese hueco es justo el problema de cronometrar una tarea: una ejecución atascada es precisamente el fallo que querías detectar. Por eso una ejecución que no ha enviado su beat final cae en cuanto supera el intervalo más la ventana de gracia, con un error que indica cuánto lleva ejecutándose. No hay un ajuste aparte de duración máxima: un beat retrasado y una ejecución atascada son la misma pregunta — cuánto esperamos antes de darla por perdida — así que la gracia que ya configuraste responde a ambas.
PAYLOADS
Haz POST de JSON a tu URL de heartbeat para informar métricas del sistema o campos arbitrarios. La tarjeta Último Informe en la página de detalle del monitor muestra el último payload como JSON con formato cuando es posible, con texto sin formato como alternativa.
curl -fsS -X POST https://keepitalive.dev/heartbeat/{token} \
-H 'Content-Type: application/json' \
-d '{"job":"nightly-backup","status":"ok","rows":42850}'RECEPTOR DE EVENTOS: MANTENIMIENTO CI/CD
El modo Receptor de eventos es útil para pipelines de despliegue. Crea un disparador de payload con la condición payload.status == "maintenance" y clasificación de incidente maintenance. Envía un payload de mantenimiento antes del despliegue y un payload running/ok en un paso final if: always(). El incidente se abre mientras la expresión coincide y se cierra cuando el siguiente payload aceptado lo limpia.
- name: Start maintenance
run: |
curl -fsS -X POST "$KEEPITALIVE_HEARTBEAT_URL" \
-H "Content-Type: application/json" \
-d '{"status":"maintenance","source":"github-actions","sha":"${{github.sha}}"}'
- name: Deploy
run: ./deploy.sh
- name: End maintenance
if: always()
run: |
curl -fsS -X POST "$KEEPITALIVE_HEARTBEAT_URL" \
-H "Content-Type: application/json" \
-d '{"status":"running","source":"github-actions","sha":"${{github.sha}}"}'Los receptores de eventos limitan los payloads aceptados según el intervalo del monitor. Para trabajos de despliegue muy cortos, usa el menor intervalo que permita tu plan o retrasa el payload final hasta que haya transcurrido el intervalo. Si la fuente se queda en silencio, KEEPitALIVE no asume la recuperación.
#!/bin/bash
URL="https://keepitalive.dev/heartbeat/{token}"
CPU=$(top -bn1 | grep "Cpu" | awk '{print $2}')
RAM=$(free -m | awk '/Mem/{printf "%.1f", $3/$2*100}')
DISK=$(df -h / | awk 'NR==2{print $5}')
curl -fsS -X POST $URL \
-H "Content-Type: application/json" \
-d "{\"hostname\":\"$(hostname)\",\"cpu\":\"$CPU%\",\"ram\":\"$RAM%\",\"disk\":\"$DISK\"}"$url = "https://keepitalive.dev/heartbeat/{token}"
$os = Get-CimInstance Win32_OperatingSystem
$body = @{
hostname = $env:COMPUTERNAME
cpu = "{0:N1}%" -f (Get-CimInstance Win32_Processor).LoadPercentage
ram = "{0:N1}%" -f ((1 - $os.FreePhysicalMemory / $os.TotalVisibleMemorySize) * 100)
} | ConvertTo-Json
Invoke-RestMethod -Uri $url -Method POST -Body $body -ContentType "application/json"