Cron в Linux: полное руководство для админов + скрытые проблемы

Все, кто администрирует Linux, рано или поздно сталкивается с cron — стандартным планировщиком задач. Но если настроить его «на скорую руку», можно обнаружить неприятные сюрпризы:

  • Скрипт не запустился, тк cron работает в другом окружении
  • Сервер лёг от нагрузки, потому что 100 задач стартовали одновременно
  • Вы не узнали об ошибке из-за того, что вывод скрипта попал в /dev/null

В этой статье разбор не только основы работы с cron, но и:

  • Продвинутые форматы расписания — как задавать сложные интервалы и комбинировать условия
  • Типичные подводные камни — работа с переменными окружения, логирование, управление параллельным выполнением
  • Альтернативы для сложных сценариев — когда cron уже недостаточно и стоит обратить внимание на systemd.timer

А также, дополнительная информация:

  • Как избежать «падений» из-за наложения задач
  • Когда cron — хороший выбор, а когда лучше использовать другие инструменты

Статья будет полезна как начинающим администраторам, так и тем, кто хочет глубже разобраться в автоматизации задач в Linux.

Что такое cron

Cron — классический планировщик задач в Unix-подобных операционных системах, позволяющий автоматизировать выполнение команд и скриптов по расписанию. Позволяет запускать команды/скрипты в определенное время или с определенной периодичностью.

Состоит cron из нескольких ключевых компонентов:

  • Демон crond — фоновый процесс, который запускается при старте системы и работает постоянно
  • Таблицы cron (crontab) — файлы с расписанием задач
  • Журнал выполнения — обычно записывается в syslog или отдельные логи

Демон cron просыпается каждую минуту, проверяет crontab и выполняет задачи, которые должны запуститься в текущую минуту.

Синтаксис:

Что это значит? Разберём несколько примеров:

  1. 0 * * * * — каждый час в начале часа
  2. 0 0 * * * — каждый день в полночь
  3. 0 0 * * 0 — каждое воскресенье в полночь
  4. */15 * * * * — каждые 15 минут
  5. 0 4 1 * * — первое число каждого месяца в 4:00 утра

Также, в cron существуют «специальные строки» для часто используемых интервалов, частота которых предопределена:

  • @yearly или @annually — один раз в год (0 0 1 1 *)
  • @monthly — один раз в месяц (0 0 1 * *)
  • @weekly — один раз в неделю (0 0 * * 0)
  • @daily или @midnight — один раз в день (0 0 * * *)
  • @hourly — один раз в час (0 * * * *)
  • @reboot — при запуске системы

Следующим важным аспектом являются переменные окружения. Cron выполняет команды в минимальном окружении:

  • PATH — обычно очень ограничен (/usr/bin:/bin)
  • SHELL — обычно /bin/sh. Рекомендуется для сложных сценариев (поддержка массивов, функций)
  • HOME — домашний каталог пользователя. Подходит для задач, работающих с пользовательскими файлами
  • MAILTO — email для отправки результатов (можно использовать несколько, через запятую). Для отправки в syslog использовать: MAILTO=syslog

Крайне рекомендуется всегда указывать полные пути к командам и файлам в заданиях.

Почему рекомендуется указывать полные пути

Всегда указывать полные пути к командам и файлам в cron-заданиях связана с особенностями работы и потенциальными проблемами, которые могут (но необязательно возникнут) при их игнорировании. Постараюсь учесть все возможные:

1. Ограниченное окружение cron

Когда cron запускает задание, оно выполняется в минимальном окружении, которое сильно отличается от вашего обычного пользовательского окружения:

  • Очень ограниченная переменная PATH (обычно только /usr/bin:/bin)
  • Отсутствуют многие пользовательские переменные окружения
  • Нет .bashrc.profile и других конфигурационных файлов

Пример проблемы:

* * * * * myscript.sh  # Может не найти команду

В то время, как:

* * * * * /home/user/scripts/myscript.sh  # Точно найдёт скрипт

2. Разные версии программ

Полные пути помогают избежать ситуаций, когда cron находит другую версию программы, которая нам не нужна.

Пример:

* * * * * python3 script.py  # Какой именно python3?

Правильнее:

* * * * * /usr/local/bin/python3 /path/to/script.py

3. Проблемы с относительными путями

Cron выполняет команды с разными рабочими каталогами (часто это домашний каталог пользователя или корневой каталог). Относительные же пути могут вести не туда, куда изначально было запланировано.

Пример:

