Налаштування 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-файлів і таймерів.
Наші інженери окремо розберуть часткові випадки — перетини за часом, запуск від різних користувачів, перенесення без втрати пропущених запусків — і запропонують схему з відкатом на попередній розклад.
Обговоримо завдання налаштування розкладу: опишіть перелічене, і ми повернемося з оцінкою робіт і планом.







