Come faccio a scrivere uno script bash per riavviare un processo se muore?


226

Ho uno script Python che controllerà una coda ed eseguirà un'azione su ogni elemento:

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

Come faccio a scrivere uno script bash che controllerà se è in esecuzione e, in caso contrario, avviarlo. Circa il seguente pseudo codice (o forse dovrebbe fare qualcosa del genere ps | grep?):

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

run checkqueue.py
write processid to processidfile

Lo chiamerò da un crontab:

# crontab
*/5 * * * * /path/to/keepalivescript.sh

4
Solo per aggiungere questo per il 2017. Usa supervisord. crontab non è destinato a svolgere questo tipo di attività. Uno script bash è terribile nell'emettere il vero errore. stackoverflow.com/questions/9301494/...
mootmoot

Che ne dici di usare inittab e respawn invece di altre soluzioni non di sistema? Vedi superuser.com/a/507835/116705
Lars Nordin il

Risposte:


635

Evita file PID, cron o qualsiasi altra cosa che tenti di valutare processi che non sono i loro figli.

C'è un ottimo motivo per cui in UNIX puoi SOLO aspettare i tuoi figli. Qualsiasi metodo (analisi ps, pgrep, memorizzazione di un PID, ...) che tenta di aggirare il problema è difettoso e presenta buchi aperti. Di 'solo di no .

È invece necessario che il processo che monitora il processo sia il genitore del processo. Cosa significa questo? Significa che solo il processo che avvia il processo può attendibilmente attenderne la fine. In bash, questo è assolutamente banale.

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

Il pezzo sopra di codice bash viene eseguito myserverin un untilciclo. La prima riga inizia myservere attende che termini. Al termine, untilcontrolla il suo stato di uscita. Se lo stato di uscita è 0, significa che è terminato con grazia (il che significa che hai chiesto di chiuderlo in qualche modo, e lo ha fatto con successo). In tal caso non vogliamo riavviarlo (abbiamo appena chiesto di spegnerlo!). Se lo stato di uscita è non 0 , untilverrà eseguito il corpo del ciclo, che emette un messaggio di errore sul STDERR e riavvia il loop (torna alla linea 1) dopo 1 secondo .

Perché aspettiamo un secondo? Perché se qualcosa non va nella sequenza di avvio di myservere si blocca immediatamente, avrai un ciclo molto intenso di riavvio e arresto costante nelle tue mani. Il sleep 1toglie il ceppo da quello.

Ora tutto ciò che devi fare è avviare questo script bash (in modo asincrono, probabilmente), e lo monitorerà myservere lo riavvierà secondo necessità. Se vuoi avviare il monitor all'avvio (facendo "sopravvivere" al riavvio del server), puoi programmarlo nel cron (1) del tuo utente con una @rebootregola. Apri le tue regole cron con crontab:

crontab -e

Quindi aggiungi una regola per avviare lo script del monitor:

@reboot /usr/local/bin/myservermonitor

In alternativa; guarda inittab (5) e / etc / inittab. Puoi aggiungere una riga lì dentro per myserveriniziare a un certo livello di init ed essere rigenerato automaticamente.


Modificare.

Vorrei aggiungere alcune informazioni sul perché non utilizzare i file PID. Mentre sono molto popolari; sono anche molto imperfetti e non c'è motivo per non farlo nel modo giusto.

Considera questo:

  1. Riciclo PID (interrompendo il processo sbagliato):

    • /etc/init.d/foo start: start foo, scrivi fooil PID su/var/run/foo.pid
    • Poco dopo: foomuore in qualche modo.
    • Qualche tempo dopo: qualsiasi processo casuale che inizia (chiamalo bar) prende un PID casuale, immagina che prenda fooil vecchio PID.
    • Noti fooche non c'è più: /etc/init.d/foo/restartlegge /var/run/foo.pid, controlla per vedere se è ancora vivo, trova bar, pensa che lo sia foo, lo uccide, ne inizia uno nuovo foo.
  2. I file PID diventano obsoleti. È necessaria una logica troppo complicata (o dovrei dire non banale) per verificare se il file PID è obsoleto e tale logica è nuovamente vulnerabile 1..

  3. Cosa succede se non si ha nemmeno accesso in scrittura o ci si trova in un ambiente di sola lettura?

  4. È inutile complicazioni eccessive; guarda quanto è semplice il mio esempio sopra. Non è necessario complicarlo affatto.

