Настройка systemd timers: где ломается классический cron
Планировщик задач в Linux чаще всего сводится к строке в crontab: cron запускает команду по расписанию и на этом заканчивается. Он не знает, чем закончился прошлый запуск, что делать при сбое и был ли перезапуск машины в окно задачи. Поэтому вопрос «почему не запускаются задачи по расписанию» обычно начинается с ручного перебора системных журналов в поисках единственной упавшей строки.
Дальше — четыре типовых сбоя, которые всплывают на любом парке серверов. Ошибка уходит в никуда: без настроенного получателя вывод команды оседает в локальной почте root, куда месяцами никто не смотрит. Перезагрузка или недоступность узла в момент запуска означает, что задача просто не выполнится — cron не хранит состояние и не догоняет пропущенное. Между заданиями нет связей: выгрузка стартует раньше, чем завершилась подготовка данных, и оба процесса об этом не подозревают. Журналы разбросаны — часть в почте, часть в системных файлах, часть потеряна безвозвратно.
Настройка systemd timers снимает это на уровне архитектуры: таймер и полезная нагрузка описываются парой unit-файлов, а не одной строкой. Состояние запуска хранит сам systemd, результат предыдущего прогона виден по конкретному юниту, директива OnFailure= задаёт обработчик падения, а After= и Requires= выстраивают порядок между заданиями. Ключ Persistent=true возвращает пропущенный запуск сразу после включения машины, поэтому ночной перезапуск больше не «съедает» задачу.
Замена cron на systemd даёт одну точку правды: видно, что и когда запускалось, чем закончилось и что именно сломалось. Ниже разбираем, как переносится конкретное расписание и что проверить до отключения старых строк.
Что меняется после переноса задач: измеримый эффект
В cron единственный признак сбоя — строка в почте root или запись в syslog без статуса выхода. systemd ведёт каждую задачу как отдельный юнит со своим состоянием, поэтому доля потерянных запусков уходит в ноль: Persistent=true догоняет пропущенное окно после включения машины, а не теряет его молча. Уже на этом шаге видно, что задача либо отработала с кодом 0, либо оставила след в журнале — третьего состояния нет.
Зависимости между заданиями перестают быть договорённостью «подождать две минуты» внутри скрипта. Связки After= и Requires= дают явный порядок запуска: выгрузка не стартует, пока не завершилась подготовка данных, а падение одного шага не позволяет следующему работать по неполным данным. Повторный запуск при сбое настраивается штатно — Restart=on-failure с паузой между попытками и OnFailure= для оповещения, вместо самодельных циклов в bash.
Единый журнал запусков убирает поиск причины по разным логам. journalctl -u имя.timer за секунды отдаёт код выхода, вывод stderr и время старта, поэтому сбой обнаруживается не утром по жалобе, а в течение минут после окна. На практике время до обнаружения сбоя сокращается с нескольких часов до десятков минут.
Предсказуемость окна выполнения задаётся OnCalendar и RandomizedDelaySec: расписание читается как обычная строка, а разброс старта снимает одновременную нагрузку на диск и сеть. Дрейф, который копился у cron при долгих задачах, не возникает. В сумме число ручных вмешательств в месяц падает с восьми-десяти до одного-двух — обычно это разбор внешней причины, а не перезапуск задачи руками.
Варианты расписания: сравнение подходов и ограничений
Выбор расписания упирается не в синтаксис, а в гарантии, которые нужны задаче: зависимость от результата предыдущего запуска, поведение после простоя узла, единый журнал и разброс по времени старта.
У cron нет состояния между запусками — пропущенный из-за выключенной машины запуск просто теряется, а ошибка уходит письмом в никуда. У systemd timers есть календарные выражения, случайная задержка старта и добор пропущенного запуска, но нет распределённой блокировки: если задача должна отработать один раз на несколько узлов, планировщик её продублирует.
| Вариант | Когда подходит | Ограничения |
|---|---|---|
| Оставить текущий планировщик | Простые задачи без зависимостей и требований к журналу | Нет добора пропусков, связи с другими юнитами и состояния |
| Полный перевод на системный планировщик | Нужны зависимости, единый журнал, добор пропущенных запусков | Надо описать каждую задачу юнитом; на нескольких узлах возможно дублирование |
| Гибридная схема | Критичные задачи переводим, остальное работает как раньше | Два места правды — нужен сводный реестр расписаний |
| Внешний оркестратор заданий | Запуск распределённый, важен порядок и повтор при сбое | Отдельная система, которую нужно сопровождать и обновлять |
Гибридное расписание — рабочий компромисс на время перехода: переносим задачи по одной, сверяем журнал с прежним поведением и только после этого убираем строку из старого файла расписания. Так настройка systemd timers не превращается в разовый рискованный переезд всего узла.
Этапы внедрения: как мы переводим задачи на systemd
Перед заменой планировщика нужно увидеть полную картину: сколько заданий реально работает, какие зависят друг от друга и что произойдёт при пропуске окна запуска. Поэтому перевод идёт по фиксированному порядку — от инвентаризации до передачи в эксплуатацию, с проверкой на каждом шаге.
- Инвентаризация расписания задач. Выгружаем записи из системных и пользовательских таблиц планировщика, добавляем запуски из скриптов приложений и сверяем всё с логами. Часть заданий давно падает, часть дублируется, у части потерян владелец — это выясняется именно здесь.
- Схема зависимостей заданий. Строим граф: что идёт строго после чего, что можно параллелить, где пропуск окна допустим. Так фиксируются связки вида «бэкап после выгрузки», которые нельзя переносить по одному.
- Перенос описаний. Задание разделяется на «что запускаем» и «когда»: отдельный сервисный юнит и таймер. Расписание описывается календарным выражением, включается догон пропущенных окон после простоя — если задание это допускает.
- Настройка прав. Прописываем пользователя, рабочий каталог, переменные окружения и ограничения по ресурсам. Задание перестаёт зависеть от чьей-то сессии и уже не утащит за собой весь хост.
- Проверка на сбоях. Принудительные запуски, разбор логов, имитация перезагрузки и долгого простоя, поведение при недоступной базе. Нужно убедиться, что задание либо выполняется, либо явно сообщает о сбое.
- Передача расписания в эксплуатацию. Отдаём карту заданий, журналирование и оповещения о неуспешных запусках, инструкцию по добавлению и отключению задач. Расписание видно в одном месте и меняется без правки чужих скриптов.
Конфигурации, схемы и регламенты, которые остаются у вас
После переноса расписаний на systemd-таймеры у команды остаётся не только рабочая конфигурация, но и комплект документов, по которому её можно сопровождать без автора. Без него через полгода никто не вспомнит, почему задание стартует в 03:17, от чьего имени и что делать, если оно упало.
- Описания заданий и юнитов — что делает каждый таймер, какой скрипт вызывает, какие параметры получает и чем календарное выражение отличается от прежней строки cron.
- Схема зависимостей заданий — что обязано завершиться раньше: монтирование хранилища, поднятие базы, выгрузка из внешней системы.
- Правила доступа — от чьего имени идут запуски, какие группы и права нужны, где лежат учётные данные, чем ограничены возможности юнита.
- Документация по расписанию задач — сводная таблица «задание — периодичность — окно выполнения — владелец», включая редкие и календарные запуски.
- Регламент реагирования на сбой — как приходит уведомление, кто и в какой срок смотрит журнал, когда задание перезапускается автоматически, а когда блокируется до разбора.
- Порядок изменения расписания — как добавить или отключить задание, чтобы правка не задела соседние таймеры и не создала двойной запуск.
- Контрольные проверки — команды, которыми дежурный подтверждает: таймер активен, следующий запуск запланирован, прошлый завершился успешно.
Материалы оформляем рядом с конфигурацией, чтобы описание и юнит менялись одним изменением, а не жили в разных местах. Сопровождающая команда получает точку входа: развернуть расписание с нуля, найти владельца задания, разобрать сбой по журналу.
Кейс: как задачи по расписанию перестали теряться
Что было до
Ночные регламентные задачи — выгрузки, пересчёт витрин, обслуживание индексов — жили на классическом cron в трёх окружениях. Запуск при этом никто не отслеживал: если машина перезагружалась в момент окна, задача пропускалась молча, и утренние отчёты собирались на устаревших данных. За месяц терялось 6-9 % ночных запусков, поиск причины сбоя занимал в среднем 45 минут, а дежурный перезапускал задачи руками до 15 раз в неделю.
Что изменили
Каждую задачу описали парой юнитов — таймер и сервис — и раскатали из общего шаблона через Ansible, чтобы расписание версионировалось вместе с конфигурацией, а не жило в чужой таблице на сервере. Ключевым стал Persistent=true: пропущенное из-за выключенной машины окно догоняется сразу после включения, но при штатной работе дублирования нет. Связка OnFailure= с юнитом-обработчиком отправляет уведомление в дежурный канал, а вывод задачи пишется в системный журнал с привязкой к юниту — историю прогонов видно без отдельного агента.
Что стало
| Метрика | До | После |
|---|---|---|
| Пропущенные ночные запуски | 6-9 % | 0 |
| Поиск причины сбоя | 45 минут | 2-3 минуты |
| Ручные перезапуски | до 15 в неделю | единичные случаи |
Пропуски закрылись механикой, а не призывом «смотреть внимательнее»: пропущенный запуск догоняется, упавший сразу виден в журнале и в уведомлении. Диагностика свелась к фильтру по имени юнита, поэтому инцидент разбирается до начала рабочего дня. Переход обратим: задачи остаются отдельными юнитами, и любую из них можно вернуть на прежнее расписание, не задевая остальные.
Чем отличается запуск по расписанию от постоянного сервиса
Юнит systemd — это единица управления, и его тип определяет жизненный цикл: один процесс держит состояние постоянно, другой выполняется и завершается. Отсюда и разница между сервисом и таймером — сервис отвечает на вопрос «что должно работать», таймер — «когда запустить то, что завершается». При настройке systemd timers это разделение важнее синтаксиса расписания.
Постоянный сервис ведёт процесс: следит за ним, поднимает после падения, собирает вывод в журнал. Таймер не держит процесс в памяти — он активирует привязанный unit по календарному расписанию или по интервалу после загрузки. Саму работу оформляют oneshot-сервисом: код не крутится в фоне, а вызывается ровно на время выполнения.
Поведение после перезагрузки — ключевое отличие от старого способа запуска по минутам. С включённым догоном пропущенных запусков система выполняет задание сразу после старта, если машина в этот момент была выключена; без него пропуск теряется молча. У постоянного сервиса перезагрузка — это рестарт процесса, и состояние, не сохранённое на диске, восстанавливать приходится вручную.
Ещё два параметра, которых нет у классического cron: окно случайного разброса и точность запуска. Они размазывают нагрузку, когда десятки однотипных задач стартуют в одну и ту же минуту.
Выбор простой: если процесс живёт и держит соединение, кэш или очередь — это сервис; если задача конечна (выгрузка, бэкап, ротация логов) — таймер плюс oneshot. Смешанный вариант тоже рабочий: постоянный сервис, который сам активирует таймер по своему событию.
Технические сомнения перед переносом расписаний
Перенос расписаний не требует останавливать текущие задания: cron и таймеры работают параллельно, пока мы переводим скрипты по одному. Основная работа — не переписать логику, а воспроизвести окружение запуска: cron поднимает задание с урезанным PATH и без рабочего каталога, поэтому в unit явно задаём Environment=, WorkingDirectory= и User=. Пока строка запуска даёт тот же результат в обоих планировщиках, старое задание остаётся включённым.
Скрипты писались под cron — их придётся переписывать?
Нет. Скрипт вызывается как есть, меняется только обвязка. Частая причина «после переноса сломалось» — не логика, а другой рабочий каталог и переменные окружения, поэтому первым переводим низкорисковое задание и сверяем результат с прежним.
Задания запускаются от разных пользователей — как это переносится?
Роли описываются в unit: User= и Group=, плюс права на каталоги вывода и журналов. Там же решается случай «скрипт писал в домашний каталог» — путь задаётся явно, а не наследуется от того, кто запустил задание.
Как отлаживать ночные сбои, если команда спит?
Каждый запуск попадает в журнал с отметкой времени, кодом возврата и выводом — смотрим историю по конкретному заданию, а не гадаем. Режим догона выполняет пропущенный запуск после включения сервера, так что ночной сбой не превращается в день без данных.
А если задание зависит от сети или смонтированных ресурсов?
Зависимости описываются явно, поэтому запуск не стартует раньше, чем поднялась сеть или подключилось хранилище. Для заданий, бьющих в один ресурс, добавляем разброс по времени — пик нагрузки на сервер размазывается.
Обсудим задачу: что нужно знать перед стартом работ
Чтобы оценка переноса расписания была точной, а не «по аналогии», нам достаточно инвентаризации текущих задач и понимания окружения. Если часть данных уже под рукой, разбор идёт быстрее, а спорные места обсуждаем предметно.
- где сейчас живут расписания: crontab пользователей, файлы в
/etc/cron.dи/etc/cron.*, задачи во внутренних планировщиках приложений; - перечень задач: что запускается, с какой периодичностью, сколько длится обычно и в пике;
- зависимости и порядок: что обязано завершиться до старта следующего, где нужны блокировки от параллельного запуска;
- окружение: дистрибутивы, права на создание unit-файлов, запуск под непривилегированным пользователем, доступ к секретам;
- логи и оповещения: куда пишется вывод сейчас, кто его читает, куда должны уходить сообщения об ошибках;
- поведение при пропуске: если узел был выключен в момент запуска — догонять задачу или пропустить;
- окна для работ: когда перенос не задевает бизнес-процессы.
Этого хватает, чтобы вернуться с планом внедрения расписания и разметкой unit-файлов и таймеров. Наши инженеры отдельно разберут частные случаи — пересечения по времени, запуск от разных пользователей, перенос без потери пропущенных запусков — и предложат схему с откатом на прежнее расписание.
Обсудим задачу настройки расписания: опишите перечисленное, и мы вернёмся с оценкой работ и планом.







