Cron годами оставался де-факто стандартом планировщика в Linux, и во многих системах он работает до сих пор. Но у него структурные недостатки: результат задания уходит в локальную почту и там же теряется, пропущенный при выключенном сервере запуск просто исчезает, а для диагностики приходится копаться в /var/log/syslog. У таймеров systemd задача — это юнит с типом oneshot, все выводы попадают в журнал, пропущенный запуск можно догнать после загрузки, а расписание читается человеком. Ниже — практика миграции с cron на таймеры без боли.

Почему cron перестаёт устраивать

На старых серверах cron незаметен, пока всё идёт по плану. Проблемы вскрываются, когда задача падает: у cron нет понятия «юнит завершился с ошибкой», нет повтора при сбое и нет ограничений на ресурсы. Если скрипт виснет, он может висеть годами, и вы узнаете об этом только по отсутствию результата.

  • вывод скрипта уходит в почту или в /dev/null, а не в централизованный журнал;
  • cron не знает о зависимостях: задача может стартовать до поднятия сети или монтирования диска;
  • у cron нельзя задать лимиты памяти и CPU, висящий процесс съест машину;
  • переменная PATH в cron минимальна, поэтому скрипт, работающий из-под руки, внезапно падает по расписанию.

Пара юнитов для реальной задачи

Таймер — это всегда два файла: сервис (что делать) и таймер (когда делать). Возьмём ночной отчёт: скрипт /usr/local/bin/nightly-report.sh уже написан и проверен вручную. Первый файл — /etc/systemd/system/nightly-report.service:

[Unit]
Description=Ночной отчёт по сайтам
After=network-online.target

[Service]
Type=oneshot
User=www-data
WorkingDirectory=/srv/www
ExecStart=/usr/local/bin/nightly-report.sh
Nice=10
MemoryMax=512M
CPUQuota=80%

Второй файл — /etc/systemd/system/nightly-report.timer:

[Unit]
Description=Запуск ночного отчёта в 03:00

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

[Install]
WantedBy=timers.target

После правки достаточно выполнить daemon-reload и включить таймер, больше ничего настраивать не нужно.

Перенос существующего cron-задания по шагам

Когда задание уже живёт в /etc/cron.d, перенос выполняется по алгоритму, а не на глаз. Сначала найдите строку командой grep -R по каталогам /etc/cron.d и /etc/crontab, запомнив пользователя, рабочий каталог и переменные окружения — их предстоит перенести в сервис. Второй шаг: прогон вручную под тем же пользователем и с тем же PATH, что получит юнит, например sudo -u www-data env -i PATH=/usr/local/bin:/usr/bin:/bin /usr/local/bin/nightly-report.sh. Третий шаг: заверните успешный запуск в сервис и включите таймер, а cron-строку пока просто закомментируйте. Четвёртый шаг: за пару дней сверьте журнал командой journalctl -u nightly-report.service за два расписания и только после этого удаляйте старую строку из crontab.

Синтаксис OnCalendar и перевод cron-строк

Звёздочки пугают первое время, но структура фиксированная: год-месяц-день час:минута:секунда. Система также понимает готовые псевдонимы вроде hourly, daily, weekly, monthly и yearly.

  • 0 3 * * * превращается в *-*-* 03:00:00, либо просто в daily;
  • */15 * * * * превращается в *-*-*:00/15:00:00;
  • 30 2 * * 1 превращается в Mon *-*-* 02:30:00, работает и диапазон Mon..Fri;
  • 0 0 1 * * превращается в *-*-01 00:00:00.

Проверить выражение до включения можно без риска — команда systemd-analyze calendar печатает ближайшие срабатывания в понятном виде.

Управление и проверка

Классический цикл выглядит так: отредактировали юнит, перечитали конфигурацию, включили таймер и убедились, что он в расписании.

sudo systemctl daemon-reload
sudo systemctl enable --now nightly-report.timer
systemctl list-timers --all | grep nightly
journalctl -u nightly-report.service -n 50 --no-pager

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

Пропущенные запуски и разброс нагрузки

Ключевое отличие от cron — параметр Persistent. Если сервер в 03:00 был выключен, после загрузки таймер сам догонит пропущенный запуск. Флаг стампуется в /var/lib/systemd/timers/stamp-*.timer, поэтому удалите стамп, и догон снова сработает. Для заданий, которые бессмысленны задним числом, например сбор метрик за вчера, оставьте Persistent=false.

RandomizedDelaySec добавляет случайную задержку, чтобы десятки серверов не долбили общий NFS одновременно. В связке с FixedRandomDelay=true задержка фиксируется внутри одного юнита, но остаётся разной между машинами. Вместо календарного расписания можно задать относительное время: OnBootSec=15min запустит задачу через 15 минут после загрузки, OnUnitActiveSec=1h повторит её каждый час от предыдущего запуска.

Типичные проблемы при переходе

Самая частая жалоба звучит так: «в cron работало, в таймере — нет». Причина почти всегда окружение и права.

  • PATH системного юнита другой: пишите ExecStart с абсолютными путями или задайте Environment=PATH=…;
  • скрипт пишет в домашнюю директорию, а User=www-data: домашнего каталога может не быть, укажите WorkingDirectory;
  • ошибка Permission denied на лог-файле: задайте Umask=0027 или заранее создайте файл с нужными правами;
  • таймер молчит после правки юнита: без daemon-reload systemd работает со старой версией;
  • проверяйте синтаксис заранее: systemd-analyze verify /etc/systemd/system/nightly-report.timer.

Отдельный случай — задания, которые по cron шли под root и писали в защищённые каталоги. Не оставляйте User=root только ради простоты: дайте юниту точные права через каталог, владельца и Umask.

Пользовательские таймеры

Не всё нужно запускать от root. Юниты можно класть в каталог ~/.config/systemd/user и управлять ими через systemctl —user. Но есть нюанс: пользовательские юниты живут, только пока открыта сессия, поэтому для задач вроде часовой синхронизации включите linger.

mkdir -p ~/.config/systemd/user
cp sync.timer sync.service ~/.config/systemd/user/
systemctl --user enable --now sync.timer
loginctl enable-linger $USER
systemctl --user list-timers

Итог

Таймеры не заменяют cron там, где нужна экзотическая вложенность выражений, но для 90 процентов серверных задач — резервных копий, очистки логов, перезапуска сервисов — они строго лучше: журнал, лимиты ресурсов, догон пропущенных запусков и единый механизм управления. Начните с одного юнита, пройдитесь по list-timers и journalctl, и через неделю в /etc/cron.d останется только то, что действительно неудобно переносить.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте