Bagaimana cara menulis skrip bash untuk memulai kembali proses jika mati?

226

Saya memiliki skrip python yang akan memeriksa antrian dan melakukan tindakan pada setiap item:

# checkqueue.py
while True:
  check_queue()
  do_something()

Bagaimana cara menulis skrip bash yang akan memeriksa apakah skrip tersebut berjalan, dan jika tidak, mulai saja. Kira-kira kode pseudo berikut (atau mungkin harus melakukan sesuatu seperti ps | grep?):

# keepalivescript.sh
if processidfile exists:
  if processid is running:
     exit, all ok

run checkqueue.py
write processid to processidfile

Saya akan menyebutnya dari crontab:

# crontab
*/5 * * * * /path/to/keepalivescript.sh
Tom
sumber
4
Tambahkan saja ini untuk 2017. Gunakan pengawas. crontab tidak bermaksud melakukan tugas semacam ini. Script bash sangat buruk dalam memancarkan kesalahan nyata. stackoverflow.com/questions/9301494/…
mootmoot
Bagaimana dengan menggunakan inittab dan respawn alih-alih solusi non-sistem lainnya? Lihat superuser.com/a/507835/116705
Lars Nordin

Jawaban:

635

Hindari PID-file, crons, atau apa pun yang mencoba untuk mengevaluasi proses yang bukan anak-anak mereka.

Ada alasan yang sangat bagus mengapa di UNIX, Anda HANYA dapat menunggu anak-anak Anda. Metode apa pun (ps parsing, pgrep, menyimpan PID, ...) yang mencoba untuk mengatasi yang cacat dan memiliki lubang menganga di dalamnya. Katakan saja tidak .

Alih-alih, Anda membutuhkan proses yang memantau proses Anda untuk menjadi induk proses. Apa artinya ini? Ini berarti hanya proses yang memulai proses Anda yang dapat menunggu hingga berakhir. Dalam bash, ini benar-benar sepele.

until myserver; do
    echo "Server 'myserver' crashed with exit code $?.  Respawning.." >&2
    sleep 1
done

Sepotong kode bash di atas berjalan myserverdalam satu untillingkaran. Baris pertama dimulai myserverdan menunggu sampai akhir. Ketika itu berakhir, untilperiksa status keluarnya. Jika status keluar adalah 0, itu berarti berakhir dengan anggun (yang berarti Anda memintanya untuk ditutup, dan berhasil). Dalam hal ini kami tidak ingin memulai kembali (kami hanya meminta untuk mematikan!). Jika status keluar tidak 0 , untilakan menjalankan loop body, yang memancarkan pesan kesalahan pada STDERR dan me-restart loop (kembali ke baris 1) setelah 1 detik .

Mengapa kita menunggu sebentar? Karena jika ada sesuatu yang salah dengan urutan startup myserverdan crash segera, Anda akan memiliki loop yang sangat intensif untuk memulai dan crash terus-menerus di tangan Anda. Yang sleep 1menghilangkan ketegangan dari itu.

Sekarang yang perlu Anda lakukan adalah memulai skrip bash ini (secara tidak serempak, mungkin), dan itu akan memonitor myserverdan memulai kembali sesuai kebutuhan. Jika Anda ingin memulai monitor saat boot (membuat server "hidup" reboot), Anda dapat menjadwalkannya di cron pengguna Anda (1) dengan sebuah @rebootaturan. Buka aturan cron Anda dengan crontab:

crontab -e

Kemudian tambahkan aturan untuk memulai skrip monitor Anda:

@reboot /usr/local/bin/myservermonitor

Kalau tidak; lihat inittab (5) dan / etc / inittab. Anda dapat menambahkan baris di sana untuk myservermemulai pada level init tertentu dan direspawn secara otomatis.


Edit.

Biarkan saya menambahkan beberapa informasi tentang mengapa tidak menggunakan file PID. Meskipun mereka sangat populer; mereka juga sangat cacat dan tidak ada alasan mengapa Anda tidak hanya melakukannya dengan cara yang benar.

Pertimbangkan ini:

  1. Daur ulang PID (membunuh proses yang salah):

    • /etc/init.d/foo start: mulai foo, tulis fooPID ke/var/run/foo.pid
    • Beberapa saat kemudian: fooentah bagaimana mati.
    • Beberapa saat kemudian: setiap proses acak yang dimulai (sebut saja bar) membutuhkan PID acak, bayangkan ia mengambil fooPID lama.
    • Anda perhatikan foohilang: /etc/init.d/foo/restartmembaca /var/run/foo.pid, memeriksa untuk melihat apakah masih hidup, menemukan bar, berpikir itu foo, membunuhnya, memulai yang baru foo.
  2. File PID menjadi basi. Anda perlu logika yang terlalu rumit (atau harus saya katakan, non-sepele) untuk memeriksa apakah file PID sudah basi, dan logika seperti itu lagi rentan 1..

  3. Bagaimana jika Anda bahkan tidak memiliki akses tulis atau berada dalam lingkungan baca-saja?

  4. Komplikasi yang tidak ada gunanya; lihat betapa sederhananya contoh saya di atas. Tidak perlu mempersulit itu sama sekali.

