Wie schreibe ich ein Bash-Skript, um einen Prozess neu zu starten, wenn er stirbt?

226

Ich habe ein Python-Skript, das eine Warteschlange überprüft und für jedes Element eine Aktion ausführt:

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

Wie schreibe ich ein Bash-Skript, das überprüft, ob es ausgeführt wird, und wenn nicht, starte es. Etwa der folgende Pseudocode (oder sollte er vielleicht so etwas tun ps | grep?):

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

run checkqueue.py
write processid to processidfile

Ich werde das von einem Crontab nennen:

# crontab
*/5 * * * * /path/to/keepalivescript.sh
Tom
quelle
4
Nur um dies für 2017 hinzuzufügen. Verwenden Sie Supervisord. crontab ist nicht dazu gedacht, diese Art von Aufgabe zu erledigen. Ein Bash-Skript ist schrecklich, wenn es den wirklichen Fehler ausgibt. stackoverflow.com/questions/9301494/…
mootmoot
Wie wäre es mit inittab und respawn anstelle anderer Nicht-Systemlösungen? Siehe superuser.com/a/507835/116705
Lars Nordin

Antworten:

635

Vermeiden Sie PID-Dateien, Crones oder alles andere, was versucht, Prozesse zu bewerten, die nicht ihre Kinder sind.

Es gibt einen sehr guten Grund, warum Sie unter UNIX NUR auf Ihre Kinder warten können. Jede Methode (ps parsing, pgrep, Speichern einer PID, ...), die versucht, dies zu umgehen, ist fehlerhaft und weist klaffende Löcher auf. Sag einfach nein .

Stattdessen muss der Prozess, der Ihren Prozess überwacht, der übergeordnete Prozess sein. Was bedeutet das? Dies bedeutet, dass nur der Prozess, der Ihren Prozess startet , zuverlässig auf das Ende warten kann. In Bash ist dies absolut trivial.

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

Der obige Bash-Code wird myserverin einer untilSchleife ausgeführt. Die erste Zeile beginnt myserverund wartet auf das Ende. Wenn es endet, untilüberprüft es seinen Exit-Status. Wenn der Exit-Status lautet 0, bedeutet dies, dass er ordnungsgemäß beendet wurde (was bedeutet, dass Sie ihn aufgefordert haben, ihn irgendwie herunterzufahren, und dies erfolgreich). In diesem Fall möchten wir es nicht neu starten (wir haben es nur gebeten, herunterzufahren!). Wenn der Exit-Status nicht lautet 0, untilwird der Schleifenkörper ausgeführt, der eine Fehlermeldung bei STDERR ausgibt und die Schleife (zurück zu Zeile 1) nach 1 Sekunde neu startet .

Warum warten wir eine Sekunde? Denn wenn etwas mit der Startsequenz von nicht stimmt myserverund es sofort abstürzt, haben Sie eine sehr intensive Schleife von ständigem Neustart und Absturz an Ihren Händen. Das sleep 1nimmt die Belastung davon weg.

Jetzt müssen Sie nur noch dieses Bash-Skript starten (wahrscheinlich asynchron), und es wird myserveres nach Bedarf überwachen und neu starten. Wenn Sie den Monitor beim Booten starten möchten (damit der Server einen Neustart "überlebt"), können Sie ihn mit einer @rebootRegel im Cron (1) Ihres Benutzers planen . Öffnen Sie Ihre Cron-Regeln mit crontab:

crontab -e

Fügen Sie dann eine Regel hinzu, um Ihr Monitorskript zu starten:

@reboot /usr/local/bin/myservermonitor

Alternative; Schauen Sie sich inittab (5) und / etc / inittab an. Sie können dort eine Zeile hinzufügen, myserverum bei einer bestimmten Init-Ebene zu beginnen und automatisch neu zu erscheinen.


Bearbeiten.

