Cron v Linuxu: historie, použití a zařízení

Cron v Linuxu: historie, použití a zařízení

Klasik psal, že happy hours se nedodržují. V těch divokých časech nebyli ani programátoři, ani Unix, ale dnes to vědí jistě: cron bude sledovat čas místo nich.

Nástroje příkazového řádku jsou pro mě slabostí i rutinou. sed, awk, wc, cut a další staré programy jsou denně spouštěny pomocí skriptů na našich serverech. Mnohé z nich jsou navrženy jako úkoly pro cron, plánovač ze 70. let.

Dlouho jsem cron používal povrchně, aniž bych se ponořil do detailů, ale jednoho dne, když jsem se při spouštění skriptu potýkal s chybou, rozhodl jsem se na to důkladně prozkoumat. Tak vznikl tento článek, během kterého jsem se seznámil s POSIX crontabem, hlavními možnostmi cronu v oblíbených linuxových distribucích a designem některých z nich.

Používáte Linux a spouštíte úlohy v cronu? Zajímá vás architektura systémových aplikací v Unixu? Pak jsme na cestě!

Obsah

Původ druhů

Pravidelné spouštění uživatelských nebo systémových programů je samozřejmostí ve všech operačních systémech. Proto programátoři rozpoznali potřebu služeb, které umožňují centralizované plánování a provádění úkolů po velmi dlouhou dobu.

Operační systémy podobné Unixu vystopují svůj původ až k Unixu verze 7, který byl vyvinut v 70. letech minulého století v Bell Labs, včetně slavného Kena Thompsona. Verze 7 Unix se také dodává s cronem, službou pro pravidelně spouštěné úlohy superuživatele.

Typický moderní cron je jednoduchý program, ale algoritmus původní verze byl ještě jednodušší: služba se jednou za minutu probudila, načetla tabulku s úkoly z jednoho souboru (/etc/lib/crontab) a provedla pro superuživatele. ty úkoly, které měly být dokončeny v aktuální minutě.

Následně byly vylepšené verze jednoduché a užitečné služby dodávány se všemi operačními systémy podobnými Unixu.

Zobecněné popisy formátu crontab a základní principy utility byly v roce 1992 zahrnuty do hlavního standardu pro operační systémy typu Unix – POSIX – a tak se cron z de facto standardu stal standardem de jure.

V roce 1987 Paul Vixie po dotazování uživatelů Unixu na přání cronu vydal další verzi démona, která opravila některé problémy tradičního cronu a rozšířila syntaxi tabulkových souborů.

Od třetí verze Vixie cron začal splňovat požadavky POSIX, navíc měl program liberální licenci, respektive neexistovala žádná licence, kromě přání v README: autor neposkytuje záruky, jméno autora nelze smazat a program lze prodávat pouze společně se zdrojovým kódem. Ukázalo se, že tyto požadavky jsou kompatibilní s principy svobodného softwaru, který v těch letech získával na popularitě, takže některé klíčové distribuce Linuxu, které se objevily na počátku 90. let, vzaly Vixie cron za systémový a stále jej vyvíjejí.

Konkrétně Red Hat a SUSE vyvíjejí fork Vixie cron - cronie, zatímco Debian a Ubuntu používají původní vydání Vixie cronu s mnoha záplatami.

Podívejme se nejprve na uživatelsky definovaný nástroj crontab POSIX a poté se podívejme na rozšíření syntaxe představená ve Vixie cronu a použití variací Vixie cronu v populárních linuxových distribucích. A nakonec třešničkou na dortu je rozebrání zařízení cron démona.

POSIX crontab

Zatímco původní cron vždy běžel pro superuživatele, moderní plánovače se spíše zabývají běžnými uživatelskými úkoly, což je bezpečnější a pohodlnější.

Crony se dodávají se sadou dvou programů: neustále běžícího démona cron a uživatelsky přístupný nástroj crontab. Ten vám umožňuje upravovat tabulky úloh specifické pro každého uživatele v systému, zatímco démon spouští úlohy z uživatelských a systémových tabulek.

