Таймеры systemd
Таймер systemd (.timer-юнит) активирует сопутствующий .service-юнит по расписанию. OnCalendar задаёт события по настенным часам; OnBootSec/OnUnitActiveSec — монотонные интервалы. Persistent=true наверстывает пропущенный запуск. systemctl list-timers показывает все таймеры.
У cron есть известная слабость: когда задача запускается, она ничего не пишет в журнал, не имеет статуса сервиса, и при сбое ты узнаёшь об этом из забытого письма или сломанной резервной копии. Таймеры systemd решают эту проблему, разделяя расписание и работу: .timer-юнит определяет когда, .service-юнит — что, а журнал захватывает всё, что пишет сервис. Можно проверить последний запуск через systemctl status, увидеть активность таймера через systemctl list-timers и получить оповещения о сбоях теми же механизмами, что и для обычных сервисов. Цена — немного больше шаблонного кода: два файла вместо одной строки. Для всего важного — оно того стоит.
После этого урока ты сможешь написать пару .timer + .service, различить OnCalendar (настенные часы) и OnBootSec/OnUnitActiveSec (монотонные таймеры), включить Persistent=true для наверстывания пропущенных запусков и использовать systemctl list-timers для проверки текущего расписания.
Юнит таймера управляет юнитом сервиса. По соглашению, foo.timer активирует foo.service. Оба файла лежат в /etc/systemd/system/ для локальных установок или /lib/systemd/system/ для пакетов. Минимальная пара выглядит так:
# /etc/systemd/system/db-backup.service
[Unit]
Description=Database backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-db.sh
# Нет секции [Install] — таймер управляет запуском, а не WantedBy# /etc/systemd/system/db-backup.timer
[Unit]
Description=Daily database backup timer
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.targetСервис имеет Type=oneshot — он выполняется до завершения и выходит; systemd не ожидает, что он останется живым. Таймер имеет секцию [Install]; сервис — нет. Включить и запустить:
systemctl daemon-reload
systemctl enable --now db-backup.timerOnCalendar задаёт расписание по настенным часам с выражениями календарных событий. Синтаксис более читаемый, чем пять полей cron, и systemd его валидирует:
# Проверить выражение календаря до деплоя:
systemd-analyze calendar 'Mon-Fri *-*-* 08:00:00'
# Output: Next elapse: Mon 2024-03-18 08:00:00 UTC
# Типовые календарные события:
OnCalendar=daily # то же, что *-*-* 00:00:00
OnCalendar=weekly # Mon *-*-* 00:00:00
OnCalendar=monthly # *-*-01 00:00:00
OnCalendar=*-*-* 02:30:00 # каждый день в 02:30
OnCalendar=Mon-Fri *-*-* 09:00:00 # по будням в 09:00
OnCalendar=*:0/15 # каждые 15 минут
OnCalendar=*-*-* *:00:00 # в начале каждого часаsystemd-analyze calendar незаменима: вставь любое выражение — она покажет следующие пять активаций. Делай это до включения таймера: поймать неверное выражение до того, как оно пропустит еженедельный запуск, экономит неделю ожидания.
Монотонные таймеры измеряют прошедшее время, а не время по часам. Они отсчитываются от загрузки или от последней активации:
[Timer]
# Сработать через 5 минут после загрузки системы — полезно для пост-загрузочной настройки:
OnBootSec=5min
# Сработать через 1 час после последнего завершения этого сервиса:
OnUnitActiveSec=1h
# Комбинация: через 10 минут после загрузки, затем каждый час:
OnBootSec=10min
OnUnitActiveSec=1hКлючевое отличие от OnCalendar: монотонные таймеры не выравниваются по границам настенного времени. Если сервис занимает 20 минут, а OnUnitActiveSec=1h, следующая активация — через 1 час после завершения сервиса, а не в начале часа. Это намеренно для задач, которые не должны перекрываться, и где точное время не важно.
# Принимаемые единицы времени:
# 5min, 1h, 30s, 2d, 1weekPersistent=true наверстывает пропущенный запуск OnCalendar. Без этого, если машина была выключена в момент срабатывания таймера, запуск пропускается до следующего запланированного времени. С Persistent=true systemd запускает сервис сразу после следующей загрузки, если последняя активация старше запланированного интервала:
[Timer]
OnCalendar=daily
Persistent=true
# Если машина была выключена в полночь, запустит резервное копирование вскоре после загрузкиЭто аналог anacron в systemd для систем, которые не всегда включены. Включай для любой задачи, где «я перезагружался в нужное время» — недопустимое оправдание пропущенного бэкапа, то есть почти всегда.
systemctl list-timers показывает все активные таймеры и время следующей активации. Это первое, что проверяют, когда таймер не работает:
# Список всех активных таймеров:
systemctl list-timers
# NEXT LEFT LAST PASSED UNIT ACTIVATES
# Mon 2024-03-18 02:30:00 UTC 14h left Sun 2024-03-17 02:30:01 UTC 9h ago db-backup.timer db-backup.service
# Mon 2024-03-18 09:00:00 UTC 21h left Sun 2024-03-17 09:00:00 UTC 3h ago apt-daily.timer apt-daily.service
# Полный статус конкретного таймера:
systemctl status db-backup.timer
# Последний запуск активируемого сервиса:
systemctl status db-backup.service
journalctl -u db-backup.service -b --since "yesterday"Столбец LAST показывает, когда последний раз срабатывал; PASSED — как давно. Пустой LAST означает, что никогда не запускался — либо только что запустили, либо Persistent=true ещё не сработал для наверстывания.
Создай таймер для очистки образов Docker каждое воскресенье в 03:00.
# Шаг 1: написать юнит сервиса
cat > /etc/systemd/system/docker-prune.service << 'EOF'
[Unit]
Description=Prune unused Docker images
[Service]
Type=oneshot
ExecStart=/usr/bin/docker image prune -af --filter "until=168h"
EOF
# Шаг 2: написать юнит таймера
cat > /etc/systemd/system/docker-prune.timer << 'EOF'
[Unit]
Description=Weekly Docker image prune
[Timer]
OnCalendar=Sun *-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
EOF
# Шаг 3: перезагрузить, включить, запустить
systemctl daemon-reload
systemctl enable --now docker-prune.timer
# Шаг 4: проверить активность таймера
systemctl list-timers docker-prune.timer
# NEXT: Sun 2024-03-24 03:00:00 UTC 6d left
# Шаг 5: протестировать сервис без ожидания таймера:
systemctl start docker-prune.service
journalctl -u docker-prune.service -b -n 20
# Mar 18 12:34:05 host docker[5821]: Deleted: sha256:abc123...
# Mar 18 12:34:06 host docker[5821]: Total reclaimed space: 2.3GB
# Mar 18 12:34:06 host systemd[1]: docker-prune.service: Succeeded.Это канонический рабочий процесс: написать сервис, написать таймер, перезагрузить, включить, затем запустить сервис вручную один раз, чтобы убедиться в его работоспособности до следующего запланированного окна.
▸Почему это работает
AccuracySec и RandomizedDelaySec. По умолчанию таймеры systemd срабатывают в пределах минутного окна точности (AccuracySec=1min). Это намеренно: systemd может объединять несколько таймеров, срабатывающих близко по времени, чтобы уменьшить число пробуждений. Для точного времени установи AccuracySec=1s. Обратное — RandomizedDelaySec=: добавление случайной задержки предотвращает проблему «стада»: сотни машин с OnCalendar=daily одновременно обращаются к хранилищу резервных копий ровно в полночь. RandomizedDelaySec=1h распределяет их по часу.
▸Частая ошибка
Забыть systemctl daemon-reload. После написания или редактирования .timer или .service файла systemd не замечает изменений до перезагрузки определений юнитов. Пропуск daemon-reload приводит к тому, что systemctl либо использует старое определение, либо сообщает «Unit not found». Это самая частая причина, по которой новый таймер не делает ничего. Всегда: написать файл → daemon-reload → enable --now.
Юнит таймера имеет `OnCalendar=daily` и `Persistent=true`. Машина была выключена 3 дня. Что произойдёт при загрузке?
Таймер systemd — это .timer-юнит, активирующий сопутствующий .service-юнит. OnCalendar принимает удобочитаемые выражения календарных событий (*-*-* 02:30:00, Mon-Fri *-*-* 09:00:00), проверяемые через systemd-analyze calendar. OnBootSec и OnUnitActiveSec монотонны: они измеряют прошедшее время с загрузки или с последнего запуска, а не по границам настенного времени. Persistent=true запускает наверстывающий запуск при загрузке, если таймер на основе календаря пропустил своё окно. Включай таймер через systemctl enable --now foo.timer; всегда запускай systemctl daemon-reload после написания файлов юнитов. systemctl list-timers показывает следующую активацию, последнюю активацию и сервис, управляемый каждым таймером. Весь вывод попадает в журнал — запрашивай через journalctl -u foo.service.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.