* * * * * ./script.sh  # Где ищется этот файл?

Лучше:

* * * * * /absolute/path/to/script.sh

4. Проблемы с перенаправлением вывода

Перенаправление вывода может повести себя крайне неожиданно без полных путей.

Пример:

* * * * * some_command > output.log  # Куда именно запишется файл?

Более верно:

* * * * * /usr/bin/some_command > /full/path/to/output.log

Как найти полные пути?

Помечаю для тех, кому мало знаком Linux. В данном вопросе поможет команда which или whereis:

which python3
# /usr/local/bin/python3

whereis bash
# bash: /bin/bash /usr/share/man/man1/bash.1.gz

Управление crontab-файлами

Основные команды для работы cron:

Основные команды для работы с cron:

  1. crontab -e — редактирование crontab текущего пользователя
  2. crontab -l — просмотр текущего crontab
  3. crontab -r — удаление crontab
  4. crontab -u username -e — редактирование crontab другого пользователя (требует прав root)

Системные crontab-файлы обычно расположены в:

  1. /etc/crontab — системный crontab
  2. /etc/cron.d/ — каталог для дополнительных crontab-файлов
  3. /var/spool/cron/crontabs/ — каталог с пользовательскими crontab (в некоторых дистрибутивах)

Продвинутые возможности cron

В данном блоке разберём продвинутые возможности, которые могут быть знакомы не каждому. Они позволяют использовать не только простые временные интервалы, но и сложные шаблоны для точного управления планированием задач. Подобная гибкость позволяет адаптировать базовый планировщик под различные нужды — от периодического обслуживания до сложных многоэтапных процессов.

1. Сложные интервалы и комбинации

Cron поддерживает гибкие форматы расписания, но не все знают, как их комбинировать. Далее приведу примеры использования различных «не базовых» интервалов и комбинаций:

Диапазоны (-)

  • 0 9-18 * * * — каждый час с 9:00 до 18:00
  • 0 0 1-7 * * — первые 7 дней месяца в 00:00

Списки (,)

  • 0 0 1,15 * * — 1-го и 15-го числа каждого месяца
  • 0 0 * * 1,3,5 — по понедельникам, средам и пятницам

Шаги (/)

  • */5 * * * * — каждые 5 минут
  • 0 */3 * * * — каждые 3 часа

Комбинирование

  • 0 8-18/2 * * 1-5 — каждые 2 часа с 8:00 до 18:00, но только в рабочие дни
  • 0 0,12 1-10,15 * * — в 00:00 и 12:00 с 1-го по 10-е и 15-го числа

2. Управление параллельным выполнением

Если задача выполняется дольше, чем интервал запуска — существует риск возникновения конфликтов. Возникает это по различным причинам:

  1. Накопление процессов (например, выполнение задачи 10 минут, а интервал запуска — 5 минут, то через некоторое время будут работать несколько экземпляров скрипта)
  2. Блокировка файлов и ресурсов (например, если скрипт работает с БД или логами, несколько экземпляров могут пытаться изменять идентичные данные одновременно)
  3. Проблемы с состоянием (например, первый запуск меняет конфиг, а второй запуск об изменениях «не знает», и действует некорректно)
  4. Ошибки в логировании (несколько процессов пишут в один лог. В лучшем случае данные просто перемешаются, в худшем — у Вас появится битый файл)

В данном случае мы можем решить проблему следующими способами:

  1. Ограничение времени выполнения (timeout)
* * * * * timeout 300 /path/to/script.sh  # Остановить через 5 минут
  1. Блокировка через flock
* * * * * /usr/bin/flock -n /tmp/myscript.lock /path/to/script.sh

Где -n — не ждать, если скрипт уже работает (-w 10 — ждать 10 секунд, если требуется)

Что такое flock

flock — утилита управления блокировкой файлов. Позволяет предотвратить одновременный запуск нескольких экземпляров скрипта, что критично для задач cron, если:

  • Скрипт выполняется дольше, чем интервал между запусками
  • Несколько процессов могут конфликтовать при работе с общими ресурсами (файлами, БД и тд)

Синтаксис:

  • -n — не ждать
  • -w N — ждать N секунд
  • -x — эксклюзивная блокировка (запрещает доступ всем процессам)
  • -s — разделяемая блокировка (разрешает остальным доступ на чтение)

3. Логирование и обработка ошибок

Перенаправление вывода:

  • >> file.log 2>&1 — stdout + stderr в один файл
  • >> file.log 2>> error.log — раздельные логи

Логирование с ротацией:

