Cron тихо пропустил запуск, и никто не заметил. Настраиваем таймер systemd: логи, догон после простоя и понятное время следующего запуска.
Знакомая история: ночной бэкап сидит в cron, все ему доверяют, а через три недели выясняется, что с перезагрузки сервера в 03:29 он ни разу не запускался. Cron работает, но почти ничего не рассказывает. Таймер systemd делает ту же работу и добавляет лог, понятное время следующего запуска и догон после простоя. Ниже рабочая пара файлов и то, что стоит проверить после включения.
Коротко. Таймер - это два маленьких файла:
имя.serviceговорит, что запускать,имя.timer- когда. Включается командойsudo systemctl enable --now имя.timer.systemctl list-timersпоказывает следующий запуск,journalctl -u имя.service- что выводила задача.Persistent=trueдогоняет пропущенный запуск после простоя,RandomizedDelaySecразмазывает нагрузку. Для разовой мелочи cron по-прежнему подходит.
Cron - планировщик из семидесятых: одна строка в таблице, пять полей времени и команда. Он короткий и всем знаком, поэтому им продолжают пользоваться. Проблемы начинаются, когда задача важная:
>> /var/log/job.log 2>&1 и надеетесь.anacron, но это отдельная программа).PATH и без ваших настроек оболочки, поэтому скрипт, который работает в терминале, ночью падает с «command not found».Таймер systemd закрывает каждый пункт без дополнительных программ: systemd на вашем сервере и так работает первым процессом.
Сначала файл .service. Он описывает одну задачу, а не постоянно работающую программу: запустить скрипт, дождаться выхода, готово. Именно это говорит Type=oneshot. Кладём файл в /etc/systemd/system/:
В ExecStart пишите полный путь: systemd не ищет программу по PATH, как это делает оболочка. Сделайте скрипт исполняемым: sudo chmod +x /usr/local/bin/backup.sh. Всё, что скрипт выводит через echo, автоматически попадает в журнал, перенаправлять вывод в файл не нужно.
Файл .timer должен называться так же, как служба (backup.timer запускает backup.service). Этот запускает задачу каждую ночь в 03:30:
Что делает каждая строка:
OnCalendar - расписание в виде день_недели год-месяц-день часы:минуты:секунды. Звёздочка означает «любое», так что *-*-* 03:30:00 - каждый день в 03:30. Время берётся по часовому поясу сервера.RandomizedDelaySec=300 сдвигает каждый запуск на случайную задержку до пяти минут. Если серверов много, они не ударят по вашему хранилищу в одну и ту же секунду.Persistent=true запоминает время последнего запуска на диске. Если в 03:30 сервер был выключен, задача запустится сразу после загрузки.WantedBy=timers.target заставляет включённый таймер подниматься после каждой перезагрузки.Не уверены, что расписание означает то, что вы думаете? Попросите systemd перевести его до включения чего-либо:
Этого у cron не было никогда: вы видите настоящий момент следующего запуска, а не пять полей. Работают и короткие имена: daily, weekly, hourly.
Сначала проверим файлы на опечатки. Включаем именно .timer, а не службу:
sudo systemd-analyze verify /etc/systemd/system/backup.service /etc/systemd/system/backup.timerЕсли всё в порядке, команда ничего не выведет. Теперь перечитаем файлы и включим таймер:
sudo systemctl daemon-reload && sudo systemctl enable --now backup.timerСпросим про расписание:
Одна строка отвечает на вопрос, на который cron ответить не мог: следующий запуск завтра в 03:31:44. Лишние полторы минуты - случайная задержка от RandomizedDelaySec. Колонка LAST пуста, потому что задача ещё не запускалась.
Не ждите ночи, чтобы найти опечатку. Запустите службу напрямую, расписание от этого не изменится:
Это тот лог, которого у cron не было: время старта, всё, что напечатал скрипт, и вердикт. Deactivated successfully значит код выхода 0. Если скрипт упадёт, на том же месте появится Failed with result 'exit-code', а systemctl status backup.service покажет код выхода.
cron | таймер systemd | |
|---|---|---|
Настройка | одна строка в | два файла и одна команда |
Лог | вывод перенаправляете сами | в журнале по умолчанию |
Пропущенный запуск после простоя | теряется | запускается после загрузки при |
Время следующего запуска | считаете сами |
|
Окружение | минимальный | задаётся явно через |
Наложение запусков | стартует вторая копия, помогает | по умолчанию работающий юнит повторно не запускается |
Работает без systemd | да | нет |
Наше правило: если задача важная (бэкап, продление сертификата, очистка, которая освобождает диск), берите таймер. Если это разовая мелочь вроде «дёргать адрес раз в пять минут», cron подойдёт и напишется быстрее. Многие системные задачи самой Ubuntu уже переехали на таймеры: на свежем сервере systemctl list-timers покажет обновления apt, ротацию логов и продление сертификатов.
Отключите таймер, удалите файлы и перечитайте конфигурацию:
sudo systemctl disable --now backup.timer && sudo rm /etc/systemd/system/backup.{service,timer} && sudo systemctl daemon-reloadДля задач, которые вам важны, да: он ведёт лог, показывает следующий запуск и умеет догонять после простоя. Для быстрой разовой мелочи cron проще.
Напишите OnCalendar=*:0/15 для «каждую четверть часа по часам» или OnBootSec=5min вместе с OnUnitActiveSec=15min для «через 15 минут после предыдущего запуска».
Проверьте, что включён именно .timer, а не .service, что systemctl list-timers его показывает и что в journalctl -u имя.service нет ошибок. Сверьте и часовой пояс сервера: OnCalendar использует его.
Сохраняет время последнего запуска на диск. Если запланированный момент прошёл, пока сервер был выключен, задача запустится сразу после следующей загрузки.
Можно, они работают рядом. Переносите те задачи, где нужны логи и догон, остальные оставьте.
.service (что запускать, Type=oneshot) плюс .timer (когда), включается через systemctl enable --now имя.timer.systemd-analyze calendar показывает, когда расписание реально сработает, systemctl list-timers - что запланировано.journalctl -u имя.service, перенаправлять в файл не нужно.Persistent=true спасает запуск, пропущенный во время простоя, RandomizedDelaySec размазывает нагрузку по серверам.