systemd timers вместо cron: планировщик задач по-современному
cron делает свою работу десятилетиями, и никуда не денется. Но у systemd timers есть несколько преимуществ, из-за которых на новых серверах имеет смысл выбирать именно их. Разберёмся на практическом примере — ежедневный бэкап.
Чем таймер лучше cron
- Пропущенные запуски. Cron не поймает задание, если сервер в этот момент был выключен. Таймер с опцией
Persistent=trueвыполнит задачу при следующей загрузке. - Логи из коробки. Весь вывод задачи попадает в journald:
journalctl -u backup.service— и не нужно костылей с перенаправлением в файлы. - Зависимости. Таймер — обычный юнит, поэтому легко потребовать, например, доступность сети или смонтированного диска с бэкапами.
- Ограничение ресурсов. CPUQuota, MemoryMax и прочие cgroup-лимиты прописываются прямо в сервисе.
Шаг 1: скрипт
Начнём с того, что должен делать cronjob, — просто скрипта:
#!/bin/bash
# /usr/local/bin/backup.sh
set -euo pipefail
rsync -a --delete /var/www/ /mnt/backup/www/
echo "$(date -Is) backup done"
set -euo pipefail — обязательный минимум: скрипт упадёт при первой ошибке, а не продолжит молча портить данные дальше.
Шаг 2: service-юнит
Таймер сам ничего не запускает — он будит сервис. Создаём /etc/systemd/system/backup.service:
[Unit]
Description=Daily backup of web files
RequiresMountsFor=/mnt/backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
Nice=19
IOSchedulingClass=idle
Обратите внимание на две детали. Type=oneshot — типовой тип для разовых задач. А RequiresMountsFor=/mnt/backup гарантирует, что бэкап не запустится «в никуда», если диск не смонтирован. Nice и I/O-класс опускают задачу в фон, чтобы бэкап не мешал веб-серверу под нагрузкой.
Шаг 3: timer-юнит
Рядом кладём backup.timer — имя должно совпадать с сервисом:
[Unit]
Description=Run backup daily
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
RandomizedDelaySec=600
[Install]
WantedBy=timers.target
Пояснения к расписанию:
OnCalendar— аналог кроновской строки, но читаемый: «каждый день в 03:30»;Persistent=true— если в 03:30 сервер был выключен, задача выполнится после включения;RandomizedDelaySec=600— случайный разброс до 10 минут, чтобы десяток машин не дёргал общий NFS-сервер одновременно.
Шаг 4: активация и проверка
systemctl daemon-reload
systemctl enable --now backup.timer
systemctl list-timers backup.timer
Последняя команда показывает, когда таймер сработает в следующий раз — проверка расписания без ожидания до трёх ночи. Немедленный прогон задачи:
systemctl start backup.service
journalctl -u backup.service -e
Календарные выражения
Формат OnCalendar достаточно гибкий, вот частые случаи и их аналоги в cron:
| Расписание | OnCalendar | cron |
|---|---|---|
| Каждые 15 минут | *:0/15 | */15 * * * * |
| Ежедневно в 3:30 | *-*-* 03:30:00 | 30 3 * * * |
| По будням в 9:00 | Mon..Fri *-*-* 09:00:00 | 0 9 * * 1-5 |
| Первое число месяца | *-*-01 00:00:00 | 0 0 1 * * |
Проверить выражение до установки поможет systemd-analyze calendar "Mon..Fri 09:00" — он покажет дату ближайшего срабатывания.
Когда cron всё-таки уместнее
Таймеры требуют двух файлов вместо одной строки — это ощутимо на простых задачах. Если у вас пара crontab-записей на домашнем сервере, который работает 24/7, и логи не нужны — cron честнее. А вот для задач с зависимостями, лимитами и требованием «не потерять запуск» systemd timer — более предсказуемый инструмент, плюс вся инфраструктура наблюдения (systemctl status, journald, prometheus-экспортёры юнитов) работает с ним нативно.