Разбор выражений cron
Объясняет любое выражение cron — Vixie, Quartz, EventBridge или Jenkins, показывает ближайшие запуски в любом часовом поясе и отмечает ловушки.
Прочитано как Vixie cron.
Что это значит
В 00:00 13-го числа месяца или в пятницу
Поле за полем
| Поле | Записано | Соответствует |
|---|---|---|
| Минута | 0 | 0 |
| Час | 0 | 0 |
| День месяца | 13 | 13 |
| Месяц | * | любому значению |
| День недели | 5 | пятницу |
На что обратить внимание
- Оба поля дня ограничены, поэтому cron выполняет это, когда совпадает любое из них: день месяца или день недели, а не оба вместе. Именно поэтому 13 вместе с пятницей означает 13-е число и каждую пятницу, а не пятницу 13-го.
Ближайшие запуски
Что делает этот инструмент
Он пересказывает выражение cron словами, показывает ближайшие моменты, когда оно действительно сработает — в том часовом поясе, где работает задача, — и называет то, что выражение подразумевает, но не говорит. Последнее и есть суть: выражение cron никогда не бывает неверным так, чтобы получилась ошибка. Оно просто работает не в те дни, молча, пока кто-нибудь не заметит.
Понимаются четыре диалекта, а не один. Пятиполевая форма, которую принимает crontab, Quartz с полем секунд и календарными правилами, AWS EventBridge с годом в конце и Jenkins со своим хешем. Вставить один из них в инструмент, знающий только другой, — именно так большинство людей впервые узнаёт, что диалект не один.
Поля и сколько их
В классическом выражении пять полей, разделённых пробелами, в фиксированном порядке: минута, час, день месяца, месяц, день недели. Каждое — это звёздочка со значением «каждый», число, список через запятую, диапазон через дефис или шаг через косую черту. Месяцы и дни можно записывать трёхбуквенными именами, и это почти всегда понятнее цифр.
- Звёздочка означает любое значение, которое поле может принять.
- Диапазон вроде 9-17 означает все значения с 9 по 17 включительно.
- Шаг вроде */15 означает каждое 15-е значение от нижней границы поля; 9-17/2 шагает внутри диапазона, а 5/15 шагает от 5 до верхней границы.
- Список вроде 0,30 означает ровно эти значения.
- Вырожденный диапазон вроде 13-13 — это просто 13; некоторые библиотеки ошибаются здесь и принимают его за звёздочку.
Шестое поле — то место, где диалекты расходятся. Quartz ставит его впереди и читает как секунды; EventBridge ставит его в конец и читает как год. Шесть голых полей поэтому по-настоящему неоднозначны, и этот инструмент говорит, как он их прочитал, вместо того чтобы выбрать молча. Обёртка cron(...) — собственный синтаксис EventBridge — решает вопрос.
Макросы — это сокращения: @hourly, @daily, @midnight, @weekly, @monthly, @yearly и @annually. Они разворачиваются в обычные выражения, которые инструмент показывает. @reboot — исключение и вовсе не расписание.
Два поля дня и «или», которое выглядит как «и»
Дату можно задать двумя разными способами — днём месяца и днём недели, — и у cron есть оба поля. Когда оба ограничены, задача выполняется, если совпадает любое из них. Не оба.
Так что 0 0 13 * 5 — это не пятница 13-го. Это 13-е число каждого месяца и каждая пятница: около 64 дней в году вместо одного-двух. Выражение выглядит как конъюнкция, а ведёт себя как дизъюнкция, никто не сообщает об ошибке, и задача просто выполняется чаще, чем задумано.
У правила есть вторая половина, более странная, и её почти никто не знает. Cron проверяет не то, ограничено ли поле дня, а то, является ли первый символ поля звёздочкой. Поэтому шаг вроде */14 считается звёздочкой, хотя и ограничивает поле 1-м, 15-м и 29-м числом, — и его присутствие переводит два поля дня обратно с «или» на «и». Два одинаково ограниченных на вид выражения ведут себя тогда совершенно по-разному, и этот инструмент сообщает, какое правило действует.
Quartz и EventBridge обходят весь вопрос, не позволяя обоим полям что-то говорить: ровно одно из них должно быть вопросительным знаком, что значит «нет конкретного значения, решает другое поле».
Шаги, которые не делят своё поле
Шаг записывается так, будто он интервал, и внутри одного цикла поля он им и является. На границе — обычно нет.
*/7 в поле часов означает часы 0, 7, 14 и 21. После 21 поле кончается, поэтому следующий запуск будет в 0 часов на следующий день — через три часа, а не через семь. Каждый цикл заканчивается одним коротким промежутком, и то же самое происходит с */7 в минутах (промежуток в четыре минуты после 56) и с любым другим шагом, который не делит свой диапазон нацело. */15 в минутах и */6 в часах безопасны; большинство чисел, к которым тянется рука, — нет.
Это важно, когда шаг выбран, чтобы разнести нагрузку. Задача на */7 часов не выполняется каждые семь часов, а ограничение частоты, рассчитанное на семь, будет нарушено раз в сутки.
Дни недели нумеруются по-разному в разных местах
Vixie cron нумерует дни от 0 до 7, и воскресенье стоит на обоих концах, так что 0 и 7 — один и тот же день, а 1 — понедельник. Quartz и EventBridge нумеруют их от 1 до 7 с воскресеньем на 1, поэтому там 1 — воскресенье, а 2 — понедельник.
Выражение, скопированное из конфигурации Quartz в crontab, поэтому срабатывает на день раньше, и нигде ничего не возражает, потому что обе записи — допустимые числа в обеих системах. Трёхбуквенные имена — MON, FRI — означают один и тот же день везде и служат простой защитой.
Перевод часов: два утра в году
Демон cron не планирует моменты времени. Он каждую минуту смотрит на местные часы и спрашивает, совпадает ли выражение. Дважды в год эти часы перестают быть послушной последовательностью.
Когда они прыгают вперёд, целый час показаний просто не наступает. У задачи, поставленной на 02:30, в этот день нет 02:30. Когда они идут назад, час показаний наступает дважды, и у задачи, поставленной на 01:30, их два.
Vixie cron различает здесь два вида задач, и это различие стоит знать, потому что именно оно решает, какие из ваших задач затронуты. Задача, привязанная к показанию часов — фиксированный час и фиксированная минута, — выполняется ровно один раз в обоих случаях: после прыжка вперёд она запускается сразу, а после перевода назад cron следит, чтобы не повторить её. Задача со звёздочкой в часе или минуте — интервальная, и она просто следует за часами: весной она теряет час запусков, а осенью повторяет его.
Так что ежечасное резервное копирование действительно выполняется дважды в одну ночь в году, а ночная задача в 02:30 действительно срабатывает в необычный момент в другую. Этот инструмент сверяет ближайшие двенадцать месяцев выбранного пояса с вашим выражением и говорит, какой из двух случаев наступает, с датой.
Надёжный выход — планировать задачу в UTC либо вне местного промежутка с 01:00 до 03:00, куда не попадает ни один перевод.
На каких часах задача на самом деле
Crontab работает в местном поясе машины или в том, который указывают CRON_TZ или TZ в начале файла. Это редко пояс того, кто читает выражение, и потому часовой пояс здесь — настройка, а не пояс вашего браузера.
CronJob в Kubernetes — снова другое: он читает своё расписание в UTC, если в манифесте не задано поле timeZone, что бы ни показывали часы узла. Расписание, написанное под местные рабочие часы и развёрнутое без этого поля, срабатывает не в то время суток везде за пределами UTC — и не меняется вместе с сезонами, что для одной задачи баг, а для другой облегчение.
Jenkins и H
Jenkins добавляет символ, которого нет ни в одном другом диалекте: H, который выглядит как случайность и ею не является. Jenkins хеширует имя задачи в фиксированное значение внутри диапазона поля, поэтому задача выполняется каждый день в одно и то же время — своё, — а сотня задач с записью H * * * * равномерно распределяется по часу вместо того, чтобы стартовать всем на нулевой минуте.
Поскольку значение берётся из имени задачи, а имя не входит в выражение, ни один инструмент не может вычислить точное время. Этот разрешает каждое H в нижнюю границу его диапазона и прямо об этом говорит: форма расписания — как часто, в какие дни — точна, и лишь смещение внутри каждого периода является заменой.
Jenkins также переопределяет макросы. Его @daily — это @midnight, и это не полночь, а хешированная минута где-то в первые три часа суток.
Чего cron не делает
- Он не навёрстывает. Если машина спала или демон был выключен, когда наступало время запуска, этот запуск не произойдёт позже — он просто пропущен. anacron существует именно для этого и является другой программой.
- Он не мешает запускам накладываться. Если задача идёт дольше интервала, следующая всё равно стартует, и через некоторое время их несколько. Файл блокировки — обычный ответ.
- В классической форме у него нет понятия секунд. Самое мелкое разрешение — минута; всё, что быстрее, требует Quartz, systemd или цикла внутри самой задачи.
- @reboot — не расписание. Он выполняется один раз при запуске демона, поэтому у него нет следующего запуска, а на машине, которая никогда не перезагружается, он не выполняется вовсе.
- Выражение rate в EventBridge отсчитывается от создания правила, и этот момент не входит в выражение, так что ни один инструмент не может сказать, когда оно сработает.
Частые вопросы
- Как запустить задачу в первый понедельник месяца?
- Обычным выражением cron — никак: для этого нужно «и» между двумя полями дня, а cron даёт «или». Обычный обходной путь — поставить 0 0 1-7 * * и позволить самой команде проверить день недели. Quartz и EventBridge выражают это напрямую, как 0 0 12 ? * MON#1.
- Почему 0 0 13 * 5 срабатывает так часто?
- Потому что это значит 13-е число или каждая пятница, а не пятница 13-го. Когда оба поля дня ограничены и ни одно не начинается со звёздочки, cron запускает задачу при совпадении любого из них. В классическом диалекте нет способа записать «и».
- Чем */5 отличается от 0-59/5?
- Для поля минут — ничем: оба дают 0, 5, 10 и так далее. Разница проявляется в двух полях дня, где cron решает между «и» и «или» по тому, начинается ли поле со звёздочки, — так что */5 и 0-59/5 выбирают одни и те же дни, но по-разному соединяются со вторым полем дня.
- Выполняется ли задача дважды при переводе часов назад?
- Если в ней есть звёздочка в часе или минуте — да: она следует за часами, а этот час наступает дважды. Если час и минута фиксированы — нет, Vixie cron выполняет такую задачу один раз. Панель находок говорит, какой случай ваш, и называет дату.
- Какой часовой пояс использует выражение cron?
- Пояс машины, если в crontab не заданы CRON_TZ или TZ. CronJob в Kubernetes использует UTC, если в манифесте не задан timeZone. Поскольку пояс не входит в выражение, этот инструмент спрашивает о нём, а не берёт пояс вашего браузера.
- Можно запланировать что-то каждые 90 минут?
- Одним выражением — нет, потому что каждое поле замыкается само по себе, а 90 минут не укладываются в час. Обычная запись — два выражения, 0 0,3,6,9,12,15,18,21 * * * и 30 1,4,7,10,13,16,19,22 * * *, которые вместе дают запуск каждые 90 минут.
- Отправляется ли что-нибудь из вставленного на сервер?
- Нет. Разбор, объяснение и весь расчёт расписания выполняются в вашем браузере; ничего не загружается и не записывается, и всё работает без сети.