В standard POSIX chování démona není nijak popsáno a formalizován je pouze uživatelský program crontab. Existence mechanismů pro spouštění uživatelských úloh je samozřejmě naznačena, ale není podrobně popsána.

Zavoláním obslužného programu crontab lze provést čtyři věci: upravit uživatelskou tabulku úloh v editoru, načíst tabulku ze souboru, zobrazit aktuální tabulku úloh a vymazat tabulku úloh. Příklady nástroje crontab:

crontab -e # редактировать таблицу задач
crontab -l # показать таблицу задач
crontab -r # удалить таблицу задач
crontab path/to/file.crontab # загрузить таблицу задач из файла

Při zavolání crontab -e bude použit editor zadaný ve standardní proměnné prostředí EDITOR.

Samotné úkoly jsou popsány v následujícím formátu:

# строки-комментарии игнорируются
#
# задача, выполняемая ежеминутно
* * * * * /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

Prvních pět polí záznamů: minuty [1..60], hodiny [0..23], dny v měsíci [1..31], měsíce [1..12], dny v týdnu [0.. 6], kde 0 — neděle. Poslední, šesté, pole je řádek, který bude proveden standardním příkazovým interpretem.

V prvních pěti polích mohou být hodnoty uvedeny oddělené čárkami:

# задача, выполняемая в первую и десятую минуты каждого часа
1,10 * * * * /path/to/exec -a -b -c

Nebo s pomlčkou:

# задача, выполняемая в каждую из первых десяти минут каждого часа
0-9 * * * * /path/to/exec -a -b -c

Přístup uživatelů k plánování úloh je v POSIX regulován soubory cron.allow a cron.deny, které obsahují seznam uživatelů s přístupem crontab a uživatelů bez přístupu k programu. Umístění těchto souborů norma nijak neupravuje.

Spuštěné programy musí podle standardu předat alespoň čtyři proměnné prostředí:

  1. HOME je domovský adresář uživatele.
  2. LOGNAME - přihlášení uživatele.
  3. PATH - cesta, kde můžete najít standardní systémové nástroje.
  4. SHELL je cesta k použitému shellu.

Je pozoruhodné, že POSIX neříká nic o tom, odkud pocházejí hodnoty těchto proměnných.

Bestseller - Vixie cron 3.0pl1

Společným předkem populárních variant cronu je Vixie cron 3.0pl1, představený v mailing listu comp.sources.unix v roce 1992. Podrobněji zvážíme hlavní rysy této verze.

Vixie cron se dodává ve dvou programech (cron a crontab). Démon je jako obvykle zodpovědný za čtení a spouštění úloh ze systémové tabulky úloh a tabulek jednotlivých uživatelských úloh, zatímco obslužný program crontab je zodpovědný za úpravy uživatelských tabulek.

Tabulka úloh a konfigurační soubory

Tabulka úloh superuživatele se nachází v /etc/crontab. Syntaxe systémové tabulky odpovídá syntaxi Vixie cron s tou výjimkou, že šestý sloupec uvádí jméno uživatele, jehož jménem je úloha spuštěna:

# Запускается ежеминутно от пользователя vlad
* * * * * vlad /path/to/exec

Tabulky běžných uživatelských úloh se nacházejí v /var/cron/tabs/username a používají společnou syntaxi. Když spustíte obslužný program crontab jako uživatel, upravují se tyto soubory.

Seznamy uživatelů, kteří mají přístup ke crontab, jsou spravovány v souborech /var/cron/allow a /var/cron/deny, kde stačí zadat uživatelské jméno na samostatný řádek.

Rozšířená syntaxe

Ve srovnání s POSIX crontab obsahuje řešení Paula Wixeyho některé velmi užitečné úpravy syntaxe tabulek úloh nástroje.

Byla k dispozici nová syntaxe tabulky: například můžete zadat dny v týdnu nebo měsíce podle názvu (Po, Út atd.):

# Запускается ежеминутно по понедельникам и вторникам в январе
* * * Jan Mon,Tue /path/to/exec

Můžete určit krok, kterým se úlohy spouštějí:

# Запускается с шагом в две минуты
*/2 * * * Mon,Tue /path/to/exec

Kroky a intervaly lze kombinovat:

