Mengapa Cron Job Tidak Berjalan? Ini 5 Punca Utama
Adakah cron job anda gagal berfungsi? Ketahui lima punca utama termasuk isu PATH, tanda peratus, fail crontab yang salah, output e-mel, dan skrip yang memerlukan shell.
Mengapa cron job anda tidak berjalan
Sesuatu cron job yang "tidak pernah berjalan" hampir pasti telah pun berjalan. Ia berjalan dalam persekitaran yang bukan shell anda, ia gagal pada saat pertama, dan mesej ralat dihantar ke tempat yang tidak anda semak. Lima punca menjelaskan hampir setiap laporan: laluan carian (search path), tanda peratus, fail crontab yang salah, output yang dihantar ke e-mel, dan skrip yang menjangkakan sesi log masuk.
cron ialah daemon (servis latar belakang) yang membaca fail crontab dan memulakan arahan mengikut jadual. Ia tidak membaca .bashrc anda, ia tidak membuka terminal, ia tidak memulakan shell log masuk, dan ia tidak memberitahu anda apabila sesuatu arahan gagal. Setiap punca di bawah berpunca daripada empat fakta tersebut.
Selesaikan punca-punca ini mengikut urutan, dan mulakan dengan soalan di bawah kesemuanya: adakah cron benar-benar mencetuskan tugasan tersebut? "cron tidak pernah memulakan tugasan" dan "tugasan bermula tetapi terhenti" adalah masalah berbeza yang tidak mempunyai kaitan, jadi jawab soalan itu dahulu.
Adakah cron telah dijalankan?
Daemon ini mempunyai nama unit yang berbeza mengikut keluarga pengedaran. Semak kedua-duanya, kemudian baca log tersebut.
systemctl status cron
systemctl status crond
journalctl -u cron --since "2 hours ago"
journalctl -u crond --since "2 hours ago"Debian dan Ubuntu menamakan unit tersebut sebagai cron. Fedora, Rocky dan Alma menamakannya crond. Hanya satu daripada nama tersebut wujud pada sesebuah mesin, jadi adalah perkara biasa jika salah satu daripada dua arahan tersebut melaporkan unit tidak diketahui dan ia bukanlah satu ralat.
Baca entri yang ditulis oleh sistem anda sendiri. Jangan mencari baris yang disalin daripada panduan, kerana olahan kata berbeza antara implementasi cron dan antara tetapan log. Anda hanya perlu menyemak dua perkara: adakah terdapat entri pada minit yang ditetapkan dalam jadual anda, dan adakah entri tersebut menamakan arahan anda. Entri yang menamakan arahan anda bermakna cron telah melaksanakan tugasnya dan kegagalan tersebut berpunca daripada dalam arahan itu sendiri. Ketiadaan entri bermakna cron tidak mempunyai jadual anda, yang merupakan punca 3 di bawah.
Sesetengah imej menghantar mesej cron melalui rsyslog ke dalam fail dan bukannya ke dalam journal. Cari dalam /var/log untuk fail yang dinamakan mengikut cron atau syslog, kemudian baca bahagian akhirnya.
ls -l /var/log
sudo tail -n 50 /var/log/syslogJika unit mahupun log tidak wujud, cron mungkin tidak dipasang. Imej awan dan kontena yang minimum sering kali tidak menyertakan cron.
dpkg -l cron
rpm -q cronie
sudo apt install cron
sudo dnf install cronie
sudo systemctl enable --now cronPunca 1: cron tidak mempunyai PATH anda
Shell interaktif anda membina PATH daripada /etc/profile, ~/.profile, ~/.bashrc dan semua fail yang disumberkan oleh fail-fail tersebut. Tiada satu pun daripada itu dijalankan untuk tugasan cron. cron memulakan arahan dengan persekitaran ringkasnya sendiri, jadi program yang berada di luar direktori sistem standard tidak ditemui. Apa-apa sahaja di bawah /usr/local/bin, /opt, pengurus versi bahasa, persekitaran maya Python atau ruang kerja Go adalah calonnya. Tugasan tersebut gagal pada baris pertamanya, dan shell menulis ralat gaya "not found" yang perkataan tepatnya bergantung pada shell yang menjalankannya.
Cari laluan sebenar bagi setiap arahan yang digunakan oleh tugasan anda.
command -v docker
command -v node
readlink -f "$(command -v node)"Kemudian, sama ada tulis laluan mutlak tersebut ke dalam tugasan, atau tetapkan PATH sekali di bahagian atas crontab.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/local/bin/mytool runAmbil senarai tersebut daripada mesin anda sendiri dengan echo "$PATH" dan buang apa-apa yang hanya wujud di dalam sesi interaktif. Satu peraturan penting di sini: cron tidak mengembangkan pemboleh ubah dalam baris tugasan ini. PATH=$PATH:/usr/local/bin menyimpan teks literal $PATH:/usr/local/bin, jadi tugasan tersebut berakhir dengan laluan carian yang tidak memegang sebarang direktori yang boleh digunakan. Tulis keseluruhan senarai tersebut.
Pengurus versi memerlukan lebih daripada sekadar laluan. nvm, pyenv, rbenv dan asdf memasang fungsi shell atau direktori shims daripada .bashrc anda, dan tugasan cron tidak pernah membaca fail tersebut. Panggil binari berversi dengan laluan mutlak, atau sumberkan skrip init pengurus tersebut sebagai baris pertama skrip anda sendiri.
Punca 2: tanda peratus menamatkan arahan anda
Dalam medan arahan crontab, % bukanlah aksara biasa. % pertama yang tidak di-escape akan menamatkan arahan tersebut. Segala-galanya selepas itu diserahkan kepada arahan sebagai input standard, dan setiap % seterusnya menjadi baris baharu. Ini adalah ciri cron sebenar untuk menyalurkan input ringkas kepada program, dan inilah sebabnya nama fail bertarikh merupakan entri crontab yang sering rosak.
Tulis 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site dan tar tidak akan melihat tarikh yang diformatkan. cron memotong baris tersebut pada % yang pertama, jadi shell menerima penggantian arahan yang tidak lengkap dan baki baris anda sampai sebagai input standard. Escape setiap tanda peratus dengan garis miring songsang (backslash).
0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +\%F).tar.gz /srv/siteDua lapisan membaca satu baris itu, mengikut urutan. \% ialah peraturan cron, yang digunakan oleh cron sebelum ia memulakan apa-apa. $(date +\%F) ialah penggantian arahan, yang digunakan kemudian oleh shell yang dimulakan oleh cron. Mengetahui lapisan mana yang memiliki aksara mana adalah kunci penyelesaiannya.
Tabiat yang lebih selamat adalah dengan mengeluarkan logik daripada crontab sepenuhnya. Letakkannya di dalam skrip, di mana tanda peratus tidak mempunyai makna khas.
#!/bin/bash
set -euo pipefail
stamp="$(date +%F)"
tar -czf "/srv/backups/site-${stamp}.tar.gz" /srv/siteBaris crontab kemudiannya hanya mengandungi laluan dan ubah hala (redirect) tanpa perkara lain. Crontab yang boleh anda baca sepintas lalu ialah crontab yang boleh anda nyahpepijat (debug).
Cause 3: which crontab did you edit?
There is no single crontab. There are several files, with different owners and different field counts, and a job written into the wrong one is invisible.
crontab -eedits the crontab of the user who runs the command.sudo crontab -eedits root's. Two people debugging the same box often end up reading two different files.sudo crontab -l -u deploylists another user's crontab, which is how you confirm what is actually installed for the account that should run the job./etc/crontaband every file in/etc/cron.dcarry one extra field between the schedule and the command: the user to run as. Paste a five-field user crontab line into/etc/cron.dand the first word of your command is read as a username.- Files in
/etc/cron.dmust be named with letters, digits, underscores and hyphens. A file calledbackup.shorsite.confis skipped because of its name alone. Rename it tobackupand check your log again. - Files in
/etc/cron.dshould be owned by root, and must not be writable by group or others.ls -l /etc/cron.dshows you both facts at once. - Scripts dropped into
/etc/cron.dailyand its siblings follow the same naming rule and must also carry the execute bit. A missing execute bit is a silent skip. /etc/cron.allowand/etc/cron.denydecide who may install a crontab at all. If either exists on your box, read it before assuming your user is allowed one.
Install a user crontab with the crontab command instead of editing the spool file by hand, because crontab parses the file before installing it. When you save, read what the command prints back to you. If it refuses the file, the previous version stays live and your change never took effect, which looks exactly like cron ignoring you.
The owner also decides permissions. A job in root's crontab creates root-owned files that the application reading them may not be able to write. A job in a normal user's crontab cannot read a root-only directory. Match the owner to the work: application maintenance belongs to the application's own account, which is the reasoning behind replacing WordPress wp-cron with a system cron job. The mode of the files your job creates comes from the umask it inherits, and that is another value which is not your shell's, so how umask sets file permissions is worth reading if a job's output lands unreadable.
Punca 4: output dihantar ke e-mel yang tidak dibaca sesiapa
cron mengumpul semua yang ditulis oleh sesuatu tugasan ke standard output dan standard error. Jika tugasan tersebut menulis sebarang teks, cron akan menyerahkan teks itu kepada sistem e-mel tempatan, yang dialamatkan kepada pemilik crontab atau kepada mana-mana yang dinamakan oleh MAILTO. Pada VPS yang minimum, biasanya tiada MTA (mail transfer agent) dipasang, jadi tiada apa-apa yang dihantar. Ralat anda wujud seketika dan kemudian hilang begitu sahaja. Itulah sebab utama mengapa tugasan yang rosak kelihatan senyap.
Hantar output tersebut ke fail yang anda kawal sebaliknya.
0 3 * * * /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1>> menambah standard output ke dalam fail tersebut. 2>&1 menghalakan standard error ke mana sahaja standard output dihalakan pada masa itu, jadi ia mesti diletakkan selepas redirect tersebut. Jika ditulis secara terbalik, sebagai 2>&1 >> file, standard error akan mengekalkan destinasi asalnya, dan ralat yang anda cari adalah bahagian yang tidak akan sampai ke fail tersebut.
Journal adalah satu lagi sasaran yang baik. logger menulis ke dalam syslog di bawah tag yang anda pilih.
0 3 * * * /usr/local/sbin/backup-site.sh 2>&1 | logger -t backup-siteBaca semula dengan journalctl -t backup-site. Ini mengekalkan output tugasan tersebut bersebelahan dengan entri cron, jadi garis masa mudah untuk diikuti. Jika anda juga memerlukan rekod tentang individu mana yang menjalankan arahan mana pada pelayan, itu adalah sistem yang berasingan, dan mengaudit arahan pengguna pada pelayan anda meliputinya.
MAILTO="" di bahagian atas crontab mematikan e-mel untuk tugasan di bawahnya. Menetapkan MAILTO kepada alamat sebenar hanya membantu jika MTA yang berfungsi wujud, jadi pastikan e-mel benar-benar keluar dari pelayan sebelum anda bergantung kepadanya.
Satu peraturan semasa melakukan penyahpepijatan: jangan sekali-kali menambah > /dev/null 2>&1. Ia adalah baris yang paling popular dalam setiap crontab, dan ia membuang satu-satunya bukti yang anda miliki. Letakkannya semula kemudian jika anda mahu, setelah tugasan tersebut berfungsi.
Punca 5: skrip mengandaikan persekitaran yang tidak diberikan oleh cron
Sebaik sahaja arahan ditemui dan outputnya ditangkap, apa yang tinggal hanyalah segala perkara lain yang diberikan secara percuma oleh sesi anda.
- Shell mungkin bukan bash. Semak dengan
ls -l /bin/sh. Pada Debian dan Ubuntu, ia menghala ke dash, jadi ujian kurungan berkembar, tatasusunan dansourceakan gagal dengan ralat sintaks. Berikan skrip baris#!/bin/bashdan panggil skrip tersebut, atau tetapkanSHELLdi bahagian atas crontab. - Direktori kerja bukan direktori tempat anda berada. Gunakan laluan mutlak di mana-mana, atau
cdke direktori tersebut pada baris pertama skrip. Laluan relatif adalah punca paling biasa mengapa sesuatu tugasan "berfungsi apabila saya menjalankannya secara manual". - Lokal bukan lokal sesi anda. Sebarang perkara yang memformat tarikh atau nombor, atau menyusun teks, boleh menghasilkan output yang berbeza di bawah
LANGyang berlainan. Jika langkah seterusnya menghuraikan output tersebut, tetapkan lokal dalam skrip dan jangan hanya mengharap. - Tiada TTY (terminal). Arahan yang meminta pengesahan, membuka editor atau memaparkan bar kemajuan boleh tergantung atau keluar. Tambahkan sebarang flag bukan interaktif yang ditawarkan oleh alat tersebut.
- Tiada ejen SSH.
SSH_AUTH_SOCKtidak berada dalam persekitaran cron, jadi arahansshataursyncyang berfungsi kerana ejen anda dimuatkan kini gagal untuk mengesahkan identiti. Berikan tugasan tersebut kuncinya sendiri, yang dimiliki oleh pengguna tugasan itu. - Tiada bas sesi pengguna, jadi
systemctl --userdaripada tugasan cron akan gagal sehinggaXDG_RUNTIME_DIRditetapkan. Unit sistem adalah jawapan yang lebih baik.
Pada Fedora, Rocky dan Alma, terdapat satu lagi suspek. SELinux mengehadkan tugasan cron, jadi tugasan yang menyentuh laluan dengan label yang tidak dijangka akan dinafikan walaupun kebenaran fail kelihatan betul. Semak penafian dengan sudo ausearch -m avc -ts recent, dan baca asas SELinux untuk pelayan sebelum anda mematikan apa-apa.
Ujian satu minit untuk melihat persekitaran cron
Berhenti meneka kandungan persekitaran cron dan bacalah sendiri. Tulis skrip yang memaparkan segala-galanya, jadualkan ia setiap minit, tunggu, kemudian baca fail tersebut.
cat > /home/deploy/cron-probe.sh <<'EOF'
#!/bin/bash
echo "=== probe ==="
date -Is
pwd
id
echo "SHELL=$SHELL"
echo "LANG=$LANG"
command -v node || echo "node is not on this PATH"
env | sort
EOF
chmod +x /home/deploy/cron-probe.shTambah satu baris ke dalam crontab pengguna yang menjalankan tugasan sebenar, dengan menggunakan laluan mutlak pada kedua-dua belah pihak.
* * * * * /home/deploy/cron-probe.sh >> /home/deploy/cron-probe.log 2>&1Tunggu selama satu minit, kemudian baca /home/deploy/cron-probe.log dan bandingkan ia dengan arahan yang sama yang dijalankan dalam shell anda sendiri. Baris PATH, direktori kerja, dan lokal biasanya menjelaskan punca kegagalan dengan sendirinya. Perhatikan dua perincian persediaan ini: tanda peratus berada di dalam skrip, di mana peraturan cron tidak terpakai, dan laluan log adalah lokasi yang boleh ditulis oleh pengguna tugasan tersebut.
Padam baris crontab itu sebaik sahaja anda mendapat jawapannya. Tugasan yang berjalan setiap minit dan menambah data ke dalam fail akan memenuhi cakera yang kecil, dan ia akan melakukannya secara senyap.
Adakah jadual ini yang anda maksudkan?
Satu baris crontab pengguna bermula dengan lima medan: minit, jam, hari bulan, bulan, dan hari minggu. Dua daripadanya berinteraksi dengan cara yang mengejutkan pengguna.
Apabila hari bulan dan hari minggu kedua-duanya dihadkan, bermakna tiada satu pun yang ditetapkan sebagai *, cron akan menjalankan tugasan tersebut apabila salah satu medan sepadan. 0 0 13 * 5 bukan bermaksud "Jumaat tarikh 13". Ia akan berjalan pada tengah malam setiap tarikh 13 bulan tersebut, dan pada tengah malam setiap hari Jumaat. Untuk mendapatkan satu hari yang khusus, biarkan salah satu daripada dua medan tersebut sebagai * dan uji medan yang satu lagi di dalam skrip anda.
cron menggunakan zon waktu sistem. Banyak imej VPS dihantar dengan tetapan UTC (Coordinated Universal Time), jadi tugasan yang anda jadualkan pada 03:00 akan berjalan pada 03:00 UTC, yang mungkin berlaku pada waktu tengah hari anda. timedatectl akan memaparkan zon waktu yang digunakan oleh pelayan anda. Semak tetapan anda sendiri daripada menganggap ia sama dengan komputer riba anda.
Terdapat dua lagi perangkap jadual yang perlu diketahui. @reboot akan dicetuskan apabila cron itu sendiri bermula, yang tidak sama masanya dengan ketersediaan rangkaian. Oleh itu, tugasan yang memerlukan DNS atau hos jauh boleh gagal semasa but, tetapi berjaya pada setiap pelaksanaan manual selepas itu. Selain itu, tiada apa yang menghalang tugasan yang perlahan daripada bermula semula semasa salinan sebelumnya masih berjalan. Balut tugasan tersebut dengan kunci (lock).
*/5 * * * * /usr/bin/flock -n /tmp/backup-site.lock /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1flock -n akan berhenti serta-merta apabila kunci sudah dipegang, jadi pelaksanaan yang bertindih akan terhenti dan tidak menimbun di atas pelaksanaan pertama.
Apabila systemd timer menjadi alat yang lebih baik
cron hanya mahir dalam satu perkara: menjalankan arahan pada waktu yang ditetapkan. Selain itu, ia mempunyai banyak kelemahan. Timer memberikan anda akses kepada journal tanpa perlu melakukan sebarang redirect, status keluar (exit status) yang boleh disemak kemudian, penyusunan mengikut network-online.target, serta kelewatan rawak supaya seratus pelayan tidak bermula pada saat yang sama. Apabila tugasan anda memerlukan mana-mana ciri tersebut, menggunakan systemd service dan timer pada VPS adalah lebih mudah berbanding menyelenggara baris crontab. Menulis bahagian service memerlukan anda menjawab satu soalan yang tidak pernah ditanya oleh cron, iaitu bagaimana unit mengetahui kerja tersebut benar-benar telah bermula. Oleh itu, baca dahulu maksud Type= bagi unit simple, forking dan notify, kerana skrip yang melakukan daemonize sendiri di bawah jenis lalai akan menyebabkan unit kelihatan aktif sedangkan tiada proses yang berjalan. Gelagat cuba semula (retry behaviour) juga perlu diletakkan di sana, kerana polisi restart systemd menentukan apa yang berlaku selepas kegagalan, manakala cron tidak mempunyai penyelesaian untuk perkara tersebut.
Kekalkan cron untuk tugasan kecil. Pindahkan sebarang tugasan yang mempunyai dependency atau polisi cuba semula kepada timer. Kedua-duanya boleh berjalan pada pelayan yang sama, jadi ini bukanlah migrasi yang perlu diselesaikan dalam satu masa.
FAQ
Mengapa cron job saya berfungsi secara manual tetapi gagal apabila dijalankan oleh cron?
Kerana persekitaran shell anda dan cron adalah berbeza. Shell log masuk anda membaca /etc/profile dan ~/.bashrc, yang menetapkan PATH, lokal, dan pemboleh ubah ejen anda. cron memulakan arahan tanpa semua itu, dari direktori kerja yang berbeza, dan kadangkala menggunakan shell yang berbeza. Gunakan laluan mutlak (absolute path) untuk setiap arahan, tetapkan apa yang diperlukan di bahagian atas crontab atau di dalam skrip, dan jadualkan satu tugasan ujian selama satu minit yang menjalankan env | sort, pwd dan id ke dalam fail log supaya anda boleh membaca persekitaran sebenar cron dan bukannya meneka.
Bagaimanakah cara untuk menyemak sama ada cron benar-benar menjalankan tugasan saya?
Baca log daemon tersebut. Gunakan journalctl -u cron pada Debian dan Ubuntu, atau journalctl -u crond pada Fedora, Rocky dan Alma; sesetengah imej menghalakan mesej melalui rsyslog ke dalam fail di bawah /var/log sebagai ganti. Cari entri pada minit yang anda tetapkan dalam jadual dan pastikan ia menamakan arahan anda. Tiada entri bermakna cron tidak mempunyai jadual tersebut, jadi sahkan anda telah menyunting crontab yang betul. Entri tanpa hasil bermakna arahan tersebut bermula dan terhenti, jadi tangkap outputnya dengan pengalihan (redirect).
Mengapa date +%Y rosak di dalam crontab?
cron menganggap % sebagai aksara khas dalam medan arahan. % pertama yang tidak di-escape akan menamatkan arahan, semua yang selepasnya dihantar kepada arahan tersebut sebagai input standard, dan setiap % seterusnya menjadi baris baharu. Oleh itu, nama fail yang diformatkan dengan tarikh tidak akan sampai kepada program yang anda tulis. Escape setiap peratus sebagai \%, atau pindahkan arahan tersebut ke dalam skrip dan panggil skrip itu daripada cron, kerana di dalam skrip, tanda peratus tidak mempunyai makna khas.
Ke manakah perginya output cron job saya?
Ke sistem mel tempatan, ditujukan kepada pemilik crontab atau kepada sesiapa yang dinamakan oleh MAILTO. Kebanyakan imej VPS tidak mempunyai ejen pemindahan mel (MTA) yang dipasang, jadi mesej tersebut dibuang dan tugasan kelihatan senyap. Alihkan output ke fail dengan >> /path/to/log 2>&1, kekalkan susunan tersebut supaya ralat standard mengikuti output standard, atau pipe melalui logger -t myjob dan baca semula dengan journalctl -t myjob. Jangan gunakan > /dev/null 2>&1 semasa anda masih melakukan penyahpepijatan (debugging).
Patutkah saya menggunakan cron atau systemd timer?
Gunakan cron untuk arahan mudah pada waktu yang tetap, terutamanya jika anda mungkin perlu memindahkannya ke mesin yang tidak menjalankan systemd. Gunakan timer apabila anda mahukan output berada dalam journal tanpa perlu melakukan pengalihan, status keluar yang boleh disemak, penyusunan selepas rangkaian aktif, kelewatan permulaan secara rawak, atau polisi cuba semula selepas kegagalan. Kedua-duanya boleh dijalankan pada pelayan yang sama, jadi anda boleh memindahkan tugasan satu demi satu apabila perlu.