SSD Nodes Learn 🎉 VPS dari $5.50/bln
Panduan Matt ConnorOleh Matt Connor

Mengapa Cron Job Anda Tidak Berjalan

Ketahui lima punca utama cron job gagal berfungsi seperti PATH yang terhad, tanda peratus yang tidak di-escape, serta ralat output yang hilang tanpa dikesan dalam sistem anda.

Mengapa cron job anda tidak berjalan

Cron job yang "tidak pernah berjalan" hampir selalu sebenarnya telah berjalan. Ia berjalan dalam persekitaran yang bukan shell anda, ia gagal pada saat pertama, dan mesej ralat dihantar ke tempat yang tidak anda baca. 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 masalah 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 persamaan, 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 sebagai 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 perkataan yang digunakan berbeza antara implementasi cron dan antara tetapan log. Anda hanya perlu menyemak dua perkara: adakah terdapat entri pada minit yang dinyatakan 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. Tiada entri langsung bermakna cron tidak mempunyai jadual anda, yang merupakan punca ke-3 di bawah.

Sesetengah imej menghantar mesej cron melalui rsyslog ke dalam fail dan bukannya ke dalam journal. Cari di 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/syslog

Jika 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 cron

Punca 1: cron tidak mempunyai PATH anda

Shell interaktif anda membina PATH daripada /etc/profile, ~/.profile, ~/.bashrc dan semua fail yang dirujuk 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 run

Ambil 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 mengandungi direktori yang boleh digunakan langsung. 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 menggunakan laluan mutlak, atau rujuk 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 merupakan ciri cron sebenar untuk menyalurkan input ringkas kepada program, dan inilah sebab mengapa nama fail bertarikh menjadi 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 menerima tarikh yang diformatkan. cron memotong baris pada % pertama, jadi shell menerima penggantian arahan yang tidak lengkap dan baki baris anda tiba sebagai input standard. Escape setiap peratus dengan garis miring songsang (backslash).

0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +\%F).tar.gz /srv/site

Dua lapisan membaca satu baris tersebut, 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 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/site

Baris 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.

Punca 3: crontab mana yang anda sunting?

Tiada satu crontab tunggal. Terdapat beberapa fail dengan pemilik berbeza dan bilangan medan yang berbeza; tugasan yang ditulis ke dalam fail yang salah tidak akan berfungsi.

  • crontab -e menyunting crontab pengguna yang menjalankan arahan tersebut. sudo crontab -e menyunting crontab milik root. Dua orang yang melakukan penyahpepijatan pada pelayan yang sama sering kali membaca dua fail yang berbeza.
  • sudo crontab -l -u deploy menyenaraikan crontab pengguna lain; ini cara untuk mengesahkan apa yang sebenarnya dipasang bagi akaun yang sepatutnya menjalankan tugasan tersebut.
  • /etc/crontab dan setiap fail dalam /etc/cron.d mengandungi satu medan tambahan antara jadual dan arahan: pengguna yang akan menjalankan tugasan tersebut. Jika anda menampal baris crontab pengguna yang mempunyai lima medan ke dalam /etc/cron.d, perkataan pertama arahan anda akan dibaca sebagai nama pengguna.
  • Fail dalam /etc/cron.d mestilah dinamakan menggunakan huruf, digit, garis bawah dan tanda sempang. Fail yang dinamakan backup.sh atau site.conf akan dilangkau semata-mata kerana namanya. Namakan semula kepada backup dan semak log anda sekali lagi.
  • Fail dalam /etc/cron.d harus dimiliki oleh root dan tidak boleh ditulis oleh kumpulan atau pengguna lain. ls -l /etc/cron.d menunjukkan kedua-dua fakta ini serentak.
  • Skrip yang diletakkan ke dalam /etc/cron.daily dan direktori seumpamanya mengikut peraturan penamaan yang sama dan mestilah mempunyai bit boleh laksana (execute bit). Bit boleh laksana yang tiada akan menyebabkan skrip dilangkau tanpa sebarang ralat.
  • /etc/cron.allow dan /etc/cron.deny menentukan siapa yang dibenarkan memasang crontab. Jika salah satu fail ini wujud pada sistem anda, baca kandungannya sebelum menganggap pengguna anda dibenarkan untuk mempunyai crontab.

Pasang crontab pengguna menggunakan arahan crontab dan bukannya menyunting fail spool secara manual, kerana crontab akan menghurai fail tersebut sebelum memasangnya. Apabila anda menyimpan, baca maklum balas yang dipaparkan oleh arahan tersebut. Jika ia menolak fail itu, versi sebelumnya akan kekal aktif dan perubahan anda tidak akan berkuat kuasa; ini kelihatan seolah-olah cron mengabaikan anda.

Pemilik juga menentukan keizinan. Tugasan dalam crontab root akan mencipta fail milik root yang mungkin tidak boleh ditulis oleh aplikasi yang membacanya. Tugasan dalam crontab pengguna biasa tidak boleh membaca direktori yang hanya boleh diakses oleh root. Padankan pemilik dengan tugasan tersebut: penyelenggaraan aplikasi adalah tanggungjawab akaun aplikasi itu sendiri, itulah sebabnya menggantikan wp-cron WordPress dengan tugasan cron sistem disyorkan. Mod fail yang dicipta oleh tugasan anda datang daripada umask yang diwarisi, dan itu adalah nilai yang berbeza daripada shell anda, jadi bagaimana umask menetapkan keizinan fail wajar dibaca jika output tugasan anda tidak boleh dibaca.

