Перейти к основному содержимому

Руководства / Починка: руководства по ошибкам, которые встречаются на практике

Выражения cron: пять полей и примеры

Выражение cron это пять полей через пробел, которые задают момент запуска задачи: минута, час, день месяца, месяц, день недели. Читая слева направо, 30 2 * * 1 означает в 30-ю минуту второго часа любого дня любого месяца, если этот день понедельник.

Пять полей

Любая стандартная строка Unix cron начинается с одних и тех же пяти полей, за которыми идет команда.

* * * * *  command to run
| | | | |
| | | | +-- day of week  (0-7, 0 and 7 both mean Sunday, or SUN-SAT)
| | | +---- month        (1-12, or JAN-DEC)
| | +------ day of month (1-31)
| +-------- hour         (0-23)
+---------- minute       (0-59)

В поле стоит одно из четырех: звездочка, означающая любое значение, одно число, список или диапазон. Часы считаются по 24-часовым, без AM и PM, поэтому 6 вечера это 18. Воскресенье это и 0, и 7, потому что исторически системы расходились в этом вопросе, и cron принимает оба варианта.

Некоторые планировщики добавляют поля. Quartz, который используют многие Java-фреймворки, ставит первым поле секунд и добавляет необязательный год в конце, поэтому выражение из шести или семи полей, скопированное из справочника по Quartz, в crontab на Linux не заработает. Прежде чем копировать выражение откуда бы то ни было, посмотрите, для какого диалекта вы пишете.

Специальные символы

  • * совпадает с любым значением поля. * * * * * запускается каждую минуту.
  • , перечисляет значения. 0 8,13,20 * * * запускается в 08:00, 13:00 и 20:00.
  • - задает диапазон. 0 9-17 * * * запускается ровно в час с 09:00 по 17:00.
  • / задает шаг по диапазону. */15 * * * * запускается на 0, 15, 30 и 45-й минуте. Шаг можно совместить с диапазоном: 0 9-17/2 * * * запускается в 9, 11, 13, 15 и 17.
  • Имена. MON по SUN и JAN по DEC работают в последних двух полях и читаются лучше цифр. Внутри шага их использовать нельзя.

Еще четыре символа постоянно попадаются в руководствах и в стандартном cron не существуют. L для последнего дня, W для ближайшего рабочего дня, # для n-го дня недели в месяце и ? вместо звездочки это расширения Quartz. Выражение вида 0 0 L * * crontab на Linux отвергнет. Чтобы запустить что-то в последний день месяца, запускайте ежедневно и пусть решает сама команда:

0 0 * * * [ "$(date -d tomorrow +\%d)" = "01" ] && /usr/local/bin/month-end.sh

Обратите внимание на обратные слэши в этой строке. Знак процента в crontab имеет особый смысл, и это пятая ошибка из списка ниже.

Cron принимает также шесть сокращений вместо всех пяти полей: @yearly (пишется и как @annually), @monthly, @weekly, @daily (он же @midnight), @hourly и @reboot, который срабатывает один раз при старте cron, а не по часам. @reboot поддерживается не везде.

Частые расписания

Когда нужно запускатьВыражение
Каждую минуту* * * * *
Каждые 5 минут*/5 * * * *
Каждые 30 минут0,30 * * * *
Каждый час, ровно в час0 * * * *
Каждые 2 часа0 */2 * * *
Каждый день в полночь0 0 * * *
Каждый день в 02:3030 2 * * *
Дважды в день, в 08:00 и 20:000 8,20 * * *
По будням в 09:000 9 * * MON-FRI
Каждый час в рабочие часы по будням0 9-17 * * MON-FRI
Каждый понедельник в 07:000 7 * * MON
Каждое воскресенье в 03:1515 3 * * SUN
Первого числа месяца в 06:000 6 1 * *
Раз в квартал, 1 января, апреля, июля и октября0 0 1 1,4,7,10 *