Lassen Sie mich einige Informationen hinzufügen, warum Sie keine PID-Dateien verwenden sollten. Während sie sehr beliebt sind; Sie sind auch sehr fehlerhaft und es gibt keinen Grund, warum Sie es nicht einfach richtig machen würden.

Bedenken Sie:

  1. PID-Recycling (Tötung des falschen Prozesses):

    • /etc/init.d/foo start: Start foo, schreibe foodie PID in/var/run/foo.pid
    • Eine Weile später: foostirbt irgendwie.
    • Eine Weile später: Jeder zufällige Prozess, der startet (es nennt bar), nimmt eine zufällige PID, stellen Sie sich vor, er nimmt foodie alte PID.
    • Sie bemerken foo, dass es weg ist: /etc/init.d/foo/restartliest /var/run/foo.pid, prüft, ob es noch lebt, findet bar, denkt foo, tötet es, startet ein neues foo.
  2. PID-Dateien sind veraltet. Sie benötigen eine überkomplizierte (oder sollte ich sagen, nicht triviale) Logik, um zu überprüfen, ob die PID-Datei veraltet ist und eine solche Logik erneut anfällig ist 1..

  3. Was ist, wenn Sie nicht einmal Schreibzugriff haben oder sich in einer schreibgeschützten Umgebung befinden?

  4. Es ist sinnlose Überkomplikation; Sehen Sie, wie einfach mein Beispiel oben ist. Das muss man überhaupt nicht komplizieren.

Siehe auch: Sind PID-Dateien immer noch fehlerhaft, wenn sie richtig gemacht werden?

Apropos; Noch schlimmer als PID-Dateien ist das Parsen ps! Tu das niemals.

  1. psist sehr unsportlich. Während Sie es auf fast jedem UNIX-System finden; Die Argumente variieren stark, wenn Sie eine nicht standardmäßige Ausgabe wünschen. Und die Standardausgabe ist NUR für den menschlichen Verzehr bestimmt, nicht für das Parsing per Skript!
  2. Das Parsen psführt zu einer Menge falsch positiver Ergebnisse . Nehmen Sie das ps aux | grep PIDBeispiel und stellen Sie sich nun vor, jemand startet irgendwo einen Prozess mit einer Zahl als Argument, das zufällig mit der PID übereinstimmt, mit der Sie Ihren Daemon angestarrt haben! Stellen Sie sich zwei Personen vor, die eine X-Sitzung beginnen und nach X greifen, um Ihre zu töten. Es ist einfach alles schlecht.

Wenn Sie den Prozess nicht selbst verwalten möchten; Es gibt einige sehr gute Systeme, die als Monitor für Ihre Prozesse dienen. Schauen Sie sich zum Beispiel Runit an.

lhunath
quelle
1
@Chas. Ownes: Ich denke nicht, dass das notwendig ist. Dies würde die Implementierung ohne guten Grund nur erschweren. Einfachheit ist immer wichtiger; und wenn es häufig neu gestartet wird, verhindert der Schlaf, dass es negative Auswirkungen auf Ihre Systemressourcen hat. Es gibt sowieso schon eine Nachricht.
lhunath
2
@orschiro Es gibt keinen Ressourcenverbrauch, wenn sich das Programm verhält. Wenn es unmittelbar beim Start kontinuierlich vorhanden ist, ist der Ressourcenverbrauch mit einem Schlaf 1 immer noch völlig vernachlässigbar.
lhunath
7
Kann glauben, ich sehe nur diese Antwort. Vielen Dank!
getWeberForStackExchange
2
@ TomášZato Sie können die obige Schleife ausführen, ohne den Exit-Code des Prozesses zu testen. Beachten Sie while true; do myprocess; donejedoch, dass es jetzt keine Möglichkeit gibt, den Prozess zu stoppen.
lhunath
2
@ SergeyP.akaazure Die einzige Möglichkeit, die Eltern zu zwingen, das Kind beim Verlassen in Bash zu töten, besteht darin, das Kind in einen Job zu verwandeln und es zu signalisieren:trap 'kill $(jobs -p)' EXIT; until myserver & wait; do sleep 1; done
lhunath
33

Schauen Sie sich monit an ( http://mmonit.com/monit/ ). Es übernimmt das Starten, Stoppen und Neustarten Ihres Skripts und kann bei Bedarf Integritätsprüfungen und Neustarts durchführen.

Oder machen Sie ein einfaches Skript:

while true
do
/your/script
sleep 1
done
Bernd
quelle
4
Monit ist genau das, wonach Sie suchen.
Sarke
4
"while 1" funktioniert nicht. Sie benötigen "while [1]" oder "while true" oder "while:". Siehe unix.stackexchange.com/questions/367108/what-does-while-mean
Curtis Yallop
8

Der einfachste Weg, dies zu tun, ist die Verwendung von Flock-on-File. In Python-Skript würden Sie tun

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 der Shell können Sie tatsächlich testen, ob es ausgeführt wird:

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

Aber natürlich müssen Sie nicht testen, denn wenn es bereits ausgeführt wird und Sie es neu starten, wird es mit beendet 'other instance already running'

Wenn der Prozess stirbt, werden alle Dateideskriptoren geschlossen und alle Sperren werden automatisch entfernt.

vartec
quelle
das könnte es möglicherweise ein wenig vereinfachen, indem das Bash-Skript entfernt wird. Was passiert, wenn das Python-Skript abstürzt? Ist die Datei entsperrt?
Tom
1
Die Dateisperre wird aufgehoben, sobald die Anwendung gestoppt wird, entweder durch Beenden, natürlich oder durch Absturz.
Christian Witts
@Tom ... um etwas genauer zu sein - die Sperre ist nicht mehr aktiv, sobald das aktivierte Dateihandle geschlossen wird. Wenn das Python-Skript das Dateihandle niemals absichtlich schließt und sicherstellt, dass es nicht automatisch über das zu sammelnde Dateiobjekt geschlossen wird, bedeutet das Schließen wahrscheinlich, dass das Skript beendet / beendet wurde. Dies funktioniert auch bei Neustarts und dergleichen.
Charles Duffy
1
Es gibt viel bessere Verwendungsmöglichkeiten flock... in der Tat zeigt die Manpage explizit, wie! exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"ist die Bash-Entsprechung zu Python und lässt die Sperre beibehalten (wenn Sie also einen Prozess ausführen, bleibt die Sperre so lange erhalten, bis dieser Prozess beendet wird).
Charles Duffy
Ich habe dich herabgestimmt, weil dein Code falsch ist. Die Verwendung flockist der richtige Weg, aber Ihre Skripte sind falsch. Der einzige Befehl, den Sie in crontab setzen müssen, ist:flock -n /tmp/script.lock -c '/path/to/my/script.py'
Rutrus
6

Sie sollten monit verwenden, ein Standard-Unix-Tool, das verschiedene Dinge auf dem System überwachen und entsprechend reagieren kann.

Aus den Dokumenten: http://mmonit.com/monit/documentation/monit.html#pid_testing

Überprüfen Sie den Prozess checkqueue.py mit pidfile /var/run/checkqueue.pid
       Wenn die PID geändert wurde, wird "checkqueue_restart.sh" ausgeführt.

Sie können monit auch so konfigurieren, dass Sie beim Neustart eine E-Mail erhalten.

Clofresh
quelle
2
Monit ist ein großartiges Tool, aber es ist kein Standard im formalen Sinne , wenn es in POSIX oder SUSV angegeben wird.
Charles Duffy
5
if ! test -f $PIDFILE || ! psgrep `cat $PIDFILE`; then
    restart_process
    # Write PIDFILE
    echo $! >$PIDFILE
fi
Soulmerge
quelle
cool, das ist ein Teil meines Pseudocodes ziemlich gut. zwei qns: 1) Wie generiere ich PIDFILE? 2) Was ist Psgrep? Es ist nicht auf Ubuntu-Server.
Tom
ps grep ist nur eine kleine App, die das Gleiche tut wie ps ax|grep .... Sie können es einfach installieren oder eine Funktion dafür schreiben: function psgrep () {ps ax | grep -v grep | grep -q "$ 1"}
soulmerge
Ich habe gerade bemerkt, dass ich Ihre erste Frage nicht beantwortet habe.
Soulmerge
7
Auf einem stark ausgelasteten Server wird die PID möglicherweise recycelt, bevor Sie sie überprüfen.
Vartec
2

Ich bin nicht sicher, wie portabel es zwischen Betriebssystemen ist, aber Sie können überprüfen, ob Ihr System den Befehl 'run-one' enthält, dh "man run-one". Insbesondere enthält dieser Befehlssatz "Run-One-Constant", was genau das zu sein scheint, was benötigt wird.

Von der Manpage:

Run-One-Ständig BEFEHL [ARGS]

Hinweis: Dies kann natürlich aus Ihrem Skript heraus aufgerufen werden, macht aber auch die Notwendigkeit eines Skripts überflüssig.

Daniel Bradley
quelle
Bietet dies einen Vorteil gegenüber der akzeptierten Antwort?
Tripleee
1
Ja, ich denke, es ist vorzuziehen, einen integrierten Befehl zu verwenden, als ein Shell-Skript zu schreiben, das dasselbe tut, was als Teil der Systemcodebasis verwaltet werden muss. Selbst wenn die Funktionalität als Teil eines Shell-Skripts erforderlich ist, kann der obige Befehl auch verwendet werden, sodass er für eine Shell-Skriptfrage relevant ist.
Daniel Bradley
Dies ist nicht "eingebaut"; Wenn es standardmäßig in einer Distribution installiert ist, sollte Ihre Antwort wahrscheinlich die Distribution angeben (und im Idealfall einen Zeiger enthalten, wo Sie es herunterladen können, wenn Ihre nicht dazu gehört).
Tripleee
Sieht aus wie ein Ubuntu-Dienstprogramm; aber es ist sogar unter Ubuntu optional. manpages.ubuntu.com/manpages/bionic/man1/run-one.1.html
tripleee
Bemerkenswert: Die Dienstprogramme run-one machen genau das, was ihr Name sagt - Sie können nur eine Instanz eines Befehls ausführen, der mit run-one-nnnnn ausgeführt wird. Andere Antworten hier sind ausführbarer - sie kümmern sich überhaupt nicht um den Inhalt des Befehls.
David Kohen
1

Ich habe das folgende Skript mit großem Erfolg auf zahlreichen Servern verwendet:

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

Anmerkungen:

  • Es wird nach einem Java-Prozess gesucht, damit ich jps verwenden kann. Dies ist über Distributionen hinweg viel konsistenter als ps
  • $INSTALLATION enthält genug von dem Prozesspfad, der völlig eindeutig ist
  • Verwenden Sie den Schlaf, während Sie darauf warten, dass der Prozess abläuft. Vermeiden Sie es, Ressourcen zu verbrauchen :)

Dieses Skript wird tatsächlich verwendet, um eine laufende Instanz von Tomcat herunterzufahren, die ich über die Befehlszeile herunterfahren (und warten) möchte. Daher ist es für mich einfach keine Option, sie als untergeordneten Prozess zu starten.

Kevin Wright
quelle
1
grep | awkist immer noch ein Antimuster - Sie möchten awk "/$INSTALLATION/ { print \$1 }"das Nutzlose grepin das Awk-Skript einbinden, das durch regulären Ausdruck selbst sehr gut Zeilen finden kann. Vielen Dank.
Tripleee
0

Ich benutze dies für meinen npm-Prozess

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