Lihat juga: Apakah file-PID masih cacat saat melakukannya 'benar'?

Ngomong-ngomong; bahkan lebih buruk dari file PID yang diuraikan ps! Jangan pernah melakukan ini.

  1. pssangat tidak bisa diport. Meskipun Anda menemukannya di hampir setiap sistem UNIX; argumennya sangat bervariasi jika Anda menginginkan keluaran non-standar. Dan output standar HANYA untuk konsumsi manusia, bukan untuk penguraian scripted!
  2. Parsing psmengarah ke BANYAK positif palsu. Ambil ps aux | grep PIDcontoh, dan sekarang bayangkan seseorang memulai proses dengan nomor di suatu tempat sebagai argumen yang sama dengan PID yang Anda lihat dengan dasmon Anda! Bayangkan dua orang memulai sesi X dan Anda mengambil X untuk membunuh X Anda. Itu semua jenis yang buruk.

Jika Anda tidak ingin mengelola sendiri prosesnya; ada beberapa sistem yang sangat baik di luar sana yang akan bertindak sebagai monitor untuk proses Anda. Lihat ke runit , misalnya.

lununath
sumber
1
@Chas. Milik: Saya kira itu tidak perlu. Itu hanya akan mempersulit implementasi tanpa alasan yang baik. Kesederhanaan selalu lebih penting; dan jika sering dinyalakan kembali, tidur akan mencegahnya berdampak buruk pada sumber daya sistem Anda. Sudah ada pesan.
lhunath
2
@orschiro Tidak ada konsumsi sumber daya saat program berperilaku. Jika ada segera saat diluncurkan, terus menerus, konsumsi sumber daya dengan sleep 1 masih benar-benar diabaikan.
lhunath
7
Bisa percaya saya hanya melihat jawaban ini. Terima kasih banyak!
getWeberForStackExchange
2
@ TomášZato Anda dapat melakukan loop di atas tanpa menguji kode keluar proses while true; do myprocess; donetetapi perhatikan bahwa sekarang tidak ada cara untuk menghentikan proses.
lhunath
2
@ SergeyP.akaazure Satu-satunya cara untuk memaksa orang tua untuk membunuh anak saat keluar di bash adalah dengan mengubah anak itu menjadi pekerjaan dan memberi sinyal:trap 'kill $(jobs -p)' EXIT; until myserver & wait; do sleep 1; done
lhunath
33

Lihatlah monit ( http://mmonit.com/monit/ ). Ini menangani mulai, berhenti dan mulai ulang skrip Anda dan dapat melakukan pemeriksaan kesehatan plus restart jika perlu.

Atau lakukan skrip sederhana:

while true
do
/your/script
sleep 1
done
Bernd
sumber
4
Monit persis apa yang Anda cari.
Sarke
4
"while 1" tidak berfungsi. Anda membutuhkan "while [1]" atau "while true" atau "while:". Lihat unix.stackexchange.com/questions/367108/what-does-while-mean
Curtis Yallop
8

Cara termudah untuk melakukannya adalah menggunakan kawanan pada file. Dalam skrip Python Anda akan melakukannya

lf = open('/tmp/script.lock','w')
if(fcntl.flock(lf, fcntl.LOCK_EX|fcntl.LOCK_NB) != 0): 
   sys.exit('other instance already running')
lf.write('%d\n'%os.getpid())
lf.flush()

Dalam shell Anda benar-benar dapat menguji apakah itu berjalan:

if [ `flock -xn /tmp/script.lock -c 'echo 1'` ]; then 
   echo 'it's not running'
   restart.
else
   echo -n 'it's already running with PID '
   cat /tmp/script.lock
fi

Tetapi tentu saja Anda tidak perlu menguji, karena jika sudah berjalan dan Anda me-restart, itu akan keluar bersama 'other instance already running'

Ketika proses mati, semua deskriptor file ditutup dan semua kunci secara otomatis dihapus.

vartec
sumber
yang bisa disederhanakan sedikit dengan menghapus skrip bash. apa yang terjadi jika skrip python macet? Apakah file tidak dikunci?
Tom
1
Kunci file dilepaskan segera setelah aplikasi berhenti, baik dengan membunuh, secara alami atau menabrak.
Christian Witts
@ Tom ... untuk menjadi sedikit lebih tepat - kunci tidak lagi aktif segera setelah file menangani ditutup. Jika skrip Python tidak pernah menutup pegangan file dengan sengaja, dan memastikan skrip tidak ditutup secara otomatis melalui objek file yang dikumpulkan sampah, maka menutupnya mungkin berarti skrip keluar / terbunuh. Ini berfungsi bahkan untuk reboot dan semacamnya.
Charles Duffy
1
Ada banyak cara yang lebih baik untuk digunakan flock... pada kenyataannya, halaman manual secara eksplisit menunjukkan caranya! exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"adalah bash setara dengan Python Anda, dan meninggalkan kunci ditahan (jadi jika Anda kemudian menjalankan suatu proses, kunci akan tetap ditahan sampai proses itu keluar).
Charles Duffy
Saya menurunkan Anda karena kode Anda salah. Menggunakan flockadalah cara yang benar, tetapi skrip Anda salah. Satu-satunya perintah yang perlu Anda atur di crontab adalah:flock -n /tmp/script.lock -c '/path/to/my/script.py'
Rutrus
6

Anda harus menggunakan monit, alat unix standar yang dapat memantau berbagai hal pada sistem dan bereaksi sesuai itu.

Dari dokumen: http://mmonit.com/monit/documentation/monit.html#pid_testing

periksa proses checkqueue.py dengan pidfile /var/run/checkqueue.pid
       jika diubah pid maka exec "checkqueue_restart.sh"

Anda juga dapat mengonfigurasi monit untuk mengirimi Anda email saat melakukan restart.

clofresh
sumber
2
Monit adalah alat yang hebat, tetapi itu bukan standar dalam arti formal yang ditentukan dalam POSIX atau SUSV.
Charles Duffy
5
if ! test -f $PIDFILE || ! psgrep `cat $PIDFILE`; then
    restart_process
    # Write PIDFILE
    echo $! >$PIDFILE
fi
soulmerge
sumber
keren, itu menyempurnakan beberapa kode palsu saya dengan cukup baik. dua qns: 1) bagaimana cara menghasilkan PIDFILE? 2) apa itu psgrep? itu bukan di server ubuntu.
Tom
ps grep hanyalah aplikasi kecil yang melakukan hal yang sama ps ax|grep .... Anda hanya dapat menginstalnya atau menulis fungsi untuk itu: function psgrep () {ps ax | grep -v grep | grep -q "$ 1"}
soulmerge
Hanya memperhatikan bahwa saya belum menjawab pertanyaan pertama Anda.
soulmerge
7
Pada server yang sangat sibuk, PID mungkin akan didaur ulang sebelum Anda periksa.
vartec
2