Vedi anche: I file PID sono ancora imperfetti quando lo fai "giusto"?

A proposito; anche peggio dei file PID sta analizzando ps! Non farlo mai.

  1. psè molto insostituibile. Mentre lo trovi su quasi tutti i sistemi UNIX; i suoi argomenti variano notevolmente se si desidera un output non standard. E l'output standard è SOLO per il consumo umano, non per l'analisi tramite script!
  2. L'analisi psporta a MOLTI falsi positivi. Prendi l' ps aux | grep PIDesempio e ora immagina che qualcuno inizi un processo con un numero da qualche parte come argomento che sembra essere lo stesso del PID con cui hai fissato il tuo demone! Immagina due persone che iniziano una sessione X e stai chiedendo a X di uccidere la tua. È solo ogni tipo di male.

Se non vuoi gestire tu stesso il processo; ci sono alcuni sistemi perfettamente validi là fuori che fungeranno da monitor per i tuoi processi. Guarda runit , per esempio.


1
@Chas. Possiede: non penso sia necessario. Sarebbe solo complicare l'implementazione senza una buona ragione. La semplicità è sempre più importante; e se si riavvia spesso, il sonno gli impedirà di avere un impatto negativo sulle risorse di sistema. C'è già un messaggio comunque.
lhunath,

2
@orschiro Non c'è consumo di risorse quando il programma si comporta. Se esiste immediatamente all'avvio, continuamente, il consumo di risorse con uno sleep 1 è ancora del tutto trascurabile.
lhunath,

7
Posso credere che sto solo vedendo questa risposta. Grazie mille!
getWeberForStackExchange il

2
@ TomášZato puoi eseguire il ciclo sopra senza testare il codice di uscita del processo while true; do myprocess; donema nota che ora non c'è modo di fermare il processo.
lhunath,

2
@ SergeyP.akaazure L'unico modo per costringere il genitore a uccidere il bambino all'uscita a Bash è quello di trasformare il bambino in un lavoro e segnalarlo:trap 'kill $(jobs -p)' EXIT; until myserver & wait; do sleep 1; done
lhunath


8

Il modo più semplice per farlo è usare flock su file. Nello script Python lo faresti

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()

In shell puoi effettivamente verificare se è in esecuzione:

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

Ma ovviamente non devi testarlo, perché se è già in esecuzione e lo riavvii, uscirà con 'other instance already running'

Quando il processo termina, tutti i descrittori dei file vengono chiusi e tutti i blocchi vengono rimossi automaticamente.


ciò potrebbe concepibilmente semplificarlo un po 'rimuovendo lo script bash. cosa succede se lo script python si arresta in modo anomalo? il file è sbloccato?
Tom

1
Il blocco dei file viene rilasciato non appena l'applicazione si arresta, uccidendo, naturalmente o in crash.
Christian Witts,

@ Tom ... per essere un po 'più precisi - il blocco non è più attivo non appena il file gestisce è chiuso. Se lo script Python non chiude mai l'handle del file per intento e si assicura che non si chiuda automaticamente tramite l'oggetto file che viene raccolto in modo inutile, la sua chiusura probabilmente significa che lo script è stato chiuso / ucciso. Questo funziona anche per i riavvii e così via.
Charles Duffy,

1
Ci sono modi molto migliori di usare flock... in effetti, la pagina man dimostra esplicitamente come! exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"è l'equivalente bash del tuo Python e lascia il blocco bloccato (quindi se esegui un processo, il blocco rimarrà bloccato fino a quando quel processo non termina).
Charles Duffy,

Ti ho sottovalutato perché il tuo codice è sbagliato. L'uso flockè corretto, ma i tuoi script sono sbagliati. L'unico comando che devi impostare in crontab è:flock -n /tmp/script.lock -c '/path/to/my/script.py'
Rutrus,

6

