Автоматизация        11 сентября 2026        11         0

cron или таймеры systemd: что выбрать для задач по расписанию

Представьте ночной дамп базы, настроенный через cron на 03:30. Полгода он снимается без сбоев. Потом сервер перезагружают для обновления ядра в 03:25, и в эту ночь копии нет: cron не запускает пропущенные задачи и никому о них не сообщает. Выясняется это через две недели, когда копия понадобилась.

Сценарий типичный, и он объясняет, почему рядом с cron появился второй механизм. Таймеры systemd решают ту же задачу иначе: больше текста при настройке, меньше сюрпризов в работе. Ниже оба инструмента на одном примере. Команды - для Ubuntu Server 26.04 LTS, расписания проверены на systemd.

cron: одна строка и четыре ловушки

Таймер systemd: два файла вместо одной строкиРасписание пользователя редактируется командой crontab -e. Строка состоит из пяти полей времени и команды:

30 3 * * * /usr/local/bin/db-dump.sh

Поля идут в порядке: минута, час, день месяца, месяц, день недели. Эта строка запускает скрипт каждый день в 03:30. Задачи всей системы лежат файлами в /etc/cron.d/, там после полей времени добавляется имя пользователя.

Простота - главное достоинство cron и причина, по которой он до сих пор уместен. Но у этой строки есть четыре особенности, каждая из которых рано или поздно срабатывает.

Окружение. Задача запускается не в вашей оболочке. Переменная PATH у cron короткая, переменных из ~/.bashrc нет, текущий каталог другой. Скрипт, который отлично работал в терминале, под cron падает с command not found. Лечится полными путями к программам и явным заданием переменных в начале crontab.

Вывод. Всё, что задача напечатала, cron пытается отправить письмом владельцу. Почтовой службы на сервере обычно нет, и вывод исчезает. Об ошибке вы не узнаете, пока не допишете в конец строки перенаправление в файл: >> /var/log/db-dump.log 2>&1. За этим файлом потом тоже нужно следить, чтобы он не заполнил диск.

Наложение. Если задача запускается каждые пять минут, а выполняется семь, cron запустит вторую копию поверх первой. Две копии дампа пишут в один файл, две синхронизации дерутся за одни данные. Защита - блокировка через flock:

*/5 * * * * flock -n /run/lock/sync.lock /usr/local/bin/sync.sh

Ключ -n означает «не ждать»: если блокировка занята, вторая копия сразу завершится с кодом 1. Блокировки и другие приёмы, которые делают скрипты, которые не ломаются, разобраны отдельно.

Пропуски. Сервер был выключен в момент запуска - задача не выполнится до следующего раза. Для ежеминутной проверки это неважно, для еженедельного отчёта или ночной копии означает потерю целого цикла.

Короткий пример: разовую задачу «выполнить через два часа» можно поставить без единого файла командой sudo systemd-run --on-active=2h /usr/local/bin/task.sh. systemd создаст временный таймер, который исчезнет после срабатывания. Источник: документация systemd, systemd-run.

Таймер systemd: два файла вместо одной строки

Таймер состоит из пары юнитов с одинаковым именем. Служба описывает, что запускать, таймер - когда. Оба файла кладут в /etc/systemd/system/.

Файл backup.service:

[Unit]
Description=Nightly database dump
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/db-dump.sh

Файл backup.timer:

[Unit]
Description=Run database dump every night

