Tarkibga o'tish

cron muhiti va log'lari

Terminalda yozilganda mukammal ishlaydigan buyruq crontab’ga qo'shilgach ham xuddi shunday ishlashi kafolatlanmagan. Vaqtni eng ko'p yeydigan cron muammolarining eng katta toifasi aynan shu: skript qo'lda xatosiz ishlaydi, cron orqali esa go'yo hech narsa qilmaydi. Sababi deyarli har doim bitta — cron buyruqni interaktiv terminal sessiyasidan ancha kambag'al muhitda ishga tushiradi.

Nimalarni o'rganasiz

Bu maqola oxirida siz quyidagilarni qila olasiz:

  • cron muhiti interaktiv terminal muhitidan qanday farq qilishini tushuntirish;
  • crontab ichida PATH va boshqa muhit o'zgaruvchilarini to'g'ri o'rnatish;
  • chiqishni email orqali olish uchun MAILTO ni sozlash yoki uni o'chirib qo'yish;
  • date kabi buyruqlar cron ostida buzilmasligi uchun % belgisini to'g'ri ekranlash (escape qilish);
  • cron vazifasining natijasini journalctl va syslog orqali tekshirish;
  • "qo'lda ishlaydi, cron ostida ishlamaydi" muammosini tizimli diagnostika qilish.

cron muhiti terminal muhitidan qanday farq qiladi

Terminalga kirganingizda shell (bash) .bashrc va .bash_profile fayllaringizni o'qiydi. Ular odatda PATH ni kengaytiradi, alias va funksiyalarni aniqlaydi. cron daemon esa buyruqni login bo'lmagan, interaktiv bo'lmagan minimal muhitda ishga tushiradi — bu fayllarning birortasi ham o'qilmaydi.

echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/<username>/.local/bin

Endi buni cron’ning o'zi ko'radigan PATH bilan taqqoslang. Buning uchun vaqtinchalik crontab vazifasi orqali env chiqishini faylga yozamiz:

crontab -e
* * * * * env > /tmp/cron-env.txt

Bir daqiqa kuting, natijani tekshiring, keyin vazifani olib tashlang:

cat /tmp/cron-env.txt
SHELL=/bin/sh
PATH=/usr/bin:/bin
LOGNAME=<username>
HOME=/home/<username>

Farq darhol ko'rinadi: PATH ancha qisqa (/usr/bin:/bin), .local/bin va qo'lda qo'shilgan boshqa kataloglar yo'q, SHELL esa /bin/sh ga o'rnatilgan. Bu odatda bash ga qaraganda cheklanganroq, POSIX’ga yo'naltirilgan shell.

Tip

Sinovdan keyin * * * * * env > /tmp/cron-env.txt qatorini crontab -e bilan olib tashlang. Aks holda u hech qanday foydasiz har daqiqada faylni qayta yozib turaveradi.

Crontab ichida PATH ni tuzatish

Terminalda ishlaydigan, lekin cron ostida "command not found" deb xabar beradigan buyruqning sababi deyarli har doim shu qisqaroq PATH. Ikki xil yechim bor:

1-variant — buyruqda absolyut yo'ldan foydalanish (tavsiya etiladi):

0 2 * * * /usr/local/bin/backup.sh

2-variant — crontab boshida PATH ni aniq o'rnatish:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

0 2 * * * backup.sh

Crontab boshida KEY=value ko'rinishida yozilgan qatorlar muhit o'zgaruvchilarini o'rnatadi va ulardan pastdagi har bir vazifaga ta’sir qiladi. Ikkinchi yondashuv qulayroq ko'rinishi mumkin, lekin birinchisi ishonchliroq: skript qaysi katalogdan chaqirilishidan qat’i nazar bir xil ishlaydi va keyinchalik boshqa serverga ko'chirilsa, ma’lum bir PATH sozlamasiga bog'liq bo'lib qolmaydi.

Ishchi katalog ham terminalnikidek emas

cron odatda buyruqni foydalanuvchining $HOME katalogidan ishga tushiradi, siz uni sinab ko'rgan terminal esa boshqa joyda turgan bo'lishi mumkin. Nisbiy yo'llarga yoki nisbiy fayl havolalariga tayanadigan skript aynan shu sababli ishlamay qolishi mumkin:

0 2 * * * cd /opt/myapp && ./backup.sh

cd /opt/myapp && skript ishga tushishidan oldin ishchi katalogni aniq belgilaydi. Bu skript ichidagi nisbiy yo'llar (masalan, ./config.yaml) to'g'ri topilishini kafolatlaydi.

Chiqishni boshqarish: MAILTO

Standart holatda, agar buyruq biror chiqish (stdout yoki stderr) bersa, cron uni email orqali yuborishga urinadi (tizimda mahalliy pochta yetkazib berish sozlangan deb hisoblanadi):

[email protected]