Saya tidak yakin seberapa portabelnya di seluruh sistem operasi, tetapi Anda mungkin memeriksa apakah sistem Anda berisi perintah 'run-one', yaitu "man run-one". Secara khusus, serangkaian perintah ini termasuk 'run-one-constant', yang tampaknya tepat seperti yang dibutuhkan.

Dari halaman manual:

run-one-constant PERINTAH [ARGS]

Catatan: jelas ini dapat dipanggil dari dalam skrip Anda, tetapi juga menghilangkan kebutuhan untuk memiliki skrip sama sekali.

Daniel Bradley
sumber
Apakah ini menawarkan keuntungan atas jawaban yang diterima?
tripleee
1
Ya, saya pikir lebih baik menggunakan perintah built-in daripada menulis skrip shell yang melakukan hal yang sama yang harus dipertahankan sebagai bagian dari basis kode sistem. Bahkan jika fungsionalitas diperlukan sebagai bagian dari skrip shell, perintah di atas juga dapat digunakan sehingga relevan dengan pertanyaan skrip shell.
Daniel Bradley
Ini bukan "built in"; jika terinstal secara default pada beberapa distro, jawaban Anda mungkin harus menentukan distro (dan idealnya termasuk pointer untuk tempat mengunduhnya jika Anda bukan salah satu dari mereka).
tripleee
Sepertinya ini adalah utilitas Ubuntu; tapi itu opsional bahkan di Ubuntu. manpages.ubuntu.com/manpages/bionic/man1/run-one.1.html
tripleee
Patut dicatat: utilitas run-one melakukan persis seperti namanya - Anda hanya dapat menjalankan satu instance dari perintah yang dijalankan dengan run-one-nnnnn. Jawaban lain di sini lebih agnostik yang dapat dieksekusi - mereka tidak peduli dengan isi perintah sama sekali.
David Kohen
1

Saya telah menggunakan skrip berikut dengan sukses besar di banyak server:

pid=`jps -v | grep $INSTALLATION | awk '{print $1}'`
echo $INSTALLATION found at PID $pid 
while [ -e /proc/$pid ]; do sleep 0.1; done

catatan:

  • Ini mencari proses java, jadi saya bisa menggunakan jps, ini jauh lebih konsisten di seluruh distribusi daripada ps
  • $INSTALLATION mengandung cukup banyak jalur proses yang sama sekali tidak ambigu
  • Gunakan tidur sambil menunggu proses mati, hindari sumber daya memonopoli :)

Script ini sebenarnya digunakan untuk mematikan instance tomcat yang sedang berjalan, yang ingin saya matikan (dan tunggu) di baris perintah, jadi meluncurkannya sebagai proses anak bukanlah pilihan bagi saya.

Kevin Wright
sumber
1
grep | awkmasih merupakan antipattern - Anda ingin awk "/$INSTALLATION/ { print \$1 }"mengubah yang tidak berguna grepmenjadi skrip Awk, yang dapat menemukan baris dengan ekspresi reguler itu sendiri dengan sangat baik, terima kasih banyak.
tripleee
0

Saya menggunakan ini untuk Proses npm saya

#!/bin/bash
for (( ; ; ))
do
date +"%T"
echo Start Process
cd /toFolder
sudo process
date +"%T"
echo Crash
sleep 1
done
BitDEVil2K16
sumber