Моніторинг cron-завдань: патерн heartbeat на практиці
· 6 хв читання
HTTP-моніторинг не повідомить, що нічний cron упав. Патерн heartbeat перевертає напрямок: cron сам звітує моніторингу, і якщо він не подзвонить вчасно, ви отримаєте сповіщення.
Проблема: фонові завдання без HTTP-ендпоінта
У типового бекенду кілька шляхів:
- Вебзапити (HTTP/HTTPS до сервера) - ви моніторите їх через uptime check.
- Cron-завдання (щоденний бекап, щомісячний білінг, щогодинна синхронізація) - у них немає HTTP-ендпоінта, зовнішній моніторинг не може за ними стежити.
- Воркери (Celery, BullMQ, Sidekiq), що обробляють чергу, - теж без HTTP.
Коли cron падає (одрук у crontab, заповнений диск, відсутня змінна середовища (environment variable) або помилка імпорту після оновлення залежності (dependency upgrade)), вас ніхто не сповістить. Ви дізнаєтеся про це в понеділок зранку, помітивши, що за вихідні не пішов жоден рахунок.
Патерн heartbeat: cron пінгує моніторинг
Принцип обернений до звичайного моніторингу:
- У сервісі моніторингу ви створюєте heartbeat-монітор з очікуваним інтервалом (наприклад, "кожні 60 хвилин").
- Ви отримуєте унікальний heartbeat URL:
https://epulz.io/heartbeat/abc123xyz. - У своєму cron-завданні наприкінці успішного виконання ви викликаєте цей URL (HTTP GET або POST).
- Якщо пінг не приходить в очікуваний час (плюс grace-період), моніторинг вас сповіщає.
Практичний приклад: heartbeat із cron-завдання
# /etc/crontab
0 3 * * * www-data /usr/local/bin/backup.sh && curl -fsS -m 10 \
https://epulz.io/heartbeat/abc123xyz > /dev/null
Ключовий тут оператор &&: heartbeat надсилається лише тоді, коли backup.sh завершується з кодом виходу 0. Якщо скрипт упаде, пінг не прийде, і протягом години ви отримаєте сповіщення.
Порада: Для ретельнішого покриття додайте також 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
Тоді моніторинг зможе відрізнити "почав, але не завершив" (скрипт завис) від "взагалі не почав" (cron-завдання не запустилося).
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:
# Heartbeat не надсилається - моніторинг вас сповістить
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-період: скільки часу дати перед сповіщенням
Heartbeat-монітору потрібна певна толерантність. Cron іноді виконується довше за звичне, мережа має затримку, а синхронізація NTP може бути трохи зміщена. Grace-період - це час після завершення очікуваного інтервалу, протягом якого моніторинг ще чекає, перш ніж запустити сповіщення.
Практичні значення:
- Щогодинний cron: інтервал 60 хв + grace 10 хв
- Щоденний бекап (у середньому 20 хв): інтервал 1440 хв + grace 60 хв
- Щотижневий звіт: інтервал 10080 хв + grace 360 хв (6 год)
Надто тісний grace = хибні сповіщення (false positive). Надто вільний = запізніле сповіщення саме в той момент, коли завдання реально падає.
Де патерн heartbeat допомагає найбільше
- Нічні бекапи БД
- Синхронізація із зовнішніми API (CRM, бухгалтерія, платежі)
- Розрахунки звітів
- Завдання cleanup (видалення старих сесій, логів, тимчасових файлів)
- Цикл healthcheck довгоживучих воркерів
- Заплановані листи, розсилки, білінг
Висновок
Фонові завдання (background) часто критичніші за сам сайт, але залишаються сліпою плямою моніторингу. Патерн heartbeat потребує лише п'яти хвилин на впровадження (достатньо додати curl у кінець рядка cron) і дає таку саму впевненість, як uptime-моніторинг фронтенду.
Швидке нагадування: синтаксис cron
Більшість помилок heartbeat насправді є помилками cron - пінг ніколи не спрацьовує, бо розклад був неправильним. П'ять полів:
┌── хвилина (0-59)
│ ┌── година (0-23)
│ │ ┌── день місяця (1-31)
│ │ │ ┌── місяць (1-12)
│ │ │ │ ┌── день тижня (0-7, 0 і 7 = неділя)
│ │ │ │ │
* * * * * команда
| Вираз | Запускається |
|---|---|
*/5 * * * * |
кожні 5 хвилин |
0 * * * * |
на початку кожної години |
0 3 * * * |
щодня о 03:00 |
0 3 * * 1 |
щопонеділка о 03:00 |
30 2 1 * * |
о 02:30 першого числа місяця |
Класична пастка: */5 у полі година означає не "кожні 5 годин від поточного моменту", а години, що діляться на п'ять (0, 5, 10, 15, 20). Якщо очікуваний інтервал монітора й реальний розклад cron не збігаються, ви отримуєте фантомні сповіщення.
Принцип "dead man's switch"
Патерн heartbeat - це dead man's switch (запобіжник мертвої людини): тривога - це тиша. Це перевертає звичайний режим відмови. Звичайний моніторинг може відмовити беззвучно - якщо впаде сам сервер моніторингу, він просто перестане надсилати сповіщення, і ви про це ніколи не дізнаєтеся. Heartbeat - навпаки: тригером сповіщення є відсутність очікуваного сигналу, тож завдання, яке повністю перестає виконуватися (весь сервер вимкнено, демон cron вимкнено, машину виведено з експлуатації та забуто), - це саме той випадок, який він ловить найкраще.
Поза cron: інші планувальники
Патерн однаковий незалежно від того, що запускає завдання:
- таймери systemd - додайте curl до ланцюжка ExecStart або до ExecStartPost=. Зіставте таймер OnCalendar= з відповідним інтервалом heartbeat.
- Kubernetes CronJob - останній крок контейнера пінгує heartbeat URL. Якщо под падає або взагалі не планується (неправильний node selector, збій image pull), пінг відсутній, і ви отримуєте сповіщення.
- Windows Task Scheduler - завершіть завдання командою PowerShell Invoke-WebRequest -Uri $env:HEARTBEAT_URL -TimeoutSec 10.
- CI / заплановані пайплайни - нічний GitHub Actions або розклад GitLab може останнім кроком пінгнути heartbeat, підтвердивши, що пайплайн запустився і пройшов.
Поширені антипатерни
- Пінг на початку, а не в кінці. Heartbeat, надісланий до роботи, доводить лише те, що завдання почалося. Поставте його після роботи, з умовою успіху (
&&), щоб збій посеред виконання теж затримав пінг. - Ігнорування коду виходу.
backup.sh; curl ...(крапка з комою) надсилає пінг, навіть якщо бекап упав. Завжди використовуйте&&. - Мовчазне проковтування збоїв curl.
-fsSзмушує curl гучно падати при HTTP-помилці, тож логи вашого завдання покажуть, що heartbeat не вдалося доставити (мережева проблема на машині). - Один heartbeat на десять завдань. Якщо п'ять cron'ів ділять один URL, ви не дізнаєтеся, який перестав виконуватися. Дайте кожному запланованому завданню власний heartbeat. Якщо для того ж сервісу ви виставляєте і HTTP-ендпоінт, зіставте heartbeat зі звичайним uptime check і перевіркою порту на порту воркера, щоб покрити і "процес працює", і "запланований запуск відбувся".
Конкретний сценарій: тихий збій бекапу
Хрестоматійний випадок для heartbeat виглядає так. Нічний pg_dump пише на змонтований том. Місяцями все працює. Потім том заповнюється, pg_dump завершується з ненульовим кодом, і оскільки рядок cron використовував звичайну крапку з комою, heartbeat усе одно надсилається, а дашборд залишається зеленим. Через три тижні база даних пошкоджена, і придатного бекапу немає, бо кожен "успішний" бекап від моменту заповнення диска був файлом нульового розміру.
Патерн heartbeat ловить це вже першої ночі, але лише якщо він підключений правильно: зумовте пінг через &&, щоб ненульовий вихід його затримав, і розгляньте перевірку виводу, а не лише коду виходу. Скрипт бекапу може завершитися з кодом 0 і все одно видати обрізаний файл. Захищеніший рядок перевіряє, що дамп нетривіальний, ще до сигналу про успіх:
/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
Тепер heartbeat приходить лише якщо скрипт відпрацював успішно і підсумковий файл більший за 1 МБ. Усе, що менше, затримує пінг, і ви отримуєте сповіщення в межах grace-періоду, а не за три тижні.
Почніть моніторити свої cron-завдання
ePulz.io підтримує heartbeat-перевірки з налаштовуваним grace-періодом. 7 днів безкоштовно.
Пов'язане
Спробуйте ePulz.io безкоштовно - 7 днів без банківської картки.
Створити обліковий запис