Terug naar blog

Cron-jobs monitoren: het heartbeat-pattern in de praktijk

· 6 min leestijd

HTTP-monitoring merkt niet dat een nachtelijke cron is gecrasht. Het heartbeat-pattern draait de richting om: de cron meldt zich bij de monitoring, en als hij niet op tijd belt, krijg je een melding.

Cron-jobs monitoren: het heartbeat-pattern in de praktijk

Het probleem: achtergrondtaken zonder HTTP-endpoint

Een typische backend heeft meerdere paden:

  • Webverzoeken (HTTP/HTTPS naar de server) - die monitor je met een uptime check.
  • Cron-jobs (dagelijkse backup, maandelijkse facturatie, uurlijkse synchronisatie) - ze hebben geen HTTP-endpoint, externe monitoring kan ze niet volgen.
  • Workers (Celery, BullMQ, Sidekiq) die een queue verwerken - eveneens zonder HTTP.

Wanneer een cron crasht (een typfout in de crontab, een volle schijf, een ontbrekende omgevingsvariabele (environment variable) of een import-fout na een dependency-upgrade), waarschuwt niemand je. Je komt er maandagochtend achter, wanneer je merkt dat er in het weekend geen facturen zijn verstuurd.

Het heartbeat-pattern: de cron pingt de monitoring

Het principe is omgekeerd ten opzichte van normale monitoring:

  1. In de monitoringdienst maak je een heartbeat-monitor met een verwacht interval (bijvoorbeeld "elke 60 minuten").
  2. Je krijgt een unieke heartbeat-URL: https://epulz.io/heartbeat/abc123xyz.
  3. In je cron-job roep je aan het einde van een succesvolle run die URL aan (HTTP GET of POST).
  4. Als de ping niet binnen de verwachte tijd (plus een grace-periode) aankomt, waarschuwt de monitoring je.

Praktisch voorbeeld: heartbeat vanuit een cron-job

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

De &&-operator is cruciaal: de heartbeat wordt alleen verstuurd als backup.sh eindigt met exitcode 0. Faalt het script, dan komt de ping niet aan en krijg je binnen een uur een melding.

Tip: Voor grondigere dekking voeg je ook een "start"-heartbeat toe:

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

De monitoring kan dan "gestart maar niet afgerond" (het script liep vast) onderscheiden van "nooit gestart" (de cron-job draaide niet).

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:
    # De heartbeat wordt niet verstuurd - de monitoring waarschuwt je
    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);
});

Grace-periode: hoeveel tijd geven vóór de melding

Een heartbeat-monitor heeft enige tolerantie nodig. Een cron draait soms langer dan gebruikelijk, het netwerk heeft latency en de NTP-synchronisatie kan iets verschoven zijn. De grace-periode is de tijd na het verstrijken van het verwachte interval waarin de monitoring nog wacht voordat hij een melding afvuurt.

Praktische waarden:

  • Uurlijkse cron: interval 60 min + grace 10 min
  • Dagelijkse backup (gemiddeld 20 min): interval 1440 min + grace 60 min
  • Wekelijks rapport: interval 10080 min + grace 360 min (6 u)

Een te krappe grace = valse meldingen (false positives). Te ruim = een vertraagde melding precies op het moment dat de taak echt crasht.

Waar het heartbeat-pattern het meest helpt

  • Nachtelijke DB-backups
  • Synchronisatie met externe API's (CRM, boekhouding, betalingen)
  • Rapportberekeningen
  • Cleanup-taken (verwijderen van oude sessies, logs, tijdelijke bestanden)
  • De healthcheck-cyclus van langlopende workers
  • Geplande e-mails, nieuwsbrieven, facturatie

Conclusie

Achtergrondtaken (background) zijn vaak kritieker dan de website zelf, maar blijven een blinde vlek van de monitoring. Het heartbeat-pattern kost slechts vijf minuten om te implementeren (voeg gewoon curl toe aan het einde van de cron-regel) en geeft dezelfde zekerheid als uptime-monitoring van de frontend.

Snelle herinnering: cron-syntaxis

De meeste heartbeat-fouten zijn eigenlijk cron-fouten - de ping vuurt nooit omdat het schema verkeerd was. De vijf velden zijn:

┌── minuut (0-59)
│ ┌── uur (0-23)
│ │ ┌── dag van de maand (1-31)
│ │ │ ┌── maand (1-12)
│ │ │ │ ┌── dag van de week (0-7, 0 en 7 = zondag)
│ │ │ │ │
* * * * *  commando
Expressie Draait
*/5 * * * * elke 5 minuten
0 * * * * aan het begin van elk uur
0 3 * * * elke dag om 03:00
0 3 * * 1 elke maandag om 03:00
30 2 1 * * om 02:30 op de eerste dag van de maand

