Назад до блогу

Моніторинг cron-завдань: патерн heartbeat на практиці

· 6 хв читання

HTTP-моніторинг не повідомить, що нічний cron упав. Патерн heartbeat перевертає напрямок: cron сам звітує моніторингу, і якщо він не подзвонить вчасно, ви отримаєте сповіщення.

Моніторинг cron-завдань: патерн heartbeat на практиці

Проблема: фонові завдання без HTTP-ендпоінта

У типового бекенду кілька шляхів:

  • Вебзапити (HTTP/HTTPS до сервера) - ви моніторите їх через uptime check.
  • Cron-завдання (щоденний бекап, щомісячний білінг, щогодинна синхронізація) - у них немає HTTP-ендпоінта, зовнішній моніторинг не може за ними стежити.
  • Воркери (Celery, BullMQ, Sidekiq), що обробляють чергу, - теж без HTTP.

Коли cron падає (одрук у crontab, заповнений диск, відсутня змінна середовища (environment variable) або помилка імпорту після оновлення залежності (dependency upgrade)), вас ніхто не сповістить. Ви дізнаєтеся про це в понеділок зранку, помітивши, що за вихідні не пішов жоден рахунок.

Патерн heartbeat: cron пінгує моніторинг

Принцип обернений до звичайного моніторингу:

  1. У сервісі моніторингу ви створюєте heartbeat-монітор з очікуваним інтервалом (наприклад, "кожні 60 хвилин").
  2. Ви отримуєте унікальний heartbeat URL: https://epulz.io/heartbeat/abc123xyz.
  3. У своєму cron-завданні наприкінці успішного виконання ви викликаєте цей URL (HTTP GET або POST).
  4. Якщо пінг не приходить в очікуваний час (плюс 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 днів без банківської картки.

Створити обліковий запис