0 2 * * * /usr/local/bin/backup.sh

Fayl boshida o'rnatilgan bu qator undan pastdagi har bir vazifaning chiqishini ko'rsatilgan manzilga yuboradi. Ko'plab production serverlarda mahalliy pochta umuman sozlanmagan bo'ladi. Bunday holatda email "yuborishga urinadi", hech qayerga yetib bormaydi va shunchaki mahalliy pochta navbatida to'planib qoladi.

Agar email umuman kerak bo'lmasa, uni butunlay o'chirib qo'yish mumkin:

MAILTO=""

Chiqishni faylga yo'naltirish

Amalda eng ishonchli yondashuv — umuman email’ga tayanmasdan, chiqishni aniq faylga yuborish:

0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

>> /var/log/backup.log standart chiqishni faylni qisqartirmasdan uning oxiriga qo'shadi, 2>&1 esa xatolar oqimini (stderr) o'sha faylga yo'naltiradi. Bu Oqimlar, pipe va yo'naltirish maqolasida ko'rib chiqilgan o'sha yo'naltirish mexanizmi. Farqi faqat shundaki, u interaktiv terminalda emas, cron muhitida ishlayapti.

Warning

Agar skript allaqachon o'zi log yozsa — masalan, bash scripting modulidagi backup skripti dagi log() funksiyasi — ustidan >> file 2>&1 qo'shish baribir foydali. Chunki u skriptning o'zi qayta ishlamaydigan xatolarni (masalan, interpreter ishga tusha olmaganini) ushlaydi. Ikkalasi bir-biriga zid emas, aksincha bir-birini to'ldiradi.

Crontab’da % belgisi maxsus

Crontab buyrug'i ichida ekranlanmagan % oddiy foiz belgisi emas. cron birinchi % ni yangi qatorga almashtiradi va undan keyingi hamma narsani buyruqqa standart kirish (stdin) sifatida uzatadi. Bu % ishlatadigan har qanday buyruqni — ko'pincha date ni — jimgina buzadi:

0 2 * * * /usr/local/bin/backup.sh --out backup-$(date +%Y%m%d).tar.gz

Bu yerda cron %Y%m%d... ni yangi qator va stdin’ga aylantiradi. Natijada skript qirqilgan --out backup- ni ko'radi va umuman sana bo'lmaydi — buni tushuntiradigan hech qanday xato ham chiqmaydi. Har bir % teskari slash bilan ekranlanishi kerak:

0 2 * * * /usr/local/bin/backup.sh --out backup-$(date +\%Y\%m\%d).tar.gz

Tozaroq yechim — % ishlatadigan hamma narsani skript ichiga ko'chirish. U yerda oddiy shell qoidalari amal qiladi va ekranlash kerak bo'lmaydi. Crontab qatori esa shunchaki skriptni chaqiradi, unda % umuman bo'lmaydi. Bu crontab qatorlarini ixcham saqlab, haqiqiy mantiqni skript faylida yozish uchun yana bir sabab.

Chiqishni journalctl va syslog orqali kuzatish

cron vazifasi har safar ishga tushganda — chiqish beradimi yoki yo'qmi — daemon’ning o'zi bu faktni tizim log'iga yozadi. Ubuntu Server’da buni journalctl orqali ko'rish mumkin:

journalctl -u cron.service --since today
Jul 26 02:00:01 myserver CRON[18422]: (root) CMD (/usr/local/bin/backup.sh >> /var/log/backup.log 2>&1)

Bu yozuv vazifa muvaffaqiyatli tugaganini emas, balki ishga tushganini tasdiqlaydi. Buyruqning o'zi xato bilan tugaganmi yoki yo'qmi, cron baribir bir xil turdagi yozuvni qoldiradi. Muvaffaqiyatni muvaffaqiyatsizlikdan ajratish uchun skriptning o'z chiqishini yoki uning exit kodini tekshirish kerak.

Xuddi shu yozuv an’anaviy matnli log'da ham bor (Linux loglari maqolasida ko'rib chiqilganidek, rsyslog xabarlarni journald dan o'qib, faylga yozadi):

grep CRON /var/log/syslog | tail -n 5
Jul 26 02:00:01 myserver CRON[18422]: (root) CMD (/usr/local/bin/backup.sh >> /var/log/backup.log 2>&1)

RHEL/CentOS/Rocky oilasida bu yozuvlar odatda /var/log/cron ga tushadi, syslog esa umuman bo'lmasligi mumkin. Aniq joy distributivga qarab farq qilgani uchun birinchi tekshiruvni journalctl -u crond dan boshlash universalroq.

Amaliy vaziyat: "qo'lda ishlaydi, cron ostida ishlamaydi"

Muammo. /usr/local/bin/sync-reports.sh skripti terminaldan qo'lda ishga tushirilganda muammosiz ishlaydi, lekin crontab’da sozlangan 0 3 * * * sync-reports.sh vazifasi hech qanday ko'rinadigan natija bermaydi.