Ошибки, из-за которых задача уходит не на то время

  1. Заполнены оба поля дня. На этом спотыкаются все. Когда день месяца и день недели оба заданы не звездочкой, cron запускает задачу, если совпало любое из них, а не оба сразу. 0 0 13 * FRI сработает каждое 13-е число и каждую пятницу, а не в пятницу 13-го.
  2. Шаг, который не делит диапазон нацело. */7 * * * * срабатывает на минутах 0, 7, 14, 21, 28, 35, 42, 49 и 56, а затем начинает с 0. Промежуток через границу часа получается четыре минуты, а не семь. Та же ловушка ждет */40 в поле минут и любой шаг, не являющийся делителем 60.
  3. Шаг не в том поле. */2 * * * * это каждые две минуты. 0 */2 * * * это каждые два часа. Пропущенный ведущий 0 превращает ежечасную задачу в задачу, которая работает в 30 раз чаще.
  4. Расчет на привычное окружение. Cron дает задаче минимальное окружение, обычно чуть больше, чем PATH=/usr/bin:/bin и SHELL=/bin/sh. Скрипт, который работает в вашей оболочке, в cron падает, потому что вызывает что-то из /usr/local/bin. Пишите абсолютные пути и в команде, и внутри нее.
  5. Неэкранированный знак процента. В crontab % превращается в перевод строки, и все, что идет после первого такого знака, подается команде на стандартный ввод. date +%Y-%m-%d внутри строки crontab приходится писать как date +\%Y-\%m-\%d.
  6. Игнорирование вывода. Все, что задача печатает, уходит письмом владельцу crontab, а на машине без настроенной почты просто выбрасывается. Отправьте вывод туда, куда вы посмотрите: 0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
  7. Наложение запусков. Cron начинает следующий запуск по расписанию, не глядя, закончился ли предыдущий. Оберните долгие задачи в блокировку: */5 * * * * flock -n /tmp/sync.lock /usr/local/bin/sync.sh
  8. Забытый перевод часов. Cron работает по локальному времени машины. В дни перевода часов час около 02:00 либо не существует, либо случается дважды, поэтому задача, назначенная внутри него, будет пропущена или выполнена два раза, в зависимости от реализации cron. Держите серверы в UTC или назначайте запуски вне этого окна.
  9. Набранное crontab -r. Команда стирает весь crontab без подтверждения, а на клавиатуре стоит в одной кнопке от crontab -e. Держите crontab в системе контроля версий и ставьте его из файла.

Проверка снаружи

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

На машине убедитесь, что установлено, что показывают часы и запускал ли cron задачу:

crontab -l
timedatectl
grep CRON /var/log/syslog | tail -20
journalctl -u cron -u crond --since "2 hours ago"

Строка в журнале доказывает, что cron запустил команду. Про успех самой команды она не говорит ничего, и именно поэтому важно перенаправление в файл журнала из списка ошибок выше.

Затем проверьте результат так, как его увидит пользователь, снаружи сервера. Почти все задачи по расписанию существуют ради наблюдаемого изменения: лента, в которой должна стоять сегодняшняя дата, карта сайта, которую надо было перезаписать, выгрузка, которая должна была появиться. Запросите ее и прочитайте заголовки:

curl -sI https://example.com/exports/daily.json
curl -s https://example.com/health/last-run

Заголовок Last-Modified покажет, свежий ли файл, и сделает это без доступа к оболочке. Тот же запрос из нескольких локаций через проверку HTTP отделяет невыполненную задачу от страницы, которая закэширована на краевом сервере в одном регионе и устарела только там. Одиночный curl на самом сервере такую разницу не покажет.

Как узнать, что задача по расписанию перестала работать

Остановившаяся задача cron не производит ничего, что можно было бы заметить. Нет ни страницы с ошибкой, ни неудачного запроса, поэтому оповещение не сработает, если только кто-то не следит за самим отсутствием. Следить надо не за задачей, а за ее следом: файлом состояния, который она перезаписывает, отметкой времени, которую публикует, endpoint, который сообщает время последнего успешного запуска. Проверка такого адреса по расписанию превращает тихий отказ в уведомление, а HostTracker отправляет такие уведомления по email, SMS, голосовым звонком, в Slack, Telegram и другими способами и поддерживает 13 типов проверок. Как это настроить, описано в руководстве по проверке HTTP, остальные руководства по починке собраны в кластере починки, а полный список типов проверок есть на странице возможностей.

Проверить сейчас

Запустите бесплатную проверку своего сайта - аккаунт не нужен.

Features

Следить за этим постоянно

Получайте оповещение в момент сбоя: HostTracker проверяет более чем из 300 локаций и уведомляет по email, SMS, в Slack, Telegram и не только.

Возможности HostTracker

Ещё в этом разделе: Починка: руководства по ошибкам, которые встречаются на практике