[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=10min
Persistent=true

[Install]
WantedBy=timers.target

Type=oneshot говорит, что программа выполняется и завершается, в отличие от постоянно работающей службы. Секции [Install] в файле службы нет намеренно: её запускает таймер, а не загрузка системы. Остальные директивы те же, что в любом unit-файле службы: пользователь, зависимости, ограничения доступа.

В таймере три строки делают то, чего нет в cron. Persistent=true запоминает время последнего запуска на диске. Если момент запуска пришёлся на выключенный сервер, задача выполнится сразу после загрузки. Это ровно тот случай из начала статьи. RandomizedDelaySec=10min добавляет случайную задержку до десяти минут: когда у вас двадцать серверов снимают дамп в одну секунду и одновременно нагружают хранилище, разброс избавляет от пика.

Включается таймер, а не служба:

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer

Последняя команда показывает время следующего и предыдущего запуска. Проверить задачу, не дожидаясь ночи, можно прямым запуском службы: sudo systemctl start backup.service. Результат и весь вывод скрипта читаются командами systemctl status backup.service и journalctl -u backup.service. Перенаправлять вывод в файл не нужно: он попадает в журнал с отметками времени, и за размером журнала система следит сама.

Наложение запусков исключено самим устройством механизма. Пока служба работает, таймер не запустит её второй раз, блокировки не требуются.

Расписания: перевод с языка cron

Расписания: перевод с языка cronФормат OnCalendar читается проще, чем пять полей cron, но привыкать к нему приходится. Общий вид: ДеньНедели Год-Месяц-День Часы:Минуты:Секунды, любую часть можно заменить звёздочкой или опустить.

Таблица 1. Соответствие расписаний cron и OnCalendar

Когда cron OnCalendar
Каждый день в 03:30 30 3 * * * *-*-* 03:30:00
Каждые 15 минут */15 * * * * *:0/15
По будням в 09:50 50 9 * * 1-5 Mon..Fri *-*-* 09:50:00
По понедельникам в 08:00 0 8 * * 1 Mon *-*-* 08:00:00
Первого числа в полночь 0 0 1 * * *-*-01 00:00:00
По будням каждый час с 10 до 18 0 10-18 * * 1-5 Mon..Fri *-*-* 10..18:00:00
Раз в час, сутки, неделю @hourly, @daily, @weekly hourly, daily, weekly

У таймеров есть преимущество, которое экономит ночные тревоги: расписание можно проверить до применения. Команда разбирает выражение и показывает ближайшие срабатывания:

systemd-analyze calendar --iterations=3 "Mon..Fri *-*-* 09:50:00"
    Next elapse: Thu 2026-10-08 09:50:00 MSK
   Iteration #2: Fri 2026-10-09 09:50:00 MSK
   Iteration #3: Mon 2026-10-12 09:50:00 MSK

Видно, что после пятницы следующий запуск в понедельник, расписание понято верно. На ошибку команда отвечает сразу: выражение *-*-* 25:00:00 она отклоняет с сообщением Failed to parse calendar specification. В cron опечатка в расписании обнаруживается по факту, когда задача не запустилась или запустилась не тогда.

Кроме календарных расписаний, таймеры умеют отсчитывать интервалы от событий. OnBootSec=5min запускает задачу через пять минут после загрузки системы, OnUnitActiveSec=1h - через час после предыдущего запуска. Вторая форма полезна, когда важен промежуток между запусками, а не время на часах: задача, которая выполнялась сорок минут, в следующий раз стартует через час после своего начала, а не «в начале следующего часа».

Что выбрать

Таблица 2. cron и таймеры systemd в эксплуатации

Вопрос cron Таймер systemd
Сколько писать одна строка два файла
Пропущенный запуск теряется выполняется после загрузки при Persistent=true
Наложение запусков возможно, нужен flock исключено
Куда идёт вывод в почту или в никуда в журнал systemd
Проверка расписания нет systemd-analyze calendar
Зависимости нет «после сети», «после базы»
Ограничение ресурсов и прав нет все директивы службы
Статус последнего запуска нет systemctl status, list-timers

Из таблицы следует простое правило. Задача, от результата которой что-то зависит - резервная копия, загрузка данных, отчёт, обновление сертификата, - заслуживает таймера. Вы видите, когда она выполнялась в последний раз и чем закончилась, и можете повесить на её сбой оповещение. Мелкая задача без последствий, вроде очистки временного каталога раз в сутки, прекрасно живёт в cron, и переносить её ради единообразия незачем.

Сами дистрибутивы уже сделали этот выбор. В Ubuntu ротация журналов, очистка временных файлов и обновление списков пакетов работают на таймерах, и команда systemctl list-timers на свежем сервере показывает десяток строк.

Нюансы, которые всплывают после переноса

cron: одна строка и четыре ловушкиТочность. По умолчанию таймер срабатывает с допуском до минуты: systemd объединяет близкие по времени события, чтобы реже будить процессор. Для ночного дампа это безразлично. Если задача должна стартовать секунда в секунду, добавьте в секцию [Timer] строку AccuracySec=1s.

Часовой пояс. OnCalendar считает время по часам сервера. Сервер в UTC и расписание «09:50» означают 12:50 по Москве. Пояс можно указать прямо в выражении: Mon..Fri *-*-* 09:50:00 Europe/Moscow. Это надёжнее, чем помнить о поясе каждого сервера, и переживает его смену.

Сбой задачи не вызывает повтора. Таймер запустит службу в следующий раз по расписанию, даже если предыдущий запуск закончился ошибкой. Для задачи, которая обязана выполниться, в файл службы добавляют Restart=on-failure с паузой. С Type=oneshot это работает и даёт несколько попыток подряд.

Таймеры пользователя. Обычный пользователь может завести собственные таймеры в ~/.config/systemd/user/ и управлять ими командой systemctl --user. По умолчанию они работают, только пока пользователь вошёл в систему. Чтобы они жили всегда, администратор один раз выполняет sudo loginctl enable-linger имя_пользователя. Об этом забывают, и таймер «почему-то» перестаёт срабатывать после закрытия сессии SSH.

Контейнеры. Внутри контейнера Docker обычно нет ни systemd, ни cron. Задачу по расписанию для контейнерного приложения удобнее запускать с хоста: таймер, служба которого выполняет docker compose run --rm или docker compose exec. Так расписание, логи и статус остаются там же, где у остальных задач сервера.

И последнее о переносе. Не обязательно переписывать всё за один раз. Таймеры и cron спокойно работают рядом, и разумный порядок такой: новые важные задачи сразу оформлять таймерами, старые переносить по мере того, как до них доходят руки или после первого пропущенного запуска.

Заметка из эксплуатации: Persistent=true хранит отметку последнего запуска в каталоге /var/lib/systemd/timers/. Если таймер создан впервые и ни разу не срабатывал, после включения он не запустит задачу «задним числом», а дождётся первого времени по расписанию. Источник: документация systemd, systemd.timer.

 

Проверка команд:

  • Запущено в тестовой среде (systemd 255): оба юнита из статьи проверены командой systemd-analyze verify без замечаний; все выражения OnCalendar из таблицы 1 разобраны командой systemd-analyze calendar без ошибок; три строки вывода с датами и сообщение Failed to parse calendar specification - настоящий вывод. Поведение flock -n проверено: вторая копия завершается с кодом 1.
  • Не запускалось (нет systemd как PID 1 и cron): systemctl enable --now, list-timers, срабатывание Persistent, строки crontab. Сверено с документацией.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.