I job cron falliscono in silenzio. Un exit code 0 su uno script wrapper non garantisce che la pipeline ETL abbia realmente elaborato i dati: spesso significa che un sottoprocesso è terminato in modo anomalo, è andato in timeout su una socket non gestita o è stato terminato dal kernel OOM. Affidarsi alle e-mail locali di sistema o al syslog per task critici è un rischio operativo imprevedibile.
Il Pattern 'Dead Man's Switch'
Invece di spendere $80/mese per servizi SaaS di heartbeat monitoring, è possibile garantire visibilità totale sui task tramite un pattern watchdog invertito. Il job notifica l'avvio, invia ping periodici durante l'esecuzione e segnala lo stato finale. Se il server watchdog salta un intervallo previsto, scattano immediatamente gli alert verso Slack o Discord.
Implementazione del Wrapper di Esecuzione
Di seguito è riportato un wrapper Python leggero che racchiude qualsiasi comando CLI, raccoglie metriche di runtime e gestisce l'invio dell'heartbeat prima di terminare con lo stesso exit code del processo gestito.
import sys
import time
import subprocess
import urllib.request
import json
def pulse(endpoint: str, payload: dict):
try:
req = urllib.request.Request(
endpoint,
data=json.dumps(payload).encode('utf-8'),
headers={'Content-Type': 'application/json'},
method='POST'
)
urllib.request.urlopen(req, timeout=5)
except Exception as e:
sys.stderr.write(f"[CronPulse Warning] Impossibile inviare il ping al watchdog: {e}\n")
def run_task(watchdog_url: str, job_id: str, command: list):
pulse(f"{watchdog_url}/api/v1/ping/{job_id}/start", {"status": "running"})
start_time = time.time()
result = subprocess.run(command, capture_output=True, text=True)
duration = time.time() - start_time
payload = {
"exit_code": result.returncode,
"duration_seconds": round(duration, 2),
"stdout": result.stdout[-500:],
"stderr": result.stderr[-500:]
}
status_endpoint = "complete" if result.returncode == 0 else "fail"
pulse(f"{watchdog_url}/api/v1/ping/{job_id}/{status_endpoint}", payload)
sys.exit(result.returncode)
if __name__ == "__main__":
# Uso: python wrapper.py http://watchdog:8080 daily-backup -- tar -czf backup.tar.gz /data
run_task(sys.argv[1], sys.argv[2], sys.argv[4:])Questo wrapper utilizza unicamente la libreria standard di Python senza dipendenze esterne, rendendolo perfetto per container leggeri, ambienti systemd minimali o server legacy.
Integrazione Systemd e Deployment
# /etc/systemd/system/cronpulse-watchdog.service
[Unit]
Description=CronPulse Heartbeat Watchdog Daemon
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/cronpulse-server --config=/etc/cronpulse/config.yaml
Restart=always
RestartSec=5
User=cronpulse
[Install]
WantedBy=multi-user.target