0 0 * * * /path/to/script.sh >> "/var/log/script_$(date +\%Y\%m\%d).log" 2>&1

Примеры сложных сценариев cron

Теория — безусловно хороший аспект, но с примерами сценариев, информация, надеюсь, усвоится лучше. Ниже я приведу несколько примеров:

1. Каскадные задачи с задержкой и проверкой статуса

# Основная задача в 2:00
0 2 * * * /path/to/first_task.sh && touch /tmp/first_success

# Вторая задача через 15 минут, только если первая успешна
15 2 * * * [ -f /tmp/first_success ] && /path/to/second_task.sh && rm /tmp/first_success
  • first_task.sh запускается в 2:00, и если успешно (&&), создаётся файл-флаг /tmp/first_success
  • В 2:15 проверяется наличие флага ([ -f ... ]), и если он есть, запускается second_task.sh, после чего флаг удаляется
  • Потенциальное применение: цепочки задач (например, сбор данных — обработка — отправка отчёта)

2. Запуск только при низкой загрузке CPU

0 22 * * * [ $(awk '{print 100-$NF}' /proc/loadavg | cut -d. -f1) -lt 30 ] && /path/to/backup.sh
  • awk '{print 100-$NF}' /proc/loadavg — вычисляет свободный CPU (100 — загрузка)
  • cut -d. -f1 — оставляет только целую часть
  • -lt 30 — проверяет, что свободно больше 70% (загрузка < 30%)
  • Потенциальное применение: тяжёлые задачи (анализ логов, бэкапы).

3. Пропуск запуска, если процесс уже выполняется

*/5 * * * * [ $(pgrep -c -f "script.sh") -eq 0 ] && /path/to/script.sh
  • pgrep -c -f "script.sh" — подсчитывает количество запущенных процессов
  • -eq 0 — если процесс не найден (0), задача запускается
  • Потенциальное применение: предотвращение дублирующихся задач (например, импорт данных).

4. Ротация логов

0 3 1 * * find /var/log/app/ -name "*.log" -mtime +30 -exec gzip {} \;
  • find /var/log/app/ — ищет файлы .log старше 30 дней
  • -exec gzip {} \; — сжимает их

5. Ограничение времени выполнения скрипта

0 * * * * timeout 600 /path/to/long_script.sh
  • timeout 600 — убивает процесс через 600 секунд (10 минут).

Альтернатива cron

Возможно, в момент прочтения блоков выше, были вопросы из-за наличия Systemd Timers, как наилучшей альтернативе cron. Как и в любой тематике нашей сферы, аналоги есть буквально у всего.

Я поведаю о нём кратко, тк статья немного не про него. Ниже — ключевые особенности и сравнение.

1. Ключевые особенности

  1. Точное расписание, а именно — запуск по календарю (OnCalendar=*-*-* 03:00:00 — ежедневно в 3:00) и относительные интервалы (OnBootSec=5min — через 5 минут после старта системы).
  2. Зависимости. К примеру, можно указать зависимость от других сервисов (старт задачи после запуска сети).
  3. Устойчивость к пропускам. Если сервер был отключен — таймер не запустит задачу при следующем включении (Persistent=true).
  4. Интеграция с systemd. Логирование в journalctl, управление командами:
systemctl start mytimer.timer  # Запустить таймер
journalctl -u mytimer.service # Посмотреть логи

2. Сравнение

ВозможностьSystemd TimerCron
ЗависимостиДа (Requires=)Нет
Запуск после пропускаДа (Persistent)Нет
ЛогированиеjournalctlФайлы (/var/log/)
Точное времяСекундыТолько минуты

Заключение

Какой вывод можно сделать из статьи? Cron — простой и полезный инструмент для автоматизации задач, но при не самом аккуратном использовании может «подкинуть» Вам проблем: от падения сервера из-за перегрузки до «тихих» ошибок, о которых можно долго не знать. Самые важные правила при его использовании:

  1. Всегда указывать полные пути
  2. По возможности контролировать параллельное выполнение
  3. Обязательно настраивать логирование (это, наверное, для всей ИТ-сферы применимо. Не мне учить, так сказать)

Для сложных сценариев, где обязательны зависимости между задачами или более точное время выполнения — безусловно systemd.timer будет более приоритетным, тк даёт в разы больше контроля и лучше интегрируется. В конечном итоге, выбор между cron и альтернативами напрямую зависит от задач. Что-то простое? Используем cron. Необходима гибкость и надёжность? Systemd timers незаменим.