# Запускается с шагом в две минуты в первых десять минут каждого часа
0-10/2 * * * * /path/to/exec

Jsou podporovány intuitivní alternativy k obvyklé syntaxi (reboot, ročně, ročně, měsíčně, týdně, denně, půlnoc, hodinově):

# Запускается после перезагрузки системы
@reboot /exec/on/reboot
# Запускается раз в день
@daily /exec/daily
# Запускается раз в час
@hourly /exec/daily

Prostředí provádění úloh

Vixie cron umožňuje měnit prostředí spouštěných aplikací.

Proměnné prostředí USER, LOGNAME a HOME nejsou poskytovány pouze démonem, ale jsou převzaty ze souboru passwd. Proměnná PATH je nastavena na "/usr/bin:/bin" a proměnná SHELL je nastavena na "/bin/sh". Hodnoty všech proměnných kromě LOGNAME lze změnit v uživatelských tabulkách.

Některé proměnné prostředí (především SHELL a HOME) používá sám cron ke spuštění úlohy. Takto může vypadat použití bash místo výchozího sh ke spouštění uživatelských úloh:

SHELL=/bin/bash
HOME=/tmp/
# exec будет запущен bash-ем в /tmp/
* * * * * /path/to/exec

Nakonec budou všechny proměnné prostředí definované v tabulce (buď používané cronem nebo potřebné pro proces) předány běžící úloze.

K úpravě souborů používá crontab editor určený v proměnné prostředí VISUAL nebo EDITOR. Pokud prostředí, kde byl crontab spuštěn, nemá tyto proměnné definované, pak se použije "/usr/ucb/vi" (ucb je pravděpodobně University of California, Berkeley).

cron na Debianu a Ubuntu

Debian a jeho vývojáři vydali silně upravená verze verze Vixie cron 3.0pl1. V syntaxi souborů tabulky nejsou žádné rozdíly, pro uživatele je to stejný cron Vixie. Největší nové funkce: Podpora syslog, SELinux и PAM.

Z méně nápadných, ale hmatatelných změn - umístění konfiguračních souborů a tabulek úloh.

Uživatelské tabulky v Debianu jsou umístěny v adresáři /var/spool/cron/crontabs, systémová tabulka je tam stále - v /etc/crontab. Tabulky úloh specifických pro balíčky Debianu jsou umístěny v /etc/cron.d, odkud je démon cron automaticky čte. Řízení přístupu uživatelů se řídí soubory /etc/cron.allow a /etc/cron.deny.

Výchozí shell je stále /bin/sh, což je v Debianu malý shell kompatibilní s POSIX pomlčka, spuštěn bez načtení jakékoli konfigurace (v neinteraktivním režimu).

Samotný Cron se v posledních verzích Debianu spouští přes systemd a spouštěcí konfiguraci lze nalézt v /lib/systemd/system/cron.service. V konfiguraci služby není nic zvláštního, jakoukoli jemnější správu úloh lze provádět prostřednictvím proměnných prostředí deklarovaných přímo v crontab každého uživatele.

cronie na RedHat, Fedora a CentOS

cronie - vidlice cronu Vixie verze 4.1. Stejně jako v Debianu se syntaxe nezměnila, ale přidala podporu pro PAM a SELinux, práci v clusteru, sledování souborů pomocí inotify a další funkce.

Výchozí konfigurace je na obvyklých místech: systémová tabulka je v /etc/crontab, balíčky vkládají své tabulky do /etc/cron.d, uživatelské tabulky jdou do /var/spool/cron/crontabs.

Démon běží pod systemd, konfigurace služby je /lib/systemd/system/crond.service.

V distribucích podobných Red Hatu je výchozí spouštění /bin/sh, což je standardní bash. Všimněte si, že při spouštění úloh cron přes /bin/sh se bash shell spustí v režimu kompatibilním s POSIX a nečte žádnou další konfiguraci, běží v neinteraktivním režimu.

cronie v SLES a openSUSE

Německá distribuce SLES a její derivát openSUSE používají stejného přítele. Démon se zde také spouští pod systemd, konfigurace služby se nachází v /usr/lib/systemd/system/cron.service. Konfigurace: /etc/crontab, /etc/cron.d, /var/spool/cron/tabs. /bin/sh je stejný bash běžící v neinteraktivním režimu kompatibilním s POSIX.