Punca 4: output dihantar ke e-mel yang tidak dibaca sesiapa

cron mengumpul segala yang ditulis oleh sesuatu tugasan ke standard output dan standard error. Jika tugasan tersebut menulis apa-apa sahaja, cron akan menyerahkan teks itu kepada sistem e-mel tempatan, yang dialamatkan kepada pemilik crontab atau kepada apa sahaja 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 sekarang, jadi ia mesti diletakkan selepas redirect tersebut. Jika ditulis secara terbalik, sebagai 2>&1 >> file, standard error mengekalkan destinasi asalnya, dan ralat yang anda cari adalah bahagian yang tidak pernah 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-site

Baca semula dengan journalctl -t backup-site. Ini memastikan output tugasan tersebut berada bersebelahan dengan entri cron, jadi garis masa mudah untuk diikuti. Jika anda juga memerlukan rekod tentang siapa yang menjalankan perintah apa pada pelayan, itu adalah sistem yang berasingan, dan mengaudit perintah 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 menyahpepijat: jangan sekali-kali menambah > /dev/null 2>&1. Ia adalah baris 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 disediakan oleh cron

Sebaik sahaja arahan ditemui dan outputnya ditangkap, apa yang tinggal ialah 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 berganda, tatasusunan dan source akan gagal dengan ralat sintaks. Berikan skrip baris #!/bin/bash dan panggil skrip tersebut, atau tetapkan SHELL di bahagian atas crontab.
  • Direktori kerja bukan direktori tempat anda berada. Gunakan laluan mutlak di mana-mana, atau cd ke 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 mengisih teks, boleh menghasilkan output yang berbeza di bawah LANG yang berlainan. Jika langkah seterusnya menghuraikan output tersebut, tetapkan lokal dalam skrip dan jangan hanya berharap.
  • Tiada TTY (terminal). Arahan yang meminta pengesahan, membuka editor atau melukis bar kemajuan boleh tergantung atau keluar. Tambahkan sebarang flag bukan interaktif yang ditawarkan oleh alat tersebut.
  • Tiada ejen SSH. SSH_AUTH_SOCK tidak berada dalam persekitaran cron, jadi arahan ssh atau rsync yang berfungsi kerana ejen anda dimuatkan kini gagal untuk mengesahkan. Berikan tugasan tersebut kuncinya sendiri, yang dimiliki oleh pengguna tugasan itu.
  • Tiada bas sesi pengguna, jadi systemctl --user daripada tugasan cron gagal sehingga XDG_RUNTIME_DIR ditetapkan. 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 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.sh

Tambah satu baris pada crontab pengguna yang menjalankan tugasan sebenar, dengan menggunakan laluan mutlak pada kedua-dua bahagian.

* * * * * /home/deploy/cron-probe.sh >> /home/deploy/cron-probe.log 2>&1

Tunggu selama seminit, kemudian baca /home/deploy/cron-probe.log dan bandingkan 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 diletakkan di dalam skrip, di mana peraturan cron tidak terpakai, dan laluan log mestilah lokasi yang boleh ditulis oleh pengguna tugasan tersebut.

Padamkan 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?

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 apabila salah satu medan sepadan. 0 0 13 * 5 bukan bermaksud "Jumaat ke-13". Ia akan berjalan pada tengah malam setiap hari ke-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.

cron menggunakan zon masa sistem. Banyak imej VPS ditetapkan kepada 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 mencetak zon masa yang sebenarnya digunakan oleh pelayan anda. Semak zon masa anda sendiri dan jangan menganggap ia sama dengan zon masa 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 mungkin 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>&1

flock -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. Ia lemah dalam aspek lain. Timer memberikan anda akses kepada journal tanpa perlu melakukan sebarang redirect, status keluar 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 ciri-ciri tersebut, systemd service dan timer pada VPS memerlukan usaha yang lebih sedikit berbanding menyelenggara baris crontab. Gelagat cuba semula (retry) juga sepatutnya diletakkan di sana, kerana polisi restart systemd menentukan tindakan selepas kegagalan, manakala cron tidak mempunyai penyelesaian untuk perkara tersebut.

Gunakan 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. 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 tanda peratus sebagai \%, atau pindahkan arahan tersebut ke dalam skrip dan panggil skrip itu daripada cron, kerana di dalam skrip tanda peratus tidak mempunyai maksud khas.

Ke manakah perginya output cron job saya?

Ke sistem mel tempatan, yang dialamatkan 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 paipkan melalui logger -t myjob dan bacanya semula dengan journalctl -t myjob. Jangan gunakan > /dev/null 2>&1 semasa anda masih dalam proses penyahpepijatan (debugging).

Patutkah saya menggunakan cron atau systemd timer?

Gunakan cron untuk arahan ringkas pada waktu yang tetap, terutamanya jika anda mungkin perlu memindahkannya ke mesin yang tidak menjalankan systemd. Gunakan timer apabila anda mahu 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 berjalan pada pelayan yang sama, jadi anda boleh memindahkan tugasan satu demi satu apabila perlu.