Налаштування серверів

Cron, systemd та планувальники задач на сервері: чому ваш хостинг працює вхолосту 90% часу

Фахівець у офісі перевіряє налаштування cron задач на сервері

Уявіть собі ресторан, де кухар готує всі страви з меню кожні п'ять хвилин - незалежно від того, чи є хоч один відвідувач у залі. Абсурд? Безумовно. Але саме так працює більшість хостинг-серверів, на яких cron-задачі налаштовані "за замовчуванням" і ніхто жодного разу не заглядав у їхній розклад після початкового деплою. Планувальники задач - це нервова система вашого сервера, і якщо вона працює хаотично, весь організм страждає. Тихо, непомітно, але невблаганно.

Що насправді робить ваш сервер, поки ви спите

Більшість адміністраторів впевнені, що їхній сервер "просто працює". Насправді у фоновому режимі відбувається десятки, а іноді сотні запланованих операцій: ротація логів, оновлення SSL-сертифікатів, бекапи баз даних, очищення тимчасових файлів, надсилання email-дайджестів, перевірка оновлень CMS. Кожна з цих задач споживає CPU, оперативну пам'ять і дисковий I/O.

Проблема не в тому, що ці задачі існують, а в тому, що ніхто не контролює, коли і як вони виконуються одночасно.

Класичний приклад. На WordPress-сайті WP-Cron запускає перевірку оновлень плагінів при кожному візиті користувача. Одночасно серверний cron о 3:00 запускає mysqldump для повного бекапу бази. А logrotate вирішує саме в цей момент стиснути 2 ГБ access-логів. Результат - сервер "підвисає" на 15-40 секунд. Якщо у вас інтернет-магазин з нічними покупцями (а вони є завжди) - це втрачені замовлення.

Моніторинг роботи планувальників задач сервера з детальною перевіркою даних
Моніторинг роботи планувальників задач сервера з детальною перевіркою даних

Cron проти systemd timers: академічне порівняння двох філософій

Cron з'явився у Version 7 Unix у 1979 році. Йому 46 років. Він працює. Він простий. І він безнадійно застарів для сучасних завдань - приблизно як факс у світі месенджерів. Systemd timers, що прийшли на зміну у більшості сучасних Linux-дистрибутивів, пропонують принципово інший підхід до планування.

"Cron - це інструмент, який вирішує задачу 1970-х років: запустити щось у визначений час. Systemd timers вирішують задачу 2020-х: запустити щось у визначений час з урахуванням залежностей, ресурсних обмежень і можливості відновлення після збою." - Lennart Poettering, творець systemd, у дискусії на Hacker News, 2021

Давайте порівняємо ці два підходи по конкретних параметрах, що критично важливі для хостинг-серверів:

Параметр Cron Systemd Timers
Мінімальний інтервал 1 хвилина 1 мікросекунда
Логування syslog (загальний потік) journalctl (ізольоване)
Залежності між задачами Немає After=, Requires=
Обмеження ресурсів Через nice/ionice вручну CPUQuota=, MemoryMax= вбудовані
Поведінка при пропущеному запуску Задача просто не виконується Persistent=true - виконає при старті
Рандомізація часу запуску Немає (всі о 3:00 = всі разом) RandomizedDelaySec= вбудований
Моніторинг статусу Перевіряти вручну systemctl status + інтеграція з Prometheus

Зверніть увагу на рядок "Рандомізація часу запуску". Це та причина, через яку о 3:00 ночі сервери по всьому світу синхронно "гикають" - бо кожен адміністратор вписав у crontab магічне 0 3 * * * для бекапів.

П'ять помилок конфігурації, які перетворюють планувальник на ворога

За 15 років роботи з серверами я бачив одні й ті самі помилки настільки часто, що можу впізнати їх по характерному патерну навантаження в графіках Grafana. Ось вони, ранжовані за ступенем руйнівності:

  1. Відсутність lock-файлів. Cron не перевіряє, чи завершилась попередня копія задачі. Якщо бекап бази займає 70 хвилин, а cron запускає його щогодини - о 4:00 у вас працюють вже дві копії mysqldump одночасно. О 5:00 - три. Сервер падає о 6:00, коли прокидається ранковий трафік.
  2. MAILTO не налаштований або ігнорується. Cron відправляє результати виконання на email. Якщо email не налаштований, повідомлення про помилки зникають у порожнечу. Роками.
  3. Використання абсолютних шляхів без перевірки. Скрипт посилається на /usr/local/bin/php, але після оновлення системи PHP перемістився у /usr/bin/php. Задача тихо падає з exit code 127.
  4. Ігнорування часових зон. Сервер у Франкфурті, клієнт у Києві, розробник у Торонто. Бекап повинен запускатися "вночі". Чиєї ночі?
  5. Жодного обмеження ресурсів. Cron-задача з правами root без лімітів на CPU та RAM - це як дати підлітку кредитну картку без ліміту. Рано чи пізно він "вичерпає" все.
