Cron против таймеров systemd: cron запускает строки, таймеры запускают юниты
Одна и та же задача 03:30 на обоих часах — молчаливая почта против журналирования в journal, пропущенные запуски против Persistent=true, и таблица решений, где приземлится ваша следующая задача.
Своя машина с systemd → таймер. Однострочные задачи, контейнеры, чужие ящики → cron.
Cron падает молча — он кладёт письмо в локальный ящик, который никто не читает. Таймеры пишут каждый запуск в журнал: journalctl -u mytimer.service.
Persistent=true догоняет задачи, пропущенные, пока машина была выключена ; cron молча их пропускает.
ДВА ЧАСА
Одна и та же задача — ночной бэкап в 03:30 — на обоих часах, и то, что каждый оставляет после себя:
CRON · одна строка
30 3 * * * /usr/local/bin/backup.sh
запускается, когда настенные часы совпали. Если упало — вывод уходит письмом в локальный ящик, обычно непрочитанный. Машина была выключена в 03:30? Пропущено, молча.
а он вообще сработал? journalctl -u cron -n 20 или grep CRON /var/log/syslog
ТАЙМЕР SYSTEMD · два файла юнитов
# foo.timer
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
# foo.service — the job itself
юнит первого класса: статус, зависимости, логи на каждый запуск в журнале. Машина спала в нужный час? Persistent=true догонит одним запуском при загрузке.
проверить: systemd-analyze calendar "*-*-* 03:30:00" — следующий запуск, ещё до сохранения
вердикт по графикам: пять полей cron — компактный incumbent; OnCalendar многословнее, но строго выразительнее — Mon..Fri 09..17:00:00 в одну строку, а RandomizedDelaySec не даёт тысяче машин молотить по одному серверу
эти часы пере-стампят по собственным командам записи — они не могут читать журнал вашей машины; где в самом деле ваш журнал: разделы 04 и 05.
ПРАКТИЧЕСКИЕ РАЗРЫВЫ
- Логи : таймеры пишут stdout/stderr в журнал отдельно по юниту; cron в лучшем случае шлёт почту локальному пользователю.
- Пропущенные запуски : таймер с
Persistent=trueдогоняет одним запуском при загрузке, если его график был пропущен; cron просто пропускает. - Зависимости : таймеры умеют ждать
network-online.targetили точек монтирования; cron заставляет руками писать retry-логику в скрипте. - Синтаксис : cron — одна строка; таймер — два файла. Это вся цена перехода.
- Монотонный вариант:
OnBootSec=15min— скачки настенных часов не способны его сбить.
04ПРОВЕРИТЬ ТАЙМЕР БЕЗ ОЖИДАНИЯ
Три команды отвечают на всё — показать, что запланировано и когда сработает в следующий раз, прогнать задачу руками ровно как это сделал бы таймер, затем прочитать его логи:
systemctl list-timers --all # every timer, next + last run
sudo systemctl start backup.service # fire the job now, same unit as the timer
journalctl -u backup.service -f # watch its output live
Заметьте разделение: systemctl start backup.timer заряжает график; service — сама задача. Если list-timers показывает ваш таймер, systemctl status backup.service зелёный, а журнал показывает ваш вывод — вся цепочка жива.
verify: systemctl list-timers --all → ваш таймер, прошлый и следующий запуски в списке
05ЗАПУСТИТЬ CRON-ЗАДАЧУ ВРУЧНУЮ
Cron-задачи запускаются с облегчённым окружением, поэтому «у меня в shell работает, в cron падает» — целый жанр бага. Чтобы воспроизвести cron верно, гоните команду через sh с окружением, которое использовал бы cron:
crontab -l # confirm the exact line
env -i SHELL=/bin/sh PATH=/usr/bin:/bin sh -c '/usr/local/bin/backup.sh'
grep CRON /var/log/syslog | tail # did cron even fire it? (Debian/Ubuntu)
journalctl -u cron -n 20 # same, on systemd distros
Эта env -i строка — честный ручной тест: голое окружение с cron-дефолтным PATH. Две классики cron — голый PATH и некавыченный % (cron трактует его как перевод строки) — вылазят сразу же в этом тесте.
verify: journalctl -u cron -n 20 → CRON-запись вашей команды
«МОЙ ТАЙМЕР НЕ СРАБАТЫВАЕТ»
Таймер выглядит мёртвым — обычно это значит, что его service падает мгновенно. Четыре причины, в порядке проверки:
- Имена service и таймера не совпадают.
foo.timerзажигаетfoo.service— опечатка или переименованный юнит, и таймер «запускается» в пустоту.systemctl cat foo.timerпоказывает точно, в кого целится. - Отредактировано, но не перезаряжено. После правки файла юнита:
sudo systemctl daemon-reload && sudo systemctl restart foo.timer. Без этого ваш новый график не жив. - Неверная команда статуса.
systemctl status foo.timerвсегда показывает active, даже когда ничего не делает — читайтеlist-timersвместо. - Service, который он зажигает, падает мгновенно.
journalctl -u foo.service --since -1hпоказывает краш, которыйlist-timersпрячет.
verify: journalctl -u foo.service --since -1h → краш, который прячет list-timers
ТАБЛИЦА РЕШЕНИЙ
| cron | таймер systemd | |
|---|---|---|
| Установка | одна строка crontab | два файла юнитов |
| Логи | локальная почта, обычно не прочитана | журнал, по юниту |
| Пропущенный запуск (машина выключена) | пропущен | одным запуском с Persistent=true |
| Зависимости / порядок | нет — ручками в скрипте | полные зависимости юнитов |
| Случайная задержка | вручную через $RANDOM | RandomizedDelaySec= |
| Тест / dry-run графика | нет | systemd-analyze calendar |
| Везде есть | да — контейнеры, BSD, эмбеддед | нужен systemd (PID 1) |
| Задачи пользователя без root | crontab -e | systemd --user юниты |
Правила на пальцах : доставить задачу на свою машину с systemd → таймер. Быстрое личное напоминание или чужой ящик → cron. Всё, где есть зависимости, повторы или необходимость знать, реально ли отработало → таймер, всегда. Классический паттерн — ночной rsync-бэкап под таймером с Persistent=true — гарантирует, что бэкап случится, даже если машина спала в назначенную минуту, чего cron попросту не умеет.
конец двух часов · ничего не скачано · всё локально