Dovresti usare monit, uno strumento unix standard in grado di monitorare diverse cose sul sistema e reagire di conseguenza.

Dai documenti: http://mmonit.com/monit/documentation/monit.html#pid_testing

controllare il processo checkqueue.py con pidfile /var/run/checkqueue.pid
       se modificato pid, esegui "checkqueue_restart.sh"

Puoi anche configurare monit per inviarti un'e-mail quando fa un riavvio.


2
Monit è un grande strumento, ma è non è di serie in senso formale di essere specificati sia in POSIX o SUSV.
Charles Duffy,

5
if ! test -f $PIDFILE || ! psgrep `cat $PIDFILE`; then
    restart_process
    # Write PIDFILE
    echo $! >$PIDFILE
fi

bello, questo sta esplodendo abbastanza bene un po 'del mio pseudo codice. due qn: 1) come posso generare PIDFILE? 2) cos'è psgrep? non è sul server Ubuntu.
Tom

ps grep è solo una piccola app che fa lo stesso di ps ax|grep .... Puoi semplicemente installarlo o scrivere una funzione per questo: funzione psgrep () {ps ax | grep -v grep | grep -q "$ 1"}
soulmerge

Ho appena notato che non avevo risposto alla tua prima domanda.
soulmerge

7
Su server molto occupati è possibile che il PID venga riciclato prima del controllo.
vartec,

2

Non sono sicuro di quanto sia portatile tra i sistemi operativi, ma potresti verificare se il tuo sistema contiene il comando 'run-one', ovvero "man run-one". Nello specifico, questo set di comandi include 'run-one-costantemente', che sembra essere esattamente ciò che è necessario.

Dalla pagina man:

run-one-costantemente COMANDO [ARGS]

Nota: ovviamente questo potrebbe essere chiamato dall'interno del tuo script, ma rimuove anche la necessità di avere uno script.


Questo offre qualche vantaggio rispetto alla risposta accettata?
Tripleee,

1
Sì, penso che sia preferibile usare un comando integrato piuttosto che scrivere uno script di shell che faccia la stessa cosa che dovrà essere mantenuta come parte della base di codice di sistema. Anche se la funzionalità è richiesta come parte di uno script di shell, è possibile utilizzare anche il comando precedente, quindi è rilevante per una domanda di script di shell.
Daniel Bradley,

Questo non è "incorporato"; se è installato di default su alcune distro, la tua risposta dovrebbe probabilmente specificare la distro (e idealmente includere un puntatore per dove scaricarlo se il tuo non è uno di loro).
Tripleee,

Sembra che sia un'utilità Ubuntu; ma è facoltativo anche su Ubuntu. manpages.ubuntu.com/manpages/bionic/man1/run-one.1.html
tripleee

Vale la pena notare che le utility run-one fanno esattamente ciò che dice il loro nome: è possibile eseguire solo un'istanza di qualsiasi comando eseguito con run-one-nnnnn. Altre risposte qui sono più agnostiche eseguibili: a loro non interessa affatto il contenuto del comando.
David Kohen,

1

Ho usato il seguente script con grande successo su numerosi server:

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

Appunti:

  • Sta cercando un processo Java, quindi posso usare jps, questo è molto più coerente tra le distribuzioni rispetto a ps
  • $INSTALLATION contiene abbastanza del percorso del processo che è totalmente inequivocabile
  • Usa il sonno mentre aspetti che il processo muoia, evita le risorse di hogging :)

Questo script viene effettivamente utilizzato per chiudere un'istanza in esecuzione di Tomcat, che voglio chiudere (e attendere) dalla riga di comando, quindi avviarlo come processo figlio non è un'opzione per me.


1
grep | awkè ancora un antipattern - vuoi awk "/$INSTALLATION/ { print \$1 }"confondere l'inutile grepnella sceneggiatura di Awk, che può trovare le linee con l'espressione regolare stessa molto bene, grazie mille.
Tripleee,

0

Lo uso per il mio processo npm

#!/bin/bash
for (( ; ; ))
do
date +"%T"
echo Start Process
cd /toFolder
sudo process
date +"%T"
echo Crash
sleep 1
done
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.