Zařízení Vixie cron

Moderní potomci cronu se ve srovnání s Vixie cronem radikálně nezměnili, ale stále získali nové funkce, které nejsou nutné k pochopení principů programu. Mnoho z těchto rozšíření je nedbalých a matou kód. Původní zdrojový kód cronu od Paula Wixieho je radost číst.

Proto jsem se rozhodl analyzovat zařízení cron na příkladu programu společného pro obě větve vývoje cronu - Vixie cron 3.0pl1. Zjednoduším příklady odstraněním ifdefs, které znesnadňují čtení, a vynecháním sekundárních detailů.

Práce démona lze rozdělit do několika fází:

  1. Inicializace programu.
  2. Sbírejte a aktualizujte seznam úloh ke spuštění.
  3. Práce hlavní cronové smyčky.
  4. Spusťte úkol.

Vezměme je popořadě.

Inicializace

Při spuštění, po kontrole argumentů procesu, cron nainstaluje obslužné rutiny signálů SIGCHLD a SIGHUP. První zaznamená záznam o ukončení podřízeného procesu, druhý zavře deskriptor souboru log souboru:

signal(SIGCHLD, sigchld_handler);
signal(SIGHUP, sighup_handler);

Démon cron v systému běží vždy sám, pouze v roli superuživatele a z hlavního adresáře cron. Následující volání vytvoří soubor zámku s PID procesu démona, ujistěte se, že uživatel je správný, a změňte aktuální adresář na hlavní adresář:

acquire_daemonlock(0);
set_cron_uid();
set_cron_cwd();

Je nastavena výchozí cesta, která bude použita při spouštění procesů:

setenv("PATH", _PATH_DEFPATH, 1);

Dále je proces „démonizován“: vytvoří podřízenou kopii procesu voláním fork a novou relaci v podřízeném procesu (zavoláním setsid). Nadřazený proces již není potřeba – a ukončí se:

switch (fork()) {
case -1:
    /* критическая ошибка и завершение работы */
    exit(0);
break;
case 0:
    /* дочерний процесс */
    (void) setsid();
break;
default:
    /* родительский процесс завершает работу */
    _exit(0);
}

Ukončení nadřazeného procesu uvolní zámek souboru zámku. Kromě toho musíte aktualizovat PID v souboru na dítě. Poté je databáze úkolů naplněna:

/* повторный захват лока */
acquire_daemonlock(0);

/* Заполнение БД  */
database.head = NULL;
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);

Poté cron přejde do hlavní pracovní smyčky. Ještě předtím se ale vyplatí podívat se na načítání seznamu úkolů.

Shromažďování a aktualizace seznamu úkolů

Funkce load_database je zodpovědná za načtení seznamu úloh. Kontroluje hlavní systémový crontab a adresář uživatelských souborů. Pokud se soubory a adresář nezměnily, seznam úloh se znovu nepřečte. V opačném případě se začne tvořit nový seznam úkolů.

Načtení systémového souboru se speciálními názvy souborů a tabulek:

/* если файл системной таблицы изменился, перечитываем */
if (syscron_stat.st_mtime) {
    process_crontab("root", "*system*",
    SYSCRONTAB, &syscron_stat,
    &new_db, old_db);
}

Načítání vlastních tabulek ve smyčce:

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);
}

Poté je stará databáze nahrazena novou.

Ve výše uvedených příkladech volání process_crontab zkontroluje existenci uživatele odpovídajícího názvu souboru tabulky (pokud to není superuživatel) a poté zavolá load_user. Ten již čte samotný soubor řádek po řádku:

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;
    }
}

Zde je buď proměnná prostředí nastavena (řetězce jako VAR=hodnota) funkcemi load_env / env_set, nebo je popis úlohy (* * * * * /cesta/k/exec) načten funkcí load_entry.

Vstupní entita, která vrací load_entry, je náš úkol, který je umístěn v obecném seznamu úkolů. V samotné funkci se provádí podrobná analýza formátu času, ale více nás zajímá tvorba proměnných prostředí a parametry spouštění úlohy:

/* пользователь и группа для запуска задачи берутся из 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);

Se skutečným seznamem úkolů funguje hlavní smyčka.

Hlavní smyčka

Původní cron z verze 7 Unix fungoval docela jednoduše: znovu načetl konfiguraci ve smyčce, spustil úkoly aktuální minuty jako superuživatel a spal až do začátku další minuty. Tento jednoduchý přístup na starších počítačích vyžadoval příliš mnoho zdrojů.

V SysV byla navržena alternativní verze, kde démon spí buď do nejbližší minuty, pro kterou je úloha definována, nebo po dobu 30 minut. Prostředky pro opětovné čtení konfigurace a kontrolu úloh v tomto režimu spotřebovaly méně, ale rychle aktualizovat seznam úloh se stalo nepohodlným.

Vixie cron se vrátil ke kontrole seznamů úloh jednou za minutu, protože na konci 80. let bylo na standardních unixových strojích mnohem více zdrojů:

/* первичная загрузка задач */
load_database(&database);
/* запустить задачи, поставленные к выполнению после перезагрузки системы */
run_reboot_jobs(&database);
/* сделать TargetTime началом ближайшей минуты */
cron_sync();
while (TRUE) {
    /* выполнить задачи, после чего спать до TargetTime с поправкой на время, потраченное на задачи */
    cron_sleep();

    /* перечитать конфигурацию */
    load_database(&database);

    /* собрать задачи для данной минуты */
    cron_tick(&database);

    /* перевести TargetTime на начало следующей минуты */
    TargetTime += 60;
}

Funkce cron_sleep se přímo podílí na provádění úloh, volá funkce job_runqueue (výčet a spuštění úloh) a do_command (spuštění každé jednotlivé úlohy). Poslední funkci stojí za to analyzovat podrobněji.

Spuštění úkolu

Funkce do_command se provádí v dobrém unixovém stylu, to znamená, že dělá rozvětvení pro asynchronní provádění úlohy. Rodičovský proces pokračuje ve spouštění úloh, podřízený proces připravuje proces úlohy:

switch (fork()) {
case -1:
    /*не смогли выполнить fork */
    break;
case 0:
    /* дочерний процесс: на всякий случай еще раз пробуем захватить главный лок */
    acquire_daemonlock(1);
    /* переходим к формированию процесса задачи */
    child_process(e, u);
    /* по завершению дочерний процесс заканчивает работу */
    _exit(OK_EXIT);
    break;
default:
    /* родительский процесс продолжает работу */
    break;
}

Child_process má spoustu logiky: přebírá na sebe standardní výstup a chybové proudy, takže je lze následně odeslat na poštu (pokud je v tabulce úloh uvedena proměnná prostředí MAILTO), a nakonec čeká pro dokončení hlavního procesu úkolu.

Proces úlohy je tvořen dalším rozvětvením:

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;
}

To je obecně celý cron. Vynechal jsem některé zajímavé detaily, například účtování pro vzdálené uživatele, ale nastínil jsem to hlavní.

Doslov

Сron je překvapivě jednoduchý a užitečný program vytvořený podle nejlepších tradic světa Unixu. Nedělá nic extra, ale svou práci dělá pozoruhodně už několik desítek let. Kontrola kódu pro verzi, která je dodávána s Ubuntu, netrvala ani hodinu a užil jsem si spoustu legrace! Doufám, že jsem se o to s vámi mohl podělit.

Nevím jak vy, ale mě je trochu smutno, když si uvědomuji, že moderní programování se svou tendencí k překomplikování a přílišné abstrahování už dávno takové jednoduchosti nenapomáhá.

Ke cronu existuje mnoho moderních alternativ: systemd-timery umožňují organizovat složité systémy se závislostmi, ve fcronu můžete flexibilněji regulovat spotřebu zdrojů úkolů. Osobně mi ale vždy stačil ten nejjednodušší crontab.

Zkrátka milujte Unix, používejte jednoduché programy a nezapomínejte číst manu pro vaši platformu!

Zdroj: www.habr.com

Přidat komentář