De klassieke valkuil: */5 in het veld uur betekent niet "elke 5 uur vanaf nu", maar uren deelbaar door vijf (0, 5, 10, 15, 20). Als het verwachte interval van de monitor en het echte cron-schema niet overeenkomen, krijg je spookmeldingen.

Het "dead man's switch"-principe

Het heartbeat-pattern is een dead man's switch (dodemansknop): het alarm is stilte. Dit keert de gebruikelijke faalmodus om. Normale monitoring kan stilletjes falen - als de monitoringserver zelf uitvalt, stopt hij gewoon met het versturen van meldingen en kom je er nooit achter. De heartbeat is het tegenovergestelde: de trigger voor een melding is de afwezigheid van een verwacht signaal, dus een taak die volledig stopt met draaien (de hele server is uit, de cron-daemon is uitgeschakeld, de machine is buiten gebruik gesteld en vergeten) is precies het geval dat hij het beste opvangt.

Voorbij cron: andere schedulers

Het patroon is hetzelfde, ongeacht wat de taak start: - systemd-timers - voeg curl toe aan de ExecStart-keten of aan ExecStartPost=. Koppel de OnCalendar=-timer aan een bijpassend heartbeat-interval. - Kubernetes CronJob - de laatste stap van de container pingt de heartbeat-URL. Als de pod crasht of nooit wordt ingepland (verkeerde node selector, image pull-fout), ontbreekt de ping en krijg je een melding. - Windows Task Scheduler - sluit de taak af met het PowerShell-commando Invoke-WebRequest -Uri $env:HEARTBEAT_URL -TimeoutSec 10. - CI / geplande pipelines - een nachtelijke GitHub Actions of GitLab-schedule kan als laatste stap de heartbeat pingen om te bevestigen dat de pipeline draaide én slaagde.

Veelvoorkomende anti-patterns

  • Pingen aan het begin, niet aan het einde. Een heartbeat die vóór het werk wordt verstuurd, bewijst alleen dat de taak gestart is. Zet hem ná het werk, afhankelijk van succes (&&), zodat een crash midden in de run de ping eveneens tegenhoudt.
  • De exitcode negeren. backup.sh; curl ... (puntkomma) stuurt de ping zelfs als de backup faalde. Gebruik altijd &&.
  • curl-fouten stilletjes inslikken. -fsS zorgt dat curl bij een HTTP-fout luid faalt, zodat de logs van je taak laten zien dat de heartbeat niet kon worden afgeleverd (een netwerkprobleem op de machine).
  • Eén heartbeat voor tien taken. Als vijf crons één URL delen, weet je niet welke gestopt is. Geef elke geplande taak zijn eigen heartbeat. Als je voor dezelfde service ook een HTTP-endpoint blootstelt, koppel dan de heartbeat aan een gewone uptime check en een poortcontrole op de poort van de worker, zodat je zowel "het proces draait" als "de geplande run vond plaats" afdekt.

Een concreet scenario: het stille backup-falen

Het schoolvoorbeeld voor een heartbeat ziet er zo uit. Een nachtelijke pg_dump schrijft naar een aangekoppeld volume. Maandenlang werkt het. Dan loopt het volume vol, eindigt pg_dump met een niet-nul code, en omdat de cron-regel een gewone puntkomma gebruikte, wordt de heartbeat toch verstuurd en blijft het dashboard groen. Drie weken later is de database corrupt en is er geen bruikbare backup, omdat elke "succesvolle" backup sinds de schijf volliep een bestand van nul bytes was. Het heartbeat-pattern vangt dit al de allereerste nacht op, maar alleen als het correct is aangesloten: koppel de ping aan && zodat een niet-nul exit hem tegenhoudt, en overweeg om de output te controleren, niet alleen de exitcode. Een backup-script kan eindigen met code 0 en toch een afgekapt bestand produceren. Een meer defensieve regel verifieert dat de dump niet triviaal is voordat succes wordt gesignaleerd:

/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

Nu komt de heartbeat alleen aan als het script slaagde én het resulterende bestand groter is dan 1 MB. Alles wat kleiner is houdt de ping tegen, en je krijgt een melding binnen de grace-periode in plaats van drie weken later.

Begin met het monitoren van je cron-jobs

ePulz.io ondersteunt heartbeat checks met een configureerbare grace-periode. 7 dagen gratis.

Monitoring starten →

Gerelateerd

Delen: Link gekopieerd

Probeer ePulz.io gratis - 7 dagen zonder creditcard.

Account aanmaken