Классик писал, что счастливые часов не наблюдают. В те дикие времена ещё не было ни программистов, ни Unix, но в наши дни программисты знают твёрдо: вместо них за временем проследит cron.
Утилиты командной строки для меня одновременно слабость и рутина. sed, awk, wc, cut и другие старые программы запускаются скриптами на наших серверах ежедневно. Многие из них оформлены в виде задач для cron, планировщика родом из 70-х.
Я долго пользовался cron поверхностно, не вникая в детали, но однажды, столкнувшись с ошибкой при запуске скрипта, решил разобраться основательно. Так появилась эта статья, при написании которой я ознакомился с POSIX crontab, основными вариантами cron в популярных дистрибутивах Linux и устройством некоторых из них.
Используете Linux и запускаете задачи в cron? Вам интересна архитектура системных приложений в Unix? Тогда нам по пути!
Содержание
- Происхождение видов
- POSIX crontab
- Хит продаж — Vixie cron 3.0pl1
- cron в Debian и Ubuntu
- cronie в Red Hat, Fedora и CentOS
- cronie в SLES и openSUSE
- Устройство Vixie cron
- Послесловие
Происхождение видов
Периодическое выполнение пользовательских или системных программ — очевидная необходимость во всех операционных системах. Поэтому потребность в сервисах, позволяющих централизованно планировать и выполнять задачи, программисты осознали очень давно.
Unix-подобные операционные системы ведут свою родословную от Version 7 Unix, разработанной в 70-х годах прошлого века в Bell Labs в том числе и знаменитым Кеном Томпсоном (англ. Ken Thompson). Вместе c Version 7 Unix поставлялся и cron, сервис для регулярного выполнения задач суперпользователя.
Типичный современный cron — несложная программа, но алгоритм работы оригинального варианта был ещё проще: сервис просыпался раз в минуту, читал табличку с задачами из единственного файл (/etc/lib/crontab) и выполнял для суперпользователя те задачи, которые следовало выполнить в текущую минуту.
Впоследствии усовершенствованные варианты простого и полезного сервиса поставлялись со всеми Unix-подобными операционными системами.
Обобщённые описания формата crontab и базовых принципов работы утилиты в 1992 году были включены в главный стандарт Unix-подобных операционных систем — POSIX — и таким образом cron из стандарта де-факто стал стандартом де-юре.
В 1987 году Пол Викси (англ. Paul Vixie), опросив пользователей Unix на предмет пожеланий к cron, выпустил ещё одну версию демона, исправляющую некоторые проблемы традиционных cron и расширяющую синтаксис файлов-таблиц.
К третьей версии Vixie cron стал отвечать требованиям POSIX, к тому же у программы была либеральная лицензия, вернее не было вообще никакой лицензии, если не считать пожеланий в README: гарантий автор не даёт, имя автора удалять нельзя, а продавать программу можно только вместе с исходным кодом. Эти требования оказались совместимы с принципами набиравшего в те годы популярность свободного ПО, поэтому некоторые ключевые из появившихся в начале 90-х дистрибутивов Linux взяли Vixie cron в качестве системного и развивают его до сих пор.
В частности, Red Hat и SUSE развивают форк Vixie cron — cronie, а Debian и Ubuntu используют оригинальное издание Vixie cron со множеством патчей.
Давайте для начала познакомимся с описанной в POSIX пользовательской утилитой crontab, после чего разберём расширения синтаксиса, представленные в Vixie cron, и использование вариаций Vixie cron в популярных дистрибутивах Linux. И, наконец, вишенка на торте — разбор устройства демона cron.
POSIX crontab
Если оригинальный cron всегда работал для суперпользователя, то современные планировщики чаще имеют дело с задачами обычных пользователей, что более безопасно и удобно.
Сron-ы поставляются комплектом из двух программ: постоянно работающего демона cron и доступной пользователям утилиты crontab. Последняя позволяет редактировать таблицы задач, специфичные для каждого пользователя в системе, демон же запускает задачи из пользовательских и системной таблиц.
В стандарте POSIX никак не описывается поведение демона и формализована только пользовательская программа crontab. Существование механизмов запуска пользовательских задач, конечно, подразумевается, но не описано подробно.
Вызовом утилиты crontab можно сделать четыре вещи: отредактировать пользовательскую таблицу задач в редакторе, загрузить таблицу из файла, показать текущую таблицу задач и очистить таблицу задач. Примеры работы утилиты crontab:
crontab -e # редактировать таблицу задач
crontab -l # показать таблицу задач
crontab -r # удалить таблицу задач
crontab path/to/file.crontab # загрузить таблицу задач из файла
При вызове crontab -e
будет использоваться редактор, указанный в стандартной переменной окружения EDITOR
.
Сами задачи описаны в следующем формате:
# строки-комментарии игнорируются
#
# задача, выполняемая ежеминутно
* * * * * /path/to/exec -a -b -c
# задача, выполняемая на 10-й минуте каждого часа
10 * * * * /path/to/exec -a -b -c
# задача, выполняемая на 10-й минуте второго часа каждого дня и использующая перенаправление стандартного потока вывода
10 2 * * * /path/to/exec -a -b -c > /tmp/cron-job-output.log
Первые пять полей записей: минуты [1..60], часы [0..23], дни месяца [1..31], месяцы [1..12], дни недели [0..6], где 0 — воскресенье. Последнее, шестое, поле — строка, которая будет выполнена стандартным интерпретатором команд.
В первых пяти полях значения можно перечислять через запятую:
# задача, выполняемая в первую и десятую минуты каждого часа
1,10 * * * * /path/to/exec -a -b -c
Или через дефис:
# задача, выполняемая в каждую из первых десяти минут каждого часа
0-9 * * * * /path/to/exec -a -b -c
Доступ пользователей к планированию задач регулируется в POSIX файлам cron.allow и cron.deny в которых перечисляются, соответственно, пользователи с доступом к crontab и пользователи без доступа к программе. Расположение этих файлов стандарт никак не регламентирует.
Запускаемым программам, согласно стандарту, должны передаваться по меньшей мере четыре переменные окружения:
- HOME — домашняя директория пользователя.
- LOGNAME — логин пользователя.
- PATH — путь, по которому можно найти стандартные утилиты системы.
- SHELL — путь к использованному командному интерпретатору.
Примечательно, что POSIX ничего не говорит о том, откуда берутся значения для этих переменных.
Хит продаж — Vixie cron 3.0pl1
Общий предок популярных вариантов cron — Vixie cron 3.0pl1, представленный в рассылке comp.sources.unix в 1992 году. Основные возможности этой версии мы и рассмотрим подробнее.
Vixie cron поставляется в двух программах (cron и crontab). Как обычно, демон отвечает за чтение и запуск задач из системной таблицы задач и таблиц задач отдельных пользователей, а утилита crontab — за редактирование пользовательских таблиц.
Таблица задач и файлы конфигурации
Таблица задач суперпользователя расположена в /etc/crontab. Синтаксис системной таблицы соответствует синтаксису Vixie cron с поправкой на то, что в ней шестой колонкой указывается имя пользователя, от лица которого запускается задача:
# Запускается ежеминутно от пользователя vlad
* * * * * vlad /path/to/exec
Таблицы задач обычных пользователей располагаются в /var/cron/tabs/username и используют общий синтаксис. При запуске утилиты crontab от имени пользователя редактируются именно эти файлы.
Управление списками пользователей, имеющих доступ к crontab, происходит в файлах /var/cron/allow и /var/cron/deny, куда достаточно внести имя пользователя отдельной строкой.
Расширенный синтаксис
По сравнению с POSIX crontab решение Пола Викси содержит несколько очень полезных модификаций в синтаксисе таблиц задач утилиты.
Стал доступен новый синтаксис таблиц: например, можно указывать дни недели или месяцы поимённо (Mon, Tue и так далее):
# Запускается ежеминутно по понедельникам и вторникам в январе
* * * Jan Mon,Tue /path/to/exec
Можно указывать шаг, через который запускаются задачи:
# Запускается с шагом в две минуты
*/2 * * * Mon,Tue /path/to/exec
Шаги и интервалы можно смешивать:
# Запускается с шагом в две минуты в первых десять минут каждого часа
0-10/2 * * * * /path/to/exec
Поддерживаются интуитивные альтернативы обычному синтаксису (reboot, yearly, annually, monthly, weekly, daily, midnight, hourly):
# Запускается после перезагрузки системы
@reboot /exec/on/reboot
# Запускается раз в день
@daily /exec/daily
# Запускается раз в час
@hourly /exec/daily
Среда выполнения задач
Vixie cron позволяет менять окружение запускаемых приложений.
Переменные окружения USER, LOGNAME и HOME не просто предоставляются демоном, а берутся из файла passwd. Переменная PATH получает значение "/usr/bin:/bin", а SHELL — "/bin/sh". Значения всех переменных, кроме LOGNAME, можно изменить в таблицах пользователей.
Некоторые переменные окружения (прежде всего SHELL и HOME) используются самим cron для запуска задачи. Вот как может выглядеть использование bash вместо стандартного sh для запуска пользовательских задач:
SHELL=/bin/bash
HOME=/tmp/
# exec будет запущен bash-ем в /tmp/
* * * * * /path/to/exec
В конечном итоге все определённые в таблице переменные окружения (используемые cron или необходимые процессу) будут переданы запущенной задаче.
Для редактирования файлов утилитой crontab используется редактор, указанный в переменной окружения VISUAL или EDITOR. Если в среде, где был запущен crontab, эти переменные не определены, то используется "/usr/ucb/vi" (ucb — это, вероятно, University of California, Berkeley).
cron в Debian и Ubuntu
Разработчики Debian и производных дистрибутивов выпустили сильно модифицированную версию версию Vixie cron 3.0pl1. Отличий в синтаксисе файлов-таблиц нет, для пользователей это тот же самый Vixie cron. Крупнейшие новые возможности: поддержка syslog, SELinux и PAM.
Из менее заметных, но осязаемых изменений — расположение конфигурационных файлов и таблиц задач.
Пользовательские таблицы в Debian располагаются в директории /var/spool/cron/crontabs, системная таблица всё там же — в /etc/crontab. Специфичные для пакетов Debian таблицы задач помещаются в /etc/cron.d, откуда демон cron их автоматически считывает. Управление доступом пользователей регулируется файлами /etc/cron.allow и /etc/cron.deny.
В качестве командной оболочки по умолчанию по-прежнему используется /bin/sh, в роли которого в Debian выступает небольшой POSIX-совместимый шелл dash, запущенный без чтения какой-либо конфигурации (в неинтерактивном режиме).
Сам cron в последних версиях Debian запускается через systemd, а конфигурацию запуска можно посмотреть в /lib/systemd/system/cron.service. Ничего особенного в конфигурации сервиса нет, любое более тонкое управление задачами возможно осуществить через переменные окружения, объявленные прямо в crontab каждого из пользователей.
cronie в RedHat, Fedora и CentOS
cronie — форк Vixie cron версии 4.1. Как и в Debian, синтаксис не менялся, но добавлена поддержка PAM и SELinux, работы в кластере, слежения за файлами при помощи inotify и других возможностей.
Конфигурация по умолчанию находится в обычных местах: системная таблица — в /etc/crontab, пакеты помещают свои таблицы в /etc/cron.d, пользовательские таблицы попадают в /var/spool/cron/crontabs.
Демон запускается под управлением systemd, конфигурация сервиса — /lib/systemd/system/crond.service.
В Red Hat-подобных дистрибутивах при запуске по умолчанию используется /bin/sh, в роли которого выступает стандартный bash. Надо заметить, что при запуске задач cron через /bin/sh командная оболочка bash запускается в POSIX-совместимом режиме и не читает никакой дополнительной конфигурации, работая в неинтерактивном режиме.
cronie в SLES и openSUSE
Немецкий дистрибутив SLES и его дериватив openSUSE используют всё тот же cronie. Демон здесь тоже запускается под systemd, конфигурация сервиса лежит в /usr/lib/systemd/system/cron.service. Конфигурация: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. В качестве /bin/sh выступает тот же самый bash, запущенный в POSIX-совместимом неинтерактивном режиме.
Устройство Vixie cron
Современные потомки cron по сравнению с Vixie cron не изменились радикально, но всё же обзавелись новыми возможностями, не требующимися для понимания принципов работы программы. Многие из этих расширений оформлены неаккуратно и путают код. Оригинальный же исходный код cron в исполнении Пола Викси читать одно удовольствие.
Поэтому разбор устройства cron я решил провести на примере общей для обеих ветвей развития cron программы — Vixie cron 3.0pl1. Примеры я упрощу, убрав усложняющие чтение ifdef-ы и опустив второстепенные детали.
Работу демона можно разделить на несколько этапов:
- Инициализация программы.
- Сбор и обновление списка задач для запуска.
- Работа главного цикла cron.
- Запуск задачи.
Разберём их по порядку.
Инициализация
При запуске после проверки аргументов процесса cron устанавливает обработчики сигналов SIGCHLD и SIGHUP. Первый вносит в лог запись о завершении работы дочернего процесса, второй — закрывает файловый дескриптор файла-лога:
signal(SIGCHLD, sigchld_handler);
signal(SIGHUP, sighup_handler);
Демон cron в системе всегда работает один, только в роли суперпользователя и из главной директории cron. Следующие вызовы создают файл-лок с PID-ом процесса-демона, убеждаются, что пользователь правильный и меняют текущую директорию на главную:
acquire_daemonlock(0);
set_cron_uid();
set_cron_cwd();
Выставляется путь по умолчанию, который будет использоваться при запуске процессов:
setenv("PATH", _PATH_DEFPATH, 1);
Дальше процесс «демонизируется»: создаёт дочернюю копию процесса вызовом fork и новую сессию в дочернем процессе (вызов setsid). В родительском процессе больше надобности нет — и он завершает работу:
switch (fork()) {
case -1:
/* критическая ошибка и завершение работы */
exit(0);
break;
case 0:
/* дочерний процесс */
(void) setsid();
break;
default:
/* родительский процесс завершает работу */
_exit(0);
}
Завершение родительского процесса высвобождает лок на файле-локе. Кроме того, требуется обновить PID в файле на дочерний. После этого заполняется база задач:
/* повторный захват лока */
acquire_daemonlock(0);
/* Заполнение БД */
database.head = NULL;
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);
Дальше cron переходит к главному циклу работы. Но перед этим стоит взглянуть на загрузку списка задач.
Сбор и обновление списка задач
За загрузку списка задач отвечает функция load_database. Она проверяет главный системный crontab и директорию с пользовательскими файлами. Если файлы и директория не менялись, то список задач не перечитывается. В противном случае начинает формироваться новый список задач.
Загрузка системного файла со специальными именами файла и таблицы:
/* если файл системной таблицы изменился, перечитываем */
if (syscron_stat.st_mtime) {
process_crontab("root", "*system*",
SYSCRONTAB, &syscron_stat,
&new_db, old_db);
}
Загрузка пользовательских таблиц в цикле:
while (NULL != (dp = readdir(dir))) {
char fname[MAXNAMLEN+1],
tabname[MAXNAMLEN+1];
/* читать файлы с точкой не надо*/
if (dp->d_name[0] == '.')
continue;
(void) strcpy(fname, dp->d_name);
sprintf(tabname, CRON_TAB(fname));
process_crontab(fname, fname, tabname,
&statbuf, &new_db, old_db);
}
После чего старая база данных подменяется новой.
В примерах выше вызов функции process_crontab убеждается в существовании пользователя, соответствующего имени файла таблицы (если только это не суперпользователь), после чего вызывает load_user. Последняя уже читает сам файл построчно:
while ((status = load_env(envstr, file)) >= OK) {
switch (status) {
case ERR:
free_user(u);
u = NULL;
goto done;
case FALSE:
e = load_entry(file, NULL, pw, envp);
if (e) {
e->next = u->crontab;
u->crontab = e;
}
break;
case TRUE:
envp = env_set(envp, envstr);
break;
}
}
Здесь либо выставляется переменная окружения (строки вида VAR=value) функциями load_env / env_set, либо читается описание задачи (* * * * * /path/to/exec) функцией load_entry.
Сущность entry, которую возвращает load_entry, — это и есть наша задача, помещаемая в общий список задач. В самой функции проводится многословный разбор формата времени, нас же больше интересует формирование переменных окружения и параметров запуска задачи:
/* пользователь и группа для запуска задачи берутся из passwd*/
e->uid = pw->pw_uid;
e->gid = pw->pw_gid;
/* шелл по умолчанию (/bin/sh), если пользователь не указал другое */
e->envp = env_copy(envp);
if (!env_get("SHELL", e->envp)) {
sprintf(envstr, "SHELL=%s", _PATH_BSHELL);
e->envp = env_set(e->envp, envstr);
}
/* домашняя директория */
if (!env_get("HOME", e->envp)) {
sprintf(envstr, "HOME=%s", pw->pw_dir);
e->envp = env_set(e->envp, envstr);
}
/* путь для поиска программ */
if (!env_get("PATH", e->envp)) {
sprintf(envstr, "PATH=%s", _PATH_DEFPATH);
e->envp = env_set(e->envp, envstr);
}
/* имя пользовтеля всегда из passwd */
sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
e->envp = env_set(e->envp, envstr);
С актуальным списком задач и работает главный цикл.
Главный цикл
Оригинальный cron из Version 7 Unix работал совсем просто: в цикле перечитывал конфигурацию, запускал суперпользователем задачи текущей минуты и спал до начала следующей минуты. Этот простой подход на старых машинах требовал слишком много ресурсов.
В SysV была предложена альтернативная версия, в которой демон засыпал либо до ближайшей минуты, для которой определена задача, либо на 30 минут. Ресурсов на перечитывание конфигурации и проверку задач в таком режиме потреблялось меньше, но быстро обновлять список задач стало неудобно.
Vixie cron вернулся к проверке списков задач раз в минуту, благо к концу 80-х ресурсов на стандартных Unix-машинах стало значительно больше:
/* первичная загрузка задач */
load_database(&database);
/* запустить задачи, поставленные к выполнению после перезагрузки системы */
run_reboot_jobs(&database);
/* сделать TargetTime началом ближайшей минуты */
cron_sync();
while (TRUE) {
/* выполнить задачи, после чего спать до TargetTime с поправкой на время, потраченное на задачи */
cron_sleep();
/* перечитать конфигурацию */
load_database(&database);
/* собрать задачи для данной минуты */
cron_tick(&database);
/* перевести TargetTime на начало следующей минуты */
TargetTime += 60;
}
Непосредственно выполнением задач занимается функция cron_sleep, вызывающая функции job_runqueue (перебор и запуск задач) и do_command (запуск каждой отдельной задачи). Последнюю функцию стоит разобрать подробнее.
Запуск задачи
Функция do_command исполнена в хорошем Unix-стиле, то есть для асинхронного выполнения задачи она делает fork. Родительский процесс продолжает запуск задач, дочерний — занимается подготовкой процесса задачи:
switch (fork()) {
case -1:
/*не смогли выполнить fork */
break;
case 0:
/* дочерний процесс: на всякий случай еще раз пробуем захватить главный лок */
acquire_daemonlock(1);
/* переходим к формированию процесса задачи */
child_process(e, u);
/* по завершению дочерний процесс заканчивает работу */
_exit(OK_EXIT);
break;
default:
/* родительский процесс продолжает работу */
break;
}
В child_process довольно много логики: она принимает стандартные потоки вывода и ошибок на себя, чтобы потом переслать на почту (если в таблице задач указана переменная окружения MAILTO), и, наконец, ждёт завершения работы основного процесса задачи.
Процесс задачи формируется еще одним fork:
switch (vfork()) {
case -1:
/* при ошибки сразу завершается работа */
exit(ERROR_EXIT);
case 0:
/* процесс-внук формирует новую сессию, терминал и т.д.
*/
(void) setsid();
/*
* дальше многословная настройка вывода процесса, опустим для краткости
*/
/* смена директории, пользователя и группы пользователя,
* то есть процесс больше не суперпользовательский
*/
setgid(e->gid);
setuid(e->uid);
chdir(env_get("HOME", e->envp));
/* запуск самой команды
*/
{
/* переменная окружения SHELL указывает на интерпретатор для запуска */
char *shell = env_get("SHELL", e->envp);
/* процесс запускается без передачи окружения родительского процесса,
* то есть именно так, как описано в таблице задач пользователя */
execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
/* ошибка — и процесс на запустился? завершение работы */
perror("execl");
_exit(ERROR_EXIT);
}
break;
default:
/* сам процесс продолжает работу: ждет завершения работы и вывода */
break;
}
Вот, в общем-то, и весь cron. Какие-то интересные детали, например учёт удалённых пользователей, я опустил, но главное изложил.
Послесловие
Сron — на удивление простая и полезная программа, выполненная в лучших традициях мира Unix. Она не делает ничего лишнего, но свою работу выполняет замечательно на протяжении уже нескольких десятилетий. Ознакомление с кодом той версии, что поставляется с Ubuntu, заняло не больше часа, а удовольствия я получил массу! Надеюсь, я смог поделиться им с вами.
Не знаю, как вам, но мне немного грустно осознавать, что современное программирование с его склонностью к переусложнению и переабстрагированию уже давно не располагает к подобной простоте.
Существует множество современных альтернатив cron: systemd-timers позволяют организовать сложные системы с зависимостями, в fcron можно гибче регулировать потребление ресурсов задачами. Но лично мне всегда хватало простейших crontab.
Словом, любите Unix, используйте простые программы и не забывайте читать маны для вашей платформы!
Комментарии (60)
am-habr
19.09.2019 18:23cron был для меня настоящим открытием. Штука, которая просто работает.
VlK Автор
19.09.2019 18:26Ну, открытием он для меня был очень давно, но инструмент, безусловно полезный и простой до банальности. Я там уже писал, что очень люблю программы в этой традиции.
evgenyk
19.09.2019 18:43Многое из того, что сейчас делают на всяких крутых штуках вроде докера, кубернета и так далее командами по 10 человек, раньше за то же самое время делал один на голом линуксе плюс баш плюч крон.
OnYourLips
20.09.2019 00:08Давайте просто пример приведу, почему это не так.
У вас на сервере, работающем в UTC, несколько проектов, два нормальных, а один работает в какой-то локальной таймзоне. И все требуют крона.
И надо определённую задачу запускать для этого проекта по локальному для него времени.
Можно пересчитать часы запуска команд автоматически при записи кронтаба, используя отстаток от 24.
Усложним: запуск задачи в 2 утра в определенной таймзоне в пятницу и вторник. При пересчете придется менять день недели для некоторых таймзон. И это не автоматизируется в адекватное количество трудозатрат, пересчитывать придется вручную.scruff
20.09.2019 07:16Проще проекты раскидать по виртуалкам, а на них уже настроить нужный часовой пояс.
Zada
20.09.2019 07:52Для разработчиков нет никакой другой таймзоны, окромя UTC.
OnYourLips
20.09.2019 11:17Вы никогда не работали в проектах, у истоков которых не стояли?
Разве не было такого, что приходите в компанию, а там во всех проектах Moscow/CET/PDT?
При этом все опускают головы и говорят: "Так исторически сложилось" или "Когда я пришел, это дерьмо уже было".
И потом приходится строить костыли на костылях, чтобы это поддерживать, потому что отрефакторить такой объем единовременно или по частям хоть с какой-то стабильностью крайне опасно и на подобное просто не дают добро.
VlK Автор
21.09.2019 10:30Простите, что поздно отвечаю, как-то пропустил.
а переменная CRON_TZ тут не помогает?
OnYourLips
21.09.2019 10:39Проверял — не помогает, точно не помню, что она меняет, но не подошло.
К тому же она в Ubuntu, насколько помню, только в 18 появилась и в 16 её не было.
JTG
19.09.2019 19:48+4Здесь будет ветка переписи тех, кто случайно выполнял «crontab -r», промахнувшись мимо клавиши «e» :)
am-habr
19.09.2019 20:29Из статьи узнал про crontab -r, до этого всегда открывал crontab -e и удалял текст внутри. Судя по ветке, и хорошо, что не пользовал.
simpleadmin
20.09.2019 14:40Лет 15 назад я искал удаленную подработку. До этого я не имел опыта Linux и работал только на FreeBSD.
И вот в первый-же день (скорее даже час) работы я «промахнулся» и узнал, что в Centos у crontab нет «глупых» вопросов а-ля
# crontab -r remove crontab for root?
Всю ночь я его восстанавливал по логам. Странно, что не уволили…
keydon2
19.09.2019 20:28Ограничение, что крон может запускать задачи не чаще чем раз в секунду заставляло писать свои костыли.
Крайне не хватало возможности указать частоту опроса, пускай и с перезапуском самого крона.am-habr
19.09.2019 20:53+1Да, интересный вопрос. Ограничение по-умолчанию — минута, является большим для машины и относительно маленьким для человека. Предлагают скриптиком чаще запускать.
Больше похоже на компромис, который сложился исторически, когда машины были ещё не так быстры. Частота в секунду для современных компьютеров была бы приемлемой, наверно.VlK Автор
19.09.2019 22:43Смею предположить, что, во-первых, ежесекундные задачи — дело редкое, а во-вторых, в свое время ежесекундный запуск чего-либо было непозволительной роскошью.
Iv38
20.09.2019 11:10Ежесекундные задачи может и редкость, но вот разнести запуск ежеминутных в пределах минуты или запускать задачи два, три, четыре раза в минуту бывает нужно. То есть посекундная точность не помешала бы.
VlK Автор
20.09.2019 12:09это решаемая проблема. Какой-нибудь файл-лок можно, например, хватать на запуске задач.
Хотя я понимаю, о чем вы. Но мы вот делали именно flock -x в похожей ситуации.
NickyX3
20.09.2019 15:41Это просто, bash скрипт запускающий нужное количество команд через слип прекрасно работал у нас лет 10
rw6hrm
19.09.2019 21:53… ну и упомянуть Cron для Windows тоже стоило бы ;), на порядок удобнее встроенного планировщика.
pcdesign
20.09.2019 13:42А можно ссылку?
scruff
20.09.2019 08:53Гуру cron-a! Подскажите по следующей ситуации — потребовалось мне как-то хранить логи почтовых треэйсов N-месяцев с граничного Антиспам-сервера. Задача проста до нельзя — раз в сутки-двое копировать файл /var/log/mail.log, аппендить к нему дату и ложить в какую-то папочку. Код crontab-a был приблизительно такой: 0 2 * * * cp /var/log/mail/ /home/user/mail_`%d%m%Y. Так вот в этом случае крон не отрабатывал, логи честно не помню, т.к. скорее всего не ыбло ничего вразумительного в них. Поступил проще — создал скрипт /home/user/copy_log.sh, вставил в него cp /var/log/mail/ /home/user/mail_`%d%m%Y, добавил «o+х» (каюсь, можно было и u+x, то бишь запуск только root-у) на скрипт, а сам скрипт закинул в crontab: 0 2 * * * /home/user/copy_log.sh. Вот тогда всё заработало без косяков. Вопрос — почему крон не работал напрямую с командой cp и требует скриптовую прослойку?
fraks
20.09.2019 09:14Скорее всего проблема в переменных среды, которые при запуске от cron совсем не те что при запуске из консоли. Например не то содержание переменной path и программы/команды просто не находит. Можно попробовать прописать везде абсолютные пути, можно вывести переменные в файлы и посмотреть какие там значения. Не претендую на правильность советов :)
VlK Автор
20.09.2019 10:01Надо бы указать версию вашего дистрибутива и крона. Но вообще использование скриптов-оберток — стандартный подход.
Логов, кстати, у кронов может и не быть, стандартно бывает только отправка на почту, указанную в переменной MAILTO.
Если хочется разобраться подробней, то можно попровать вызывать прямиком шелл с вашей командой, так, как его вызывает сам cron: /bin/sh -c "cp /from/file /to/file". Часто путаница бывает связана с тем, что шелл по умолчанию берется POSIX-совместимый и запущенный в неинтерактивном режиме.
am-habr
20.09.2019 10:35Лог можно настроить, но есть ли смысл в нём? Обычно им выполняют скрипты, которые логируют в своём окружении, елси это пёрл, питон или пхп.
Однажды ковырялся в консоли и тут появляется сообщение: у вас есть новый емейл, по такому пути можете найти. «Ничего себе, консоль мне пишет про емейлы» — подумал я и, действительно, нашёл сообщение от крона в емейлах. Очень удобно оказалось.VlK Автор
20.09.2019 10:38да, в старые добрые MAILTO хорошо работало. Но сейчас несколько потеряло актуальность.
scruff
20.09.2019 10:48Скорее всего в этом и дело. Спасибо, что подсказали. Хорошо что хоть из POSIX-режима можно скриптом вызывать шельные команды. Без этого ценность крона была бы околонулевой. Кстати в виндовом шедулере я встречал аналогичный трабл — длинные команды с кучей аргументов, напрямую прописанные в задании не работают. Как только закидываешь всё задание в батник — вуаля! Всё работает. Имхуется мне, что POSIX и там пробежал.
am-habr
20.09.2019 10:07+1Не требует. Пишут, что нужно \% или через переменную среды делать. Ссылка
Скрипт удобен тем, что его можно как вручную для теста запустить, так и кроном выполнить.
И в случае crontab -r скрипт останется.VlK Автор
20.09.2019 10:15точно, надо ж экранировать проценты, т.к. проценты в кроне означают перенос строки, для передачи в команды нескольких строк :-)
Из стандарта:
A <percent-sign> character in this field shall be translated to a <newline>. Any character preceded by a
<backslash> (including the '%' ) shall cause that character to be treated literally. Only the first line (up to a
'%' or end-of-line) of the command field shall be executed by the command interpreter. The other lines shall > be made available to the command as standard input.
VlK Автор
20.09.2019 10:16вам ниже ответили, что проценты надо экранировать :-) Там же я привел выжимку из стандарта
fraks
20.09.2019 09:04+1Для винды есть nnCron и nnCron Lite (последний — бесплатен).
Формат crontab такой же + дополнения.
Я использую версию 1.17.119.0 от 2005 года, которая, как заявлено, работает на Win95 — WinXP, но у меня работает и на Win7 и кажется даже на Win10.
Неоднократно пробовал пользоваться родным виндовым планировщиком, но потом опять уходил на nnCron Lite.Iv38
20.09.2019 14:04Ну как есть… Скорее был. В 2005 я его тоже использовал, но он не поддерживается с 2008. И помнится, под Win 7 он уже требовал каких-то телодвижений для нормальной работы.
Так-то крутая была софтина, но она написана на форте и скрипты под нее надо было писать на диалекте форта, а это чудовищно. Обратная польская запись хороша для машин, а не для людей.
red_led
20.09.2019 09:11Для тех, кто ещё не столкнулся с этой замечательной фичей, процитирую википедию:
Все условия (времени запуска) проверяются по «логическому И», кроме условий «день недели» и «день месяца» — указанные совместно, они обрабатываются по «логическому ИЛИ», то есть «по любому из дней», что отражено в документации (Ubuntu, Debian, FreeBSD). Однако такая логика неочевидна и не позволяет создать условие типа «первый понедельник каждого месяца» или «каждую пятницу в 13 число».
Приходится писать костыли в баше.
Xabik
20.09.2019 09:25Т.е. Cron ежеминутно проверяет таблицы и выполняет команды при совпадении с системным временем/датой. Конечно возможно это что-то необычное, но если необходимо запустить задачу в определенную секунду, миллисекунду?
VlK Автор
20.09.2019 10:05Секунды (тем более — миллисекунды) традиционно не делали, т.к. для всех пользователей каждую секунду проверять задачи — дело неблагодарное. Тут Крон не поможет.
Еще более гранулярно, на миллисекундах, фактически не имеет смысла делать, т.к. вся эта возня с шеллами, форками и экзеками уже занимает сотни миллисекунд, точности не выйдет.
В таких случаях вам понадобится что-то очень специальное.
evgenyk
20.09.2019 11:02Главная фишка систем, созданных на базе крона, это высокая надежность, достигнутая малой кровью. Т.е. например вы можете сделать скрипт, который падает каждый второй раз, запускать его из крона и все будет работать, лишь бы скрипт не портил данные.
Тогда для себя я решил, насчет секунд, что раз cron имеет точность в минуту, значит так и надо и этот гвоздь торчит из стула не просто так. Поэтому ИМХО, если нужна точность лучше, чем минуты, нужно делать это в своей программе, а из крона только проверять, не упала ли эта программа и перезапускать в случае падения.Akdmeh
20.09.2019 11:47А еще проще использовать тот же supervisord или systemctl, которые:
1) включаются вместе с ОС и имеют возможность запуска скрипта после запуска зависимостей (т.е., когда включилась база данных или обработчик очередей)
2) функция контроля (перезагружать ли после ошибки или штатного завершения работы, сколько попыток сделать, с каким диапазоном сделать перезапуск)
3) все это логгируется и вытаскивается через тот же journalctl
А сам скрипт исполняет работу и спит от 5 до 30 секунд. Чтобы не текла память, можно также завершать скрипт после получаса-часа работы, тогда systemctl автоматически включит задачу повторно.
Поэтому cron в этом смысле выглядит довольно странным и не универсальным решением.evgenyk
20.09.2019 12:04> Поэтому cron в этом смысле выглядит довольно странным и не универсальным решением.
«Каждая программа в своем развитии доходит до необходимости отправлять электронную почту» © Не помню кто.
В смысле каждая программа тащит в себя все фичи, до которых только может дотянуться.
Крон не странная программа, это очень простая штука, для 95% задач его хватает и в этом его сила. В простоте.
Универсальные решения это не UNIX way.Akdmeh
20.09.2019 12:07Для многих задач — да, я его активно использую для автозапуска многих задач.
Но для наблюдателя над ходом исполнения программы — возможно, это не самое эффективное решение, хотя и довольно простое. Конечно, все зависит от программы, которая запускается кроном, но supervisord или systemctl в этом плане более заточенные.evgenyk
20.09.2019 12:27ИМХО у них разное назначение.
cron — запуск скриптов, которые выполняют задачу и выходят.
supervisord, systemctl — запуск демонов.
evgenyk
20.09.2019 12:08> но если необходимо запустить задачу в определенную секунду, миллисекунду?
Если нужно запустить программу в нужную миллисекунду, то тут уже нехило пахнет специальной операционной системой реального времени да еще и с очерь хорошей синхронизацией времени.
simpleadmin
20.09.2019 14:58Шаги и интервалы можно смешивать:
# Запускается каждую вторую минуту первых десяти минут каждого часа 0-10/2 * * * * /path/to/exec
Я бы не использовал в описании интервально-шаговых событий слово «каждую». Отнюдь, не придираюсь, просто зачастую формулировку «каждую вторую» воспринимают как «каждую чётную».
С учётом данного примера с 0-й левой границей интервала это справедливо, но может вызывать непонимание конструкций вида
1-11/2 * * * * /path/to/exec
Разумеется и здесь можно говорить «каждую вторую начиная с 1», но сам стараюсь использовать формулировки «через две минуты», «с шагом в две минуты».
Groosha
20.09.2019 17:47Что раздражает у crontab, так это отсутствие (?) подтверждения при удалении через crontab -r. Особенно с учётом того, что клавиши "e" и "r" расположены рядом на клавиатуре, поэтому набирая "crontab -e" нужно быть КРАЙНЕ внимательным.
0xd34df00d
20.09.2019 21:14Не знаю, как вам, но мне немного грустно осознавать, что современное программирование с его склонностью к переусложнению и переабстрагированию уже давно не располагает к подобной простоте.
Тем временем в Gentoo как раз выпиливают vixie-cron как неподдерживаемый. В качестве одной из альтернатив как раз предлагают cronie.
Эх, а я помню, как в 2004-м первый раз игрался с гентой, и
emerge vixie-cron
был одним из шагов в хендбуке. Уходят эпохи.
evgenyk
ИМХО Было бы полезно так же упомянуть как unset некоторые переменные окружения для выполняемой программы. Ту же MAILTO, например.
VlK Автор
Насколько видел из кода, переменные нельзя удалить в самом кроне, только перезаписать типа MAILTO=""