Praleidęs daugiu nei 10 metų administruodamas serverius ir įrangą juose nusprendžiau pasidalinti patirtimi ir geriausiomis praktikomis kaip administruoti Cron darbus.
Periodinių užduočių valdymo sistema - Cron - viena populiariausių programinių įrangų Linux sistemose.
Apie ją turėti žinoti kiekvienas IT srityje dirbantis žmogus.
Sunkiai rastume projektą kuris kaip nors išgyventų be periodinių užduočių: el. laiškų siuntimo, naujienlaiškių siuntimo, laiškų tikrinimo, duomenų generavimo, duomenų importavimo, duomenų siuntimo ar kito darbo su trečiųjų šalių sistemomis (tarkime API).
Išmokti valdyti Cron nėra sudėtinga, o žinios, kuriomis pasidalinsiu, galėtų padėti išvengti dažnai pasitaikančių bėdų ar nesusipratimų.
Turinys
- Susipažinkime kas yra Cron
- Cron konfigūravimo pradžia
- Cron sintaksė
- Cron trumpiniai
- Cron trumpinys - @reboot
- Specialūs Cron simboliai
- Komentarai
- Cron komandų vykdymo laikai žmonių kalba
- Cron standartinė aplinka
- Laiškai apie klaidas - MAILTO
- Komandų standartinis žurnalas
- Komandų išvesties žurnalas
- Laiko juosta žurnaluose
- Komandų išvesties žurnalas (2 dalis)
- Kur nutrūko bash skriptas?
- Komandų žudymas
- Cron savininkas
- Cron atmintinė
Susipažinkime kas yra Cron
Cron yra paprasčiausia programa (demonas, jeigu tiksliau) kuris nuolat veikia operacinėje sistemoje ir laukia nurodyto laiko kada bus galima pavykdyti komandą.
Labai svarbu pabrėžti, kad Cron yra tik programinė įranga. Tai nėra operacinės sistemos dalis.
Dėl to, kad tai tik programa, ją galima išdiegti (apt uninstall cron
) arba jos vietoje įdiegti kitą periodinių užduočių valdymo programą.
Programų / konkurenčių yra tikra gausybė. Bet šis straipsnis nebus apie tai.
Kaip pavyzdys - jeigu parašėte programą, kuri patikrina elektroninius laiškus ir jeigu yra naujų - apie tai informuoja savininką SMS žinute, tai Cron yra būtent tai ko Jums reikia.
Tokiu atveju greičiausiai norėsite, kad programa veiktų tarkime kas 15 minučių. Būtent Cron ir bus ta sistema kuri paleis komandą nurodytu intervalu.
Cron konfigūravimo pradžia
Iš esmės viskas ko reikia, tai pavykdyti redagavimo komandą crontab -e
, nustatyti laiką kada komanda turi būti vykdoma ir įrašyti komandos aprašą.
Tipiškai po šios komandos įvykdymo atsidaro tekstų redaktorius (nano
arba vim
) kuriame rašysite kažką panašaus:
* 0 * * * komanda
Pirmi penki skaičiukai reiškia laiką kada vykdyti komandą: minutės (0 - 59), valandos (0 - 23), mėnesio diena (1-31), mėnuo (1 - 12) ir savaitės diena (0 - 7, kur tiek 0 tiek 7 reiškia sekmadienį).
Tokios laiko konfigūracijos užtenka suplanuoti užduotį pavykdyti praktiškai bet kada.
Žemiau keli praktiniai konfigūravimo pavyzdžiai.
Importuoti prekes iš kitos sistemos penkios minutės po vidurnakčio:
5 0 * * * importuoti-prekes
.
Kiekvieno mėnesio dešimtą dieną ir 9:45 min. išsiųsti algalapius:
45 9 10 * * siusti-algalapius
Sudėtingesnius laiko intervalus nurodyti taip pat galima. Pavyzdžiui kiekvieną pirmadienį 9 ryto tikrink ar „įkrito“ naujas užsakymas kas 5 minutes:
*/5 9 * * 1 kur-uzsakymai
Kas pusvalandį išsiųsk šimtą naujienlaiškių (kiekvieną penkioliktą ir keturiasdešimt penktą minutę):
15,45 * * * siusk-100-naujienlaiskiu
Arba tik darbo metu tikrink ar yra pranešimų iš klientų kas penkiolika minučių:
*/15 8-17 * * * ar-yra-laisku
O jeigu leidžiate darbuotojams atsikvėpti ir jie gali per pietų pertraukas nedirbti, tai:
*/15 8-12,13-17 * * * ar-yra-laisku
Daug kas nežino, tačiau žvaigždutė Cron nėra reikšmė „visada“. Žvaigždutė reiškia intervalą nuo mažiausios iki didžiausios reikšmės. Tarkime žvaigždutė (*
) minutėse reikštų 0-59
intervalą.
Cron sintaksė
Tuščios eilutės, be reikalo palikti tarpai ir tabuliacijos ženklai yra ignoruojami. Todėl jų galite palikti kiek tik norite.
Todėl dvi eilutės žemiau darys lygiai tą patį:
*/5 9 * * 1 kur-uzsakymai
*/5 9 * * 1 kur-uzsakymai
Geriau tuo nepiktnaudžiauti, o pasinaudoti praktiškesniu būdu tvarkingai komentuojant savo aprašus:
# minutės valandos diena mėnuo sav. diena komanda
*/5 9 * * 1 kur-uzsakymai
Kai turėsite labai daug eilučių - tikrai pravers.
Cron trumpiniai
Standartinis Cron taip pat duoda galimybę naudoti trumpinius, kurie yra labai patogūs tais atvejais, kada nereikia itin didelio tikslumo: @hourly
, @daily
, @weekly
, @monthly
, @annually
, @yearly
.
Tarkime kasdien komandą galima sutrumpinti su @daily
(abi eilutės darys tą patį):
0 0 * * * komanda
@daily komanda
Cron trumpinys - @reboot
Yra dar specialus trumpinys @reboot
- komanda bus įvykdyta tik vieną kartą po mašinos įjungimo.
Tai gali būti naudinga paleisti kokius servisus ar pranešimus tik kartą.
Arba jeigu neturi disciplinos - @reboot sleep 8h && poweroff
- kompiuterį išjungti po 8 valandų darbo.
Specialūs Cron simboliai
Nors komandos pavadinimą gali sudaryti bet kokie simboliai, labai svarbu žinoti, kad paprastai procentų simbolio naudoti negalite (%
). Prieš jį naudojant reikia pasvirojo brūkšnelio \
simbolio.
Šis simbolis Cron atitinka naujos eilutės simbolį. Ši problema gali lengvai atsirasti jeigu naudojate date
komandą, pvz:
@daily siusti-naujienlaiski > $(date '+%F').log
Tokią komandą Cron nukirps, ir ji taps:
@daily siusti-naujienlaiski > $(date '
Ir nesuveiks. Žemiau komanda darys ką Jūs norite:
@daily siusti-naujienlaiski > $(date '+\%F').log
Komentarai
Visada šaunu savo Cron įrašuose palikti komentarus. Juos galite palikti eilutę pradėję su #
simboliu:
# Siunčia reklamą kas savaitę
@weekly siusti-naujienlaiski
Verta žinoti, kad komentarų, priešingai nei programavimo kalbose, negalima niekur kitur rašyti. Pavyzdžiui žemiau esanti eilutė neveiks:
@weekly siusti-naujienlaiski # Siunčia reklamą kas savaitę (ši eilutė neteisinga)
Cron komandų vykdymo laikai žmonių kalba
Pradedančiąjam gali būti sunku konfigūruoti Cron laikus. Laimei, yra įrankių internete, kurie gali konvertuoti į žmonių kalbą Cron išraiškas (jeigu tiksliau - į anglų kalbą): www.freeformatter.com/cron-expression-generator-quartz.html.
Cron standartinė aplinka
Cron visada savo komandas pradeda su /bin/sh
aplinka. Kas čia yra gerai, kad taip taupoma atmintis ir greitis. Antras didžiulis privalumas - sh
yra visose Linux sistemose. Todėl Jūsų komandos tikrai veiks visur.
Kas blogai, kad tikrai ne kartą susidursite, jog kažkokia komanda neveiks, nes greičiausiai tikitės kažko daugiau nei sh
gali pasiūlyti. Juk /bin/bash
yra naujesnė sh
versija.
Skirtumų tarp sh
ir bash
yra labai daug. Dėl įdomumo tik kelis galima paminėti:
-
sh
neturi masyvų. -
Daugelis tipinių
bash
komandų neveiks:source
,function
,let
,declare
… -
Išvesties palaikymas su
<<<SKYRIUS
-
Ir daug daugiau.
Todėl jeigu reikia Cron skriptuose bash
aplinkos, galite įjungti ją visoms komandoms:
SHELL=/bin/bash
# Siunčia reklamą kas savaitę
@weekly siusti-naujienlaiski
Prieš ir po lygybės simbolio (=
) gali būti tarpai.
Deja tik keletas aplinkos kintamųjų šioje aplinkoje leidžiami: LOGNAME
(prisijungusiojo vardas), HOME
(kelias iki namų katalogo), SHELL
(aplinka kurią ką tik nagrinėjome), MAILTO
(skaitykite skyrių žemiau).
Laiškai apie klaidas - MAILTO
Greičiausias būdas gauti pranešimus kai kažkas blogai sistemoje - registruoti paštą su aplinkos kintamuoju MAILTO
:
MAILTO=klaidos@example.com
@weekly siusti-naujienlaiski
Čia reikėtų žinoti du dalykus. Be jokios konfigūracijos ir be jokio MAILTO=
sakinio, Cron visas išvestis išsiųs Cron komandų savininkui (tam kas sukūrė / įrašė komandas).
Išsiųsti laiškus į „išorę“ galima tik jeigu sistemoje yra įdiegtas ir sukonfigūruotas toks siuntimas (smtp
).
Komandų standartinis žurnalas
Visos komandos kurios bus paleistos galima rasti standartiniame syslog
žurnale:
grep CRON /var/log/syslog
Išvestis bus kažkas panašaus į tai:
Feb 12 13:17:01 server CRON[8029]: (root) CMD (cd / && run-parts --report /etc/cron.hourly)
Feb 12 13:30:01 server CRON[8292]: (root) CMD (docker exec -i grafikas-prod-web python manage.py send_pending_replacement_alerts)
Feb 12 13:45:02 server CRON[11724]: (root) CMD (docker exec -i grafikas-prod-web python manage.py send_email_about_coming_duties)
Deja, iš žurnalo bus ne tiek ir daug naudos. Matysite kada komanda buvo paleista, koks naudotojas paleido (šiame pavyzdyje root
) ir kokia komanda paleista.
Komandų išvesties žurnalas
Tai dalykas kuriuo teks pasirūpinti patiems. Cron nuostabus įrankis, tačiau didelę bėdą Cron atneša, kada reikia aiškintis kas įvyko konkrečiai. Komanda gal ir buvo paleista norėtu laiku, bet ar ji padarė būtent tai ko norėjote?
Tipinė Cron eilutė (su crontab -l
pagalba) iš mano serverių:
@daily /siusti-naujienlaiski >> /logs/siusti-naujienlaiski.$(date '+\%F').log 2>&1
Iš pavadinimų bus aišku, kad:
-
Komanda paleidžiama kiekvieną dieną (
@daily
). -
Komanda siunčia naujienlaiškį (
/siusti-naujienlaiski
).
Visada verta laikyti žurnalą ir jį pildyti. Dėl to naudojamas peradresavimas su >>
kuris sukuria naują žurnalo failą jeigu jo nėra arba papildo egzistuojantį žurnalą / failą.
Žurnalas su komandos išvestimis bus pildomas į /logs/siusti-naujienlaiski.2019-01-01.log
failą (failo vardo pavyzdys su data). Taip bus išsaugoma visa, su komanda susijusi, istorija: dienos data ir komandos išvestis. O su logrotate
pagalba tokius failus galima automatiškai archyvuoti. Pavyzdžiui paliekant tik paskutinius 7 žurnalus ar pan.
Asmeniškai aš žurnalus dar ir saugoju į atsarginių kopijų diską. Šią informaciją tikrai versta saugoti - kas jeigu paaiškėtų, kad Cron komanda (šiuo atveju /siusti-naujienlaiski
) veikė nekorektiškai jau kurį laiką? Žurnalų failas būtų viena iš vietų kur galima būtų tikrinti pasekmes.
Būtina tiek stdout
tiek stderr
išvestis nukreipti į tą patį failą su 2>&1
komandos gale - kad žurnale būtų įrašas jeigu įvyko kokios problemos.
Nenurodžius 2>&1
klaidos bus pristatomos į Linux naudotojo paskyrą su kuria vykdoma Cron komanda. Todėl patogu nurodyti administratoriaus el. pašto adresą (tikrą) jeigu sistemoje yra įdiegtas SMTP. Tą galima padaryti su MAILTO
apie kurį rašiau anksčiau.
Laiko juosta žurnaluose
Jeigu sistema sukonfigūruota kažkokiai laiko juostai (arba tiesiog standartiškai UTC) ir neturite galimybių pakeisti jos operacinėje sistemoje, gali būti pravartu panaudoti CRON_TZ
aplinkos kintamąjį. Cron gerbs laiko nustatymą ir tas laikas bus matomas žurnale.
Tai gali būti labai svarbu, jeigu sistemos laiko juosta yra UTC, o Jūsų paties juosta yra Lietuvos, tai žurnaluose turėsite 2-3 valandų skirtumą priklausomai nuo to kaip pasuktas/atsuktas laikrodis tam sezonui.
Laiko juostų vardus Linux sistemose sužinoti galima su komanda timedatectl list-timezones
.
Lietuvos laiko juosta apsirašytų: Europe/Vilnius
.
Gerai ir tai, kad sistema sureaguos į valandų pasukimus / atsukimus ir nieko papildomai konfigūruoti nereikės.
Komandų išvesties žurnalas (2 dalis)
Praeitame skyriuje minėtas žurnalų kūrimas patogus tik tada, kai galite sau leisti žurnalus įrašinėti į diską ir turite laiko sukonfigūruoti logrotate
, kad tie žurnalai neužimtų disko vietos. Disko vieta ypač svarbus kriterijus kuriant mikroservisus.
Todėl jeigu norite komandų išvestį registruoti tame pačiame syslog
faile, tuomet padės komanda logger
:
@hourly tikrinti-pasta 2>&1 | logger
Visas komandų klaidas rasite /var/log/syslog
. O Linux, be jokių konfigūracijų rotuos tą failą ir taip neužkimšite disko vietos.
Kur nutrūko bash skriptas?
Jeigu esate bash
mylėtojas kaip aš, tai greičiausiai ne viena Jūsų komanda bus bash
skriptas. Deja, kartais neaišku kas su skriptu negerai atsitiko ir kur/kodėl jis nutrūko.
Laimei, kiekvieną įvykdytą eilutę galima atspausdinti į išvestį jeigu skriptas turės set -x
nustatymą skripto pirmose eilutėse.
Pavyzdžiui:
#!/bin/bash
set -x
echo Testas | mail -s "Laiško tesstas" dev@example.com
Savo žurnaluose pamatysite eilutes kurios buvo vykdomos:
+ echo Testas
+ mail -s 'Laiško tesstas' dev@example.com
Taip galėsite greičiau atsekti kur programa nutrūko.
Komandų žudymas
Cron konfigūravimas iš esmės turi du iššūkius - žurnalų laikymą (rašiau apie tai aukščiau) ir komandų vykdymo „perlipimus“.
Kad viena komanda neprasidėtų kai kita dar nebaigė darbo, būtina nustatyti rėžius ir „užmušti“ komandų procesus jeigu komanda nespėjo padaryti savo darbo.
Praktikoje tai dažniausiai sutinkama su naujienlaiškių siuntimais. Tarkime kas 2 minutes siunčiate 100 el. laiškų. Cron aprašas galėtų būti toks:
*/2 * * * * siusti-naujienlaisius
Ir kaip dažnai atsitinka, kad komanda nespėja užbaigti savo darbo per planuojamą laiką.
Tarkime praktikoje išsiųsti 100 laiškų per 2 minutes neturėtų būti problema ir dažnu atveju tai veiks.
Tačiau bus dienų kai viskas veiks daug lėčiau arba pasieksite kažkokius el. laiškų siuntimo limitus kurie neleis per 2 minutes tiek išsiųsti.
Todėl jeigu parašyta komanda neužtikrina „persipynimo“ ir dvi komandos įvykdytos vienu metu sudubliuos darbą - greičiausiai papulsite į bėdą.
Čia nesusipratimų išvengti padės timeout
komanda, kuri irgi dažnai randama tarp standartiškai įdiegtų Linux komandų.
Naudojimosi aprašas būtų toks: timeout LAIKAS KOMANDA
. Pavyzdžiui: timeout 2m siusti-naujienlaiski
.
Cron apraše tai atrodytų taip:
*/2 * * * * timeout 2m siusti-naujienlaisius
Naudojantis timeout
reikia žinoti, kad sistemos žurnale syslog
rasite eilutę kuri pasakys, kad Cron komanda buvo nutraukta.
Iš patirties rekomenduočiau nenustatinėti timeout
ir Cron reikšmių santykiu 1:1.
Nes jeigu komanda vykdo kažką kas stipriai apkrauna CPU (tarkime iš kitos sistemos importuoja didelį srautą duomenų), tai nužudžius komandą ir vėl paleidus CPU vėl turės dirbti dideliais apkrovimais dar nespėjus tvarkingai išsivalyti savo atmintį ar Swap.
Turbūt geriausias variantas būtų turėti kažką panašaus:
*/5 * * * * timeout 2m siusti-naujienlaisius
O pritaikius skyrių apie žurnalus:
*/5 * * * * timeout 2m siusti-naujienlaisius >> /logs/siusti-naujienlaiskius.$(date '+\%F').log 2>&1
Cron savininkas
Geriausiai būtų, kad savo komandų neaprašinėtumėte kaip root
(tarkime sudo crontab -e
), nes taip sudarysite tikrai daug sąlygų saugumo spragoms atsirasti.
Tarkime jeigu Cron aprašas root
naudotojo paskyroje yra toks:
@hourly /usr/bin/siusti-naujienlaiski
Tai užtektų kam nors rašymo teisių į /usr/bin/siusti-naujienlaiski
ir viskas, šis žmogus galėtų lengvai pats tapti root
naudotoju. O tada - galėtų prieiti prie visko sistemoje.
Taigi visada Cron aprašinėkite su paprasto naudotoju ir nenaudokite jokių sudo
(nei komandoms aprašuose, nei paties Cron neredaguokite).
Cron atmintinė
Tikiuosi straipsnis buvo naudingas.
Jeigu reikėtų atmintinę parašyti („špargalkę“), tai atrodytų taip:
# Failas redaguojamas su: crontab -e
# Standartinis `sh` pakeičiamas į `bash` nes reikia spec. funkcijos `source`
SHELL = /bin/bash
# Naudoti lietuvišką laiko juostą
CRON_TZ = Europe/Vilnius
# Apie nesėkmes siųsti laišką sistemų administratoriui
MAILTO = dev@domenas.lt
# Importuoti prekes kartą į dieną (bet kada)
@daily importuoti-prekes
# Siųsti savaitės naujienlaiškį kiekvieno mėnesio pirmą dieną, o
# visus gavėjus išsaugoti /var/log/syslog faile
* * 1 * * siusti-savaitini-naujienlaiski | logger
# Tikrinti ar yra naujų užsakymų darbo laiku
*, 8-12,13-17 * * * ar-yra-uzsakymu > /logs/uzsakymai.log 2>&1
# Paleisti Django komandą dėl kurios reikia bash
@hourly source .virtualenv/bin/activate && python manage.py siusti-naujienas
Jeigu patys naudojate Cron alternatyvas (anacron
, fcron
, hcron
ar kažką kito) - būtų smalsu sužinoti dėl ko juos naudojate.