Інтерфейс програми оптимізації cron задач хостинг на екрані комп'ютера
Інтерфейс програми оптимізації cron задач хостинг на екрані комп'ютера

Практичний рецепт: як перебудувати розклад задач за одні вихідні

Теорія - прекрасна річ, але вам потрібен конкретний план дій. Ось покроковий алгоритм, який я використовую при аудиті серверів клієнтів. Він займає 4-6 годин і зазвичай зменшує пікове навантаження на 30-50%.

Перший крок - інвентаризація. Зберіть усі cron-задачі з усіх джерел. Це не тільки /var/spool/cron/, а й /etc/cron.d/, /etc/cron.daily/, /etc/cron.hourly/, а також вбудовані планувальники CMS (WordPress, Magento, Drupal). Команда for user in $(cut -f1 -d: /etc/passwd); do crontab -l -u $user 2>/dev/null; done - ваш перший друг.

Другий крок - класифікація за критичністю:

  • Критичні - бекапи, оновлення SSL, моніторинг (не можна пропускати, потрібен Persistent=true)
  • Важливі - очищення кешу, ротація логів, синхронізація даних (можна затримати на 1-2 години)
  • Косметичні - статистика, звіти, оптимізація зображень (можна виконувати у найменш завантажений час)
  • Зайві - дубльовані задачі, залишки від старих проектів, тестові скрипти (видалити негайно)

Третій крок - розподіл у часі. Золоте правило: ніколи не ставте дві ресурсомісткі задачі у один п'ятихвилинний слот. Використовуйте RandomizedDelaySec у systemd або додавайте sleep $((RANDOM % 300)) на початку cron-скриптів.

Четвертий і найважливіший крок - впровадьте flock для кожної cron-задачі без винятку. Замість 0 * * * * /scripts/backup.sh пишіть 0 * * * * /usr/bin/flock -n /tmp/backup.lock /scripts/backup.sh. Ця одна зміна запобігає 80% інцидентів, пов'язаних з перевантаженням.

Systemd timers на практиці: міграція без страху

Якщо ви вирішили перейти з cron на systemd timers (а на серверах з Ubuntu 20.04+, Debian 11+ або CentOS Stream 9 це цілком виправдано), процес простіший, ніж здається. Для кожної cron-задачі потрібно створити два файли: .service та .timer.

Приклад. Замість cron-рядка 30 2 * * * /scripts/db-backup.sh ви створюєте:

/etc/systemd/system/db-backup.service - з описом задачі, обмеженнями CPUQuota=25% та MemoryMax=512M. І /etc/systemd/system/db-backup.timer - з OnCalendar=*-*-* 02:30:00, Persistent=true та RandomizedDelaySec=300.

Що ви отримуєте одразу:

  • Команда systemctl list-timers показує, коли кожна задача запускалась востаннє і коли запуститься наступного разу
  • journalctl -u db-backup.service видає ізольовані логи конкретно цієї задачі - більше ніякого гортання syslog у пошуках голки в стозі сіна
  • Якщо сервер був вимкнений о 2:30, задача виконається одразу після запуску (Persistent=true) - cron просто промовчав би
  • Задача фізично не може з'їсти більше 25% CPU та 512 МБ пам'яті - навіть якщо скрипт містить баг

Коли планувальник стає архітектором: задачі як код

Найзріліший підхід, який я спостерігаю у командах з DevOps-культурою - це зберігання конфігурації всіх планованих задач у Git-репозиторії разом з інфраструктурним кодом. Ansible, Terraform, навіть простий Makefile - не має значення інструмент. Має значення принцип: якщо конфігурацію cron не можна відтворити з репозиторію за 5 хвилин, її не існує.

Одна команда з Кракова, з якою я працював минулого року, втратила сервер через збій дискового контролера. Відновити дані з бекапу вдалося за годину. Але відтворити розклад з 47 cron-задач, налаштованих вручну за три роки різними людьми - зайняло два тижні. Два тижні неповноцінної роботи сервісу. Після цього інциденту вони перенесли всі задачі у systemd timers, описали їх у Ansible-ролях і тепер повний деплой нового сервера займає 12 хвилин.

Чи перевіряли ви останнім часом, що саме виконує ваш сервер о третій годині ночі? Можливо, він вже роками робить щось, про що ви давно забули - і витрачає на це ресурси, за які ви платите щомісяця. Одна команда crontab -l може відкрити вам більше, ніж будь-який моніторинг. Спробуйте сьогодні ввечері. Результат може вас здивувати.