Сценарий, отработавший вручную без единой запинки, отказывается работать по расписанию. Задание в таблице планировщика есть, время наступает, а результата нет. Разочарование знакомо каждому второму администратору, и у него есть точное объяснение: планировщик не запускает сценарий в том окружении, в котором его запускали руками. Диагностика этой разницы занимает двадцать минут, если знать, куда смотреть. Статья разбирает различия окружений, первые проверки, пути как причину номер один, переменные, ловушки формата и процедуру отладки.
Разница окружений ручного запуска и планировщика
Ручной запуск происходит в интерактивной оболочке: она прочитала профиль, настроила переменные, развернула псевдонимы и функции, привела пути к привычному виду. Всё это невидимо для пользователя, и он честно считает, что так выглядит "просто запуск".
Планировщик запускает задание в минимальном окружении: скромный набор путей поиска команд, домашний каталог, стандартная оболочка из семейства простых, и никакого чтения профилей пользователя. Сценарий, опирающийся на переменные, псевдонимы и расширения интерактивной оболочки, встречает мир без своих подпорок и падает на первом шаге.
Список различий короткий, но каждый пункт в нём стрелял хотя бы однажды: пути поиска, переменные с секретами и настройками, интерпретатор по умолчанию, отсутствие чтения профилей, рабочий каталог, кодировка и язык сообщений. Устранение любого различия это либо явная настройка в таблице заданий, либо самодостаточный сценарий.
Идеал, к которому сходится практика: задание планировщика не зависит от окружения вовсе. Абсолютные пути, явные переменные, самодостаточный сценарий. Такие задания переезжают между машинами копированием строки, и это лучший тест на зрелость: если для переноса задания нужно вспоминать, что ещё настроить в окружении, задание ещё не готово.
Рядом с классическим планировщиком живут таймеры менеджера системы, и часть различий окружения там снимается своей конструкцией: переменные задаются прямо в описании юнита, рабочий каталог объявлен явно, зависимости перечислены по именам. Выбор механизма это отдельный разговор, но для новых заданий в хозяйстве с менеджером системы стоит помнить, что часть проблем этой статьи там решается не дисциплиной, а устройством.
Первые проверки журналы планировщика и захват вывода
Диагностика начинается с вопроса, запускался ли сценарий вообще. Журнал планировщика отвечает прямо: в системном журнале или собственном файле планировщик фиксирует попытки запуска заданий с временем. Отсутствие попытки и провал попытки это разные проблемы, и их лечение начинается с разных концов.
Вторая проверка это вывод задания. По умолчанию планировщик отправляет вывод почтой локальному пользователю, и непрочитанные письма часто содержат точную причину провала. Надёжнее захватить вывод явно: перенаправление стандартного потока и потока ошибок в файл рядом со сценарием сохраняет всё, что задание сказало перед остановкой:
* * * * * /opt/scripts/nightly.sh >> /var/log/nightly.log 2>&1
Третья проверка это факт выполнения: файл-метка в конце сценария, строка в журнале, любая тропа, которая остаётся после успешно дошедшего до конца кода. Различие "запустился и упал" от "не запускался" определяется именно такой тропой, и без неё диагностика гадает между двумя разными вселенными.
Четвёртая проверка очевидностей, которую пропускают из уважения к себе: исполняемость файла, права на чтение интерпретатором, существование всех путей, работоспособность службы планировщика. Проверка занимает минуту и снимает самый неловкий класс причин, встречающийся, к сожалению, чаще всего в самый напряжённый день.
Пути как причина номер один
Минимальный набор путей поиска команд в планировщике не включает локальные каталоги программ и места ручной установки. Команда, найденная руками по расширенным путям, планировщиком не находится, и сценарий останавливается на первой же внешней утилите:
/usr/bin/env: python3: No such file or directory
Лечение многослойное. Первый слой: абсолютные пути к командам внутри сценария. Второй слой: явная настройка путей в начале сценария, добавляющая нужные каталоги. Третий слой: строка путей в самой таблице заданий, действующая на все её задания. Выбор слоя это выбор между точностью и удобством, а строгий вариант первый: сценарий, вызывающий утилиты по абсолютным путям, не зависит ни от чего.
Отдельная разновидность путевой проблемы: рабочие каталоги. Задание стартует в домашнем каталоге пользователя, а не там, где лежит сценарий, и обращения к относительным путям бьют мимо. Лечение однослойное и безоговорочное: сценарий первым делом переходит в собственный каталог по абсолютному пути, либо все относительные обращения заменяются абсолютными.
Проверка путей входит в процедуру отладки обязательным шагом: выгрузка окружения задания в файл одной строкой диагностики показывает и пути, и переменные, и интерпретатор, и всё остальное. Один взгляд на выгрузку снимает половину вопросов, которые без неё задаются вслепую.
Отдельная разновидность путевой проблемы живёт в системах с несколькими версиями утилит: под рукой работает свежая версия из локального каталога, а планировщик находит старую из базовых путей. Симптом коварен: сценарий стартует, но работает странно, и подозрение падает на что угодно, кроме путей. Выгрузка окружения показывает обе версии рядом, и абсолютный путь к нужной снимает двусмысленность навсегда.
Сценарии с переменными окружения и секретами
Второй по частоте класс причин: сценарий рассчитывает на переменные, которых в минимальном окружении нет. Пароли и адреса из окружения интерактивной сессии, настройки из профиля, параметры, экспортированные когда-то и забытые. В планировщике сценарий встречает пустоту там, где ждал значения, и провал происходит в самой чувствительной точке.
Лечение начинается с честного списка: какие переменные нужны сценарию, откуда они брались, где их место в запланированном запуске. Секреты не переезжают в таблицу заданий открытым текстом из принципа: для них существует отдельное хранилище с правами, и сценарий читает их оттуда сам.
Настройки без секретов прописываются в начале сценария явно либо выносятся в отдельный файл настроек, который сценарий читает по абсолютному пути. Такой файл переезжает вместе со сценарием, и задание остаётся самодостаточным в смысле конфигурации.
Тонкий нюанс про интерпретатор: таблица заданий по умолчанию использует простую оболочку, и сценарии, написанные под расширенную, ведут себя по-разному. Решение двоякое: строка оболочки в таблице заданий, меняющая интерпретатор для всех строк, либо строка вызова интерпретатора в самом задании с явным путём. Второе надёжнее: сценарий сам объявляет, кем он должен исполняться.
Кодировка и язык сообщений это тихий различитель: интерактивная сессия может жить с одними настройками языка, а минимальное окружение с другими, и сценарии, разбирающие вывод утилит по словам, ломаются на ровном месте. Явная установка языка в начале сценария закрывает класс, и разбор вывода становится предсказуемым на любой машине парка.
Синтаксис таблицы заданий и скрытые ловушки формата
Формат строки задания кажется примитивным, и примитивность обманчива. Первая ловушка: процент в строке команды. Планировщик трактует его как перевод строки, и команда с форматной строкой внутри разваливается на части в неожиданном месте. Экранирование решает вопрос, но знать о нём надо до третьего часа диагностики, а не после.
Вторая ловушка: концы строк из другого мира. Таблица, отредактированная в редакторе другой платформы, несёт невидимые символы возврата каретки, и планировщик не узнаёт строку с хвостом. Симптом коварен: строка выглядит идеально, а не работает. Лечение в проверке байтов строки и настройке редактора.
Третья ловушка: отсутствие пустой строки в конце таблицы. Часть планировщиков игнорирует последнюю строку без завершающего перевода, и задание, написанное последним, не выполняется. Вечная мелочь, вечный класс вчерашних настроек, не работающих сегодня.
Четвёртая ловушка: минуты и часы в обратном порядке, звёздочки в неправильных полях, спешка в пятницу вечером. Формат проверяется чтением вслух и табличкой полей рядом, а свежие задания проверяются минутным интервалом до того, как уйти домой.
Пятая ловушка: списки допуска. Пользователь может быть вне списка разрешённых на запуск, и таблица тогда не работает вовсе. Проверка списков входит в очевидности первой пятиминутки.
Диагностика экспериментом и минимальное окружение
Окончательный приём диагностики это воспроизвести окружение планировщика руками. Утилита запуска с очищенным окружением даёт команде мир планировщика без ожидания расписания:
env -i HOME=/root SHELL=/bin/sh PATH=/usr/bin:/bin /opt/scripts/nightly.sh
Провал в минимальном окружении это точная копия провала по расписанию, и дальше отладка идёт привычными средствами: контрольные выводы, разбор по шагам, сравнение с рабочим окружением по выгрузке переменных. Диагностика перестаёт быть ожиданием и становится управляемым экспериментом.
Обратный приём работает так же: выгрузка окружения рабочего задания в файл показывает, на чём сценарий держался. Разница двух выгрузок, интерактивной и минимальной, это список того, что нужно добавить в задание, и обычно список короткий: пара путей, пара переменных, интерпретатор.
Для заданий со сложной судьбой существует приём-обёртка: короткий сценарий-обёртка настраивает окружение, читает настройки, переходит в нужный каталог и вызывает основной сценарий. Вся зависимость от окружения собирается в одном месте, а таблица заданий остаётся простой строкой вызова обёртки по абсолютному пути.
Минута дисциплины завершает эксперимент: найденная причина фиксируется в комментарии сценария. Через полгода тот же сценарий в новых руках спасается этой строкой от повторения всей истории.
Хронологическая ловушка дополняет набор: задание, проверенное летом, ломается зимой из-за перехода времени, а задание, рассчитанное на секунду в расписании, зависит от точности хода часов. Минимизация зависимостей от локального времени и контроль после каждого перехода входят в гигиену расписаний, а задания с жёсткой привязкой к моменту переписываются на события.
Рабочая процедура отладки задания планировщика
Процедура, собирающая всё в порядок действий, выглядит так:
- Проверить журнал планировщика: была ли попытка запуска вообще;
- Захватить вывод задания в файл и прочитать первую ошибку;
- Проверить очевидности: права, службу, списки допуска, формат строки;
- Выгрузить окружение задания и сравнить с рабочим;
- Устранить различия: абсолютные пути, явные переменные, интерпретатор;
- Повторить запуск минимальным окружением и вернуть задание в строй.
За процедурой стоит принцип экономики: каждая минута, вложенная в самодостаточность задания, экономит час диагностики в будущем, и задание, доведённое до состояния "переносится одной строкой", больше никогда не попадает в этот сценарий.
Итоговое суждение по классу проблем спокойное. Неработающее по расписанию задание это почти никогда не загадка, а разница окружений, и разница эта видна, измерима и устраняема за один раз. Планировщик честен: он делает ровно то, что написано, в ровно том окружении, которое имеет. Починить написанное и окружение это работа администратора, и она ровно того размера, который описан в этой статье.
Отдельная привычка для команды: тестовое задание-проба с минутным интервалом живёт в таблице постоянно и пишет метку в протокол. Любое изменение планировщика, обновление системы или правки окружения видны по пробе за минуту, и класс проблем "задания вообще перестали работать" ловится до первых пострадавших настоящих заданий.
Мелкая привычка с большим эффектом: свежие задания получать контрольной строкой в файл протокола. Пара строк вывода при каждом старте закрывает вопрос "запускался ли" навсегда и даёт материал для быстрой ревизии недель спустя.