Таймер systemd, как и строка в crontab, запускает команду по расписанию. Файлов два вместо одной строки,
зато вывод задачи сохраняется в журнал, а запуск, пропущенный из-за выключенного сервера, выполнится после
включения. Чаще всего при переносе ошибаются в расписании: у OnCalendar свой формат, и одно правило cron
в нём работает иначе.
Чем таймер лучше cron?
- Вывод попадает в журнал. Всё, что команда печатает, лежит в
journalctl -u backup.serviceс временем и кодом выхода. В cron вывод пропадает, если не перенаправить его в файл. - Пропущенный запуск догоняется. С
Persistent=trueзадача, которая должна была сработать, пока сервер был выключен, запустится сразу после включения. Cron такие запуски просто пропускает. - Запуски не наслаиваются. Если служба ещё работает, когда таймер срабатывает снова, второй экземпляр
не стартует. В cron для этого нужен
flock. - Окружение задано явно. Пользователь и переменные описаны в файле службы. Команда не упадёт из-за
короткого
PATH, как бывает в cron. - Видно, когда следующий запуск.
systemctl list-timersпоказывает все таймеры с временем прошлого и следующего срабатывания.
Cron проще, когда задача одна и короткая или когда systemd нет, например в Docker-контейнере.
Как перенести задачу из crontab?
Допустим, в crontab такая строка:
0 3 * * * /home/deploy/backup.sh
Создайте службу /etc/systemd/system/backup.service. Она описывает, что запускать:
[Unit]
Description=Ночной бэкап базы
[Service]
Type=oneshot
User=deploy
ExecStart=/home/deploy/backup.sh
И таймер /etc/systemd/system/backup.timer с тем же именем. Он описывает, когда запускать:
[Unit]
Description=Ночной бэкап базы в 03:00
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
Включите таймер и уберите строку из crontab, иначе задача будет запускаться дважды:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
crontab -e # удалите старую строку
Таймер запускает службу с таким же именем, поэтому backup.timer запустит backup.service. Другое имя
можно указать в Unit=.
Как перевести cron-выражение в OnCalendar?
Формат OnCalendar — день-недели год-месяц-число часы:минуты:секунды. День недели можно не указывать,
* значит «любое значение», диапазон пишется через .., шаг через /.
| cron | OnCalendar | Когда |
|---|---|---|
* * * * * |
*-*-* *:*:00 |
каждую минуту |
*/5 * * * * |
*-*-* *:00/5:00 |
каждые 5 минут |
0 * * * * |
*-*-* *:00:00 или hourly |
каждый час |
15 */2 * * * |
*-*-* 00/2:15:00 |
каждые 2 часа в 15 минут |
0 3 * * * |
*-*-* 03:00:00 |
каждый день в 03:00 |
30 8 * * 1-5 |
Mon..Fri *-*-* 08:30:00 |
по будням в 08:30 |
0 0 * * 1 |
Mon *-*-* 00:00:00 |
по понедельникам в полночь |
0 9 1 * * |
*-*-01 09:00:00 |
1-го числа в 09:00 |
0 0 1 */3 * |
*-01/3-01 00:00:00 |
раз в квартал |
Готовая строка для любого выражения есть в инструменте «Cron-выражение»: впишите выражение,
и в блоке «В других планировщиках» появится OnCalendar. Там же записи для Laravel, Kubernetes и
GitHub Actions.
Если в cron заданы и день месяца, и день недели (0 0 1 * 1), задача запускается, когда совпало любое
из двух: 1-го числа или в понедельник. В OnCalendar должны совпасть оба условия. Чтобы получить поведение cron, укажите две строки OnCalendar= в одном таймере: одну
с числом, другую с днём недели. Таймер сработает по любой из них.
Проверить строку можно до того, как ставить таймер:
systemd-analyze calendar --iterations=5 "Mon..Fri *-*-* 08:30:00"
Команда покажет нормализованную запись и пять ближайших запусков, а при ошибке в строке напишет, где она.
В каком часовом поясе считается OnCalendar?
В часовом поясе сервера, как и cron. Посмотреть его можно командой timedatectl. Если сервер в UTC, а
задача нужна в 03:00 по Москве, допишите пояс в конец строки:
OnCalendar=*-*-* 03:00:00 Europe/Moscow
Так расписание не съедет, если пояс сервера когда-нибудь поменяют.
Как проверить, что таймер работает?
systemctl list-timers backup.timer # когда прошлый и следующий запуск
systemctl status backup.service # чем закончился последний запуск
journalctl -u backup.service --since today # вывод задачи
sudo systemctl start backup.service # запустить вручную, не дожидаясь расписания
Если в list-timers таймера нет, он не включён: проверьте systemctl is-enabled backup.timer.
Частые ошибки
- Забыли
daemon-reload. После правки файлов systemd продолжает работать со старой версией, пока не выполнитьsudo systemctl daemon-reload. - Включили службу вместо таймера.
systemctl enable backup.serviceничего не запланирует. Включать нужноbackup.timer. - Нет
Type=oneshot. Без него служба считается запущенной, как только процесс стартовал, иsystemctl startне ждёт конца задачи. Для задач по расписанию ставьтеoneshot. - Запуск на минуту позже. По умолчанию
AccuracySec=1min: systemd может сдвинуть срабатывание в пределах минуты, чтобы объединить пробуждения. Если важна точность до секунды, поставьтеAccuracySec=1s. Persistent=trueтам, где догонять не нужно. После простоя сервера таймер сразу запустит пропущенное. Бэкапу это и нужно, а рассылка клиентам уйдёт в момент включения сервера, даже если это ночь.- Пояс указан в проверке, а не в таймере. Если Вы следите за задачей в Steadrun, часовой пояс проверки
должен совпадать с поясом, в котором считается
OnCalendar.
Как узнать, что задача не выполнилась?
Упавшую службу видно в systemctl status и в журнале, но сообщать об этом systemd не умеет. А если таймер
выключили или сервер лежит, в журнал вообще ничего не попадёт.
Чтобы узнавать о таких сбоях, задача должна отмечаться после каждого успешного запуска, а кто-то должен ждать этой отметки. Так работает Steadrun. Добавьте в службу пинг после основной команды:
[Service]
Type=oneshot
User=deploy
ExecStart=/home/deploy/backup.sh
ExecStartPost=-/usr/bin/curl -fsS -m 10 --retry 3 -o /dev/null https://steadrun.ru/ping/ВАШ-UUID
ExecStartPost в службе oneshot выполняется, только если ExecStart завершился успешно. Минус перед
путём значит, что сбой самого curl не помечает службу упавшей. В Steadrun создайте проверку с выражением
0 3 * * * и тем же часовым поясом. Если пинг не придёт к сроку, Вы получите алерт в Telegram, на почту или
на вебхук.
Вариант, который сразу сообщает о падении и присылает хвост вывода, есть в рецептах. Как подключить проверку с нуля, описано в быстром старте. Бесплатно можно следить за пятью задачами.