1-qadam — vazifa umuman ishga tushayotganini tasdiqlash:

journalctl -u cron.service --since "1 hour ago" | grep sync-reports
Jul 26 03:00:01 myserver CRON[19011]: (root) CMD (sync-reports.sh)

Vazifa ishga tushgan — demak, muammo cron’ning o'zida emas, buyruq ishlayotgan sharoitlarda.

2-qadam — PATH va nisbiy yo'l bo'yicha shubhani tekshirish:

which sync-reports.sh
/usr/local/bin/sync-reports.sh

Fayl PATH da bor, lekin crontab qatorida shunchaki sync-reports.sh deb yozilgan. Terminalda u PATH orqali topiladi, lekin cron’ning qisqaroq PATH i ham /usr/local/bin ni o'z ichiga oladimi, buni yuqoridagi env > /tmp/cron-env.txt usuli bilan alohida tekshirish kerak.

3-qadam — absolyut yo'l bilan va chiqishni log'ga yo'naltirib qayta urinish:

0 3 * * * /usr/local/bin/sync-reports.sh >> /var/log/sync-reports.log 2>&1

Tekshirish:

tail -n 20 /var/log/sync-reports.log
rsync: command not found

Xulosa. Endi haqiqiy sabab ko'rinib turibdi: skript ichida ishlatilgan rsync buyrug'i cron’ning qisqartirilgan PATH ida topilmayapti. Yechim — skript ichida rsync uchun absolyut yo'ldan (/usr/bin/rsync) foydalanish yoki crontab boshida PATH ni kengaytirish.

Keng tarqalgan xatolar

  • Avval log'ni tekshirmasdan "ishlamayapti" deb xulosa qilish. journalctl -u cron.service ikkita butunlay boshqa muammoni darhol ajratib beradi: vazifa umuman ishga tushdimi yoki ishga tushib, keyin muvaffaqiyatsiz tugadimi. Birinchi diagnostika qadami sifatida uni o'tkazib yubormaslik kerak.
  • MAILTO ni hech qachon tekshirmasdan unga tayanish. Mahalliy pochta servisi sozlanmagan serverda xabar "yuboriladi" deb hisoblash xato — xabar hech qayerga yetib bormaydi. Production’da chiqishni faylga yoki markazlashgan log'ga yo'naltirish ishonchliroq.
  • Interaktiv shell bilan sinab, production’da boshqa shell ostida ishga tushirish. Agar skript #!/bin/bash bilan boshlansa-yu, bash ga xos konstruksiyalardan foydalansa, odatda muammo bo'lmaydi — cron skript faylini /bin/sh orqali emas, to'g'ridan-to'g'ri ishga tushiradi. Muammoning ko'proq uchraydigan sababi teskarisi: oddiy POSIX sh semantikasini nazarda tutib yozilgan skript aslida faqat bash da bor xususiyatga tayanib chiqadi. Har ikki holatda ham skriptning execute ruxsatini (chmod +x) va shebang qatorini tekshirish foydali birinchi qadam.

Mashqlar

  1. * * * * * env > /tmp/cron-env.txt vazifasi yordamida cron’ning PATH qiymatini terminalingizdagi $PATH bilan taqqoslang va farqlarni yozib qo'ying.
  2. Har daqiqada ishlaydigan va o'z chiqishini >> ~/cron-test.log 2>&1 bilan ~/cron-test.log ga qo'shadigan test vazifasi yarating. Bir necha daqiqadan keyin journalctl -u cron.service da ko'rganingizni faylning o'zi bilan taqqoslang.
  3. Crontab boshiga MAILTO="" qo'shing va u nimani o'chirib qo'yishini tushuntiring (haqiqiy pochta serveri bo'lmasa ham, sozlamaning ma’nosini tushunish yetarli).
  4. Yuqoridagi "qo'lda ishlaydi, cron ostida ishlamaydi" vaziyatini /usr/local/bin dan tashqaridagi buyruqni (masalan, rsync) chaqiradigan o'z skriptingiz yordamida qayta yarating.

Xulosa

cron buyruqlarni terminalga qaraganda sezilarli darajada kambag'al muhitda ishga tushiradi: qisqaroq PATH, boshqa ishchi katalog, bash o'rniga sh. Buni oldindan bilish "qo'lda ishlaydi, cron ostida ishlamaydi" muammolarining ko'pchiligining oldini oladi. Absolyut yo'llar, aniq PATH, faylga yo'naltirilgan chiqish va journalctl -u cron.service ni tekshirish — bularning hammasi bitta asosiy muammoning turli qirralari. Keyingi maqola cron’ning zamonaviy muqobili — systemd timer ni ko'rib chiqadi. U aynan shu kuzatuvchanlik (observability) muammosini boshqacha hal qiladi.

Manbalar