Comment écrire un script bash pour redémarrer un processus s'il meurt?

226

J'ai un script python qui vérifiera une file d'attente et effectuera une action sur chaque élément:

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

Comment puis-je écrire un script bash qui vérifiera s'il est en cours d'exécution et sinon, le démarrera. À peu près le pseudo-code suivant (ou peut-être qu'il devrait faire quelque chose comme ps | grep?):

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

run checkqueue.py
write processid to processidfile

J'appellerai ça depuis une crontab:

# crontab
*/5 * * * * /path/to/keepalivescript.sh
À M
la source
4
Juste pour ajouter cela pour 2017. Utilisez supervisord. crontab n'est pas censé faire ce genre de tâche. Un script bash est terrible en émettant la vraie erreur. stackoverflow.com/questions/9301494/…
mootmoot
Que diriez-vous d'utiliser inittab et respawn au lieu d'autres solutions non-système? Voir superuser.com/a/507835/116705
Lars Nordin

Réponses:

635

Évitez les fichiers PID, les crons ou tout autre élément qui tente d'évaluer des processus qui ne sont pas leurs enfants.

Il y a une très bonne raison pour laquelle sous UNIX, vous ne pouvez attendre que vos enfants. Toute méthode (analyse ps, pgrep, stockage d'un PID, ...) qui essaie de contourner ce problème est défectueuse et comporte des trous béants. Dites simplement non .

Au lieu de cela, vous avez besoin du processus qui surveille votre processus pour être le parent du processus. Qu'est-ce que ça veut dire? Cela signifie que seul le processus qui démarre votre processus peut attendre de manière fiable qu'il se termine. En bash, c'est absolument trivial.

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

Le morceau de code bash ci-dessus s'exécute myserveren untilboucle. La première ligne démarre myserveret attend qu'elle se termine. Une fois terminé, untilvérifie son état de sortie. Si l'état de sortie est 0, cela signifie qu'il s'est terminé avec élégance (ce qui signifie que vous lui avez demandé de s'arrêter d'une manière ou d'une autre, et il l'a fait avec succès). Dans ce cas, nous ne voulons pas le redémarrer (nous venons de lui demander de s'arrêter!). Si l'état de sortie n'est pas 0 , untilexécutera le corps de la boucle, qui émet un message d'erreur sur STDERR et redémarre la boucle (retour à la ligne 1) après 1 seconde .

Pourquoi attendons-nous une seconde? Parce que si quelque chose ne va pas avec la séquence de démarrage de myserveret qu'elle se bloque immédiatement, vous aurez une boucle très intensive de redémarrage et de plantage constant entre vos mains. Le sleep 1enlève la souche à partir de cela.

Maintenant, tout ce que vous devez faire est de démarrer ce script bash (de manière asynchrone, probablement), et il le surveillera myserveret le redémarrera si nécessaire. Si vous souhaitez démarrer le moniteur au démarrage (pour que le serveur "survienne" aux redémarrages), vous pouvez le planifier dans le cron (1) de votre utilisateur avec une @rebootrègle. Ouvrez vos règles cron avec crontab:

crontab -e

Ajoutez ensuite une règle pour démarrer votre script de moniteur:

@reboot /usr/local/bin/myservermonitor

Alternativement; regardez inittab (5) et / etc / inittab. Vous pouvez y ajouter une ligne pour myservercommencer à un certain niveau d'initialisation et être réapparu automatiquement.


Éditer.

Permettez-moi d'ajouter quelques informations sur pourquoi ne pas utiliser les fichiers PID. Bien qu'ils soient très populaires; ils sont également très imparfaits et il n'y a aucune raison pour que vous ne le fassiez pas simplement de la bonne façon.

Considère ceci:

  1. Recyclage PID (tuant le mauvais processus):

    • /etc/init.d/foo start: démarrer foo, écrire foole PID de/var/run/foo.pid
    • Un peu plus tard: foomeurt en quelque sorte.
    • Un peu plus tard: tout processus aléatoire qui démarre (appelez-le bar) prend un PID aléatoire, imaginez qu'il prenne fool'ancien PID.
    • Vous remarquez que c'est fooparti: /etc/init.d/foo/restartlit /var/run/foo.pid, vérifie pour voir s'il est toujours vivant, trouve bar, pense que c'est foo, tue, commence un nouveau foo.
  2. Les fichiers PID deviennent périmés. Vous avez besoin d'une logique trop compliquée (ou devrais-je dire, non triviale) pour vérifier si le fichier PID est périmé, et une telle logique est à nouveau vulnérable 1..

  3. Et si vous n'avez même pas accès en écriture ou si vous êtes dans un environnement en lecture seule?

  4. C'est une surcompensation inutile; voyez à quel point mon exemple ci-dessus est simple. Pas besoin de compliquer ça du tout.

Voir aussi: Les fichiers PID sont-ils toujours défectueux lorsqu'ils le font «correctement»?

Au fait; pire encore que l'analyse des fichiers PID ps! Ne fais jamais ça.

  1. psest très impraticable. Bien que vous le trouviez sur presque tous les systèmes UNIX; ses arguments varient considérablement si vous souhaitez une sortie non standard. Et la sortie standard est UNIQUEMENT pour la consommation humaine, pas pour l'analyse par script!
  2. L'analyse psconduit à BEAUCOUP de faux positifs. Prenons l' ps aux | grep PIDexemple, et imaginez maintenant que quelqu'un démarre un processus avec un nombre quelque part comme argument qui se trouve être le même que le PID avec lequel vous avez regardé votre démon! Imaginez deux personnes qui démarrent une session X et vous attendez que X tue la vôtre. C'est juste toutes sortes de mauvais.

Si vous ne voulez pas gérer le processus vous-même; il existe des systèmes parfaitement bons qui serviront de moniteur pour vos processus. Regardez runit , par exemple.

lhunath
la source
1
@Chas. Ownes: Je ne pense pas que ce soit nécessaire. Cela ne ferait que compliquer la mise en œuvre sans raison valable. La simplicité est toujours plus importante; et s'il redémarre souvent, le sommeil l'empêchera d'avoir un impact négatif sur les ressources de votre système. Il y a déjà un message de toute façon.
lhunath
2
@orschiro Il n'y a pas de consommation de ressources lorsque le programme se comporte. S'il existe immédiatement au lancement, en continu, la consommation de ressources avec un sommeil 1 est encore tout à fait négligeable.
lhunath
7
Je peux croire que je vois juste cette réponse. Merci beaucoup!
getWeberForStackExchange
2
@ TomášZato vous pouvez faire la boucle ci-dessus sans tester le code de sortie du processus while true; do myprocess; donemais notez qu'il n'y a maintenant aucun moyen d'arrêter le processus.
lhunath
2
@ SergeyP.akaazure La seule façon de forcer le parent à tuer l'enfant à la sortie en bash est de transformer l'enfant en travail et de le signaler:trap 'kill $(jobs -p)' EXIT; until myserver & wait; do sleep 1; done
lhunath
33

Jetez un œil à monit ( http://mmonit.com/monit/ ). Il gère le démarrage, l'arrêt et le redémarrage de votre script et peut effectuer des vérifications de l'état et redémarrer si nécessaire.

Ou faites un simple script:

while true
do
/your/script
sleep 1
done
Bernd
la source
4
Monit est exactement ce que vous recherchez.
Sarke
4
"tandis que 1" ne fonctionne pas. Vous avez besoin de "while [1]" ou "while true" ou "while:". Voir unix.stackexchange.com/questions/367108/what-does- while
Curtis Yallop
8

La façon la plus simple de le faire est d'utiliser flock on file. En script Python, vous feriez

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

Dans le shell, vous pouvez réellement tester s'il fonctionne:

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

Mais bien sûr, vous n'avez pas à tester, car s'il est déjà en cours d'exécution et que vous le redémarrez, il se fermera avec 'other instance already running'

À la fin du processus, tous ses descripteurs de fichiers sont fermés et tous les verrous sont automatiquement supprimés.

vartec
la source
cela pourrait éventuellement simplifier un peu en supprimant le script bash. que se passe-t-il si le script python plante? le fichier est-il déverrouillé?
Tom
1
Le verrouillage du fichier est libéré dès que l'application s'arrête, soit en tuant, naturellement ou en plantant.
Christian Witts
@Tom ... pour être un peu plus précis - le verrou n'est plus actif dès que le descripteur de fichier est fermé. Si le script Python ne ferme jamais le descripteur de fichier par intention et s'assure qu'il ne se ferme pas automatiquement via l'objet fichier en cours de récupération, la fermeture signifie probablement que le script a quitté / a été tué. Cela fonctionne même pour les redémarrages et autres.
Charles Duffy
1
Il existe de bien meilleures façons d'utiliser flock... en fait, la page de manuel montre explicitement comment! exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"est l'équivalent bash de votre Python, et laisse le verrou maintenu (donc si vous exécutez ensuite un processus, le verrou restera maintenu jusqu'à la fin de ce processus).
Charles Duffy
Je vous ai déçu parce que votre code est incorrect. L'utilisation flockest la bonne façon, mais vos scripts sont incorrects. La seule commande que vous devez définir dans crontab est:flock -n /tmp/script.lock -c '/path/to/my/script.py'
Rutrus
6

Vous devez utiliser monit, un outil Unix standard qui peut surveiller différentes choses sur le système et réagir en conséquence.

Depuis les documents: http://mmonit.com/monit/documentation/monit.html#pid_testing

vérifier le processus checkqueue.py avec le fichier pid /var/run/checkqueue.pid
       si changé pid alors exécutez "checkqueue_restart.sh"

Vous pouvez également configurer monit pour vous envoyer un e-mail lors d'un redémarrage.

clofresh
la source
2
Monit est un excellent outil, mais il n'est pas standard dans le sens formel d'être spécifié dans POSIX ou SUSV.
Charles Duffy
5
if ! test -f $PIDFILE || ! psgrep `cat $PIDFILE`; then
    restart_process
    # Write PIDFILE
    echo $! >$PIDFILE
fi
soulmerge
la source
cool, c'est étoffer une partie de mon pseudo code assez bien. deux qns: 1) comment générer un PIDFILE? 2) qu'est-ce que psgrep? ce n'est pas sur le serveur ubuntu.
Tom
ps grep est juste une petite application qui fait la même chose que ps ax|grep .... Vous pouvez simplement l'installer ou écrire une fonction pour cela: function psgrep () {ps ax | grep -v grep | grep -q "$ 1"}
soulmerge
Je viens de remarquer que je n'avais pas répondu à votre première question.
soulmerge
7
Sur un serveur très occupé, il est possible que le PID soit recyclé avant de vérifier.
vartec
2

Je ne sais pas à quel point il est portable sur les systèmes d'exploitation, mais vous pouvez vérifier si votre système contient la commande «run-one», c'est-à-dire «man run-one». Plus précisément, cet ensemble de commandes comprend «run-one-constant», ce qui semble être exactement ce dont vous avez besoin.

Depuis la page de manuel:

commande run-one-constamment [ARGS]

Remarque: évidemment, cela pourrait être appelé à partir de votre script, mais cela supprime également la nécessité d'avoir un script.

Daniel Bradley
la source
Est-ce que cela offre un avantage sur la réponse acceptée?
tripleee
1
Oui, je pense qu'il est préférable d'utiliser une commande intégrée que d'écrire un script shell qui fait la même chose qui devra être maintenu dans le cadre de la base de code système. Même si la fonctionnalité est requise dans le cadre d'un script shell, la commande ci-dessus peut également être utilisée afin qu'elle soit pertinente pour une question de script shell.
Daniel Bradley
Ce n'est pas "intégré"; s'il est installé par défaut sur une distribution, votre réponse devrait probablement spécifier la distribution (et idéalement inclure un pointeur pour savoir où la télécharger si la vôtre n'en fait pas partie).
tripleee
On dirait que c'est un utilitaire Ubuntu; mais c'est facultatif même sur Ubuntu. manpages.ubuntu.com/manpages/bionic/man1/run-one.1.html
tripleee
À noter: les utilitaires run-one font exactement ce que leur nom dit - vous ne pouvez exécuter qu'une seule instance d'une commande exécutée avec run-one-nnnnn. Les autres réponses ici sont plus indépendantes des exécutables - elles ne se soucient pas du tout du contenu de la commande.
David Kohen
1

J'ai utilisé le script suivant avec beaucoup de succès sur de nombreux serveurs:

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

Remarques:

  • Il cherche un processus java, donc je peux utiliser jps, c'est beaucoup plus cohérent entre les distributions que ps
  • $INSTALLATION contient suffisamment de chemin de processus qui est c'est totalement sans ambiguïté
  • Utilisez le sommeil en attendant la fin du processus, évitez de monopoliser les ressources :)

Ce script est en fait utilisé pour arrêter une instance de tomcat en cours d'exécution, que je veux arrêter (et attendre) sur la ligne de commande, donc le lancer en tant que processus enfant n'est tout simplement pas une option pour moi.

Kevin Wright
la source
1
grep | awkest toujours un contre- modèle - vous voulez awk "/$INSTALLATION/ { print \$1 }"confondre l'inutile grepdans le script Awk, qui peut très bien trouver des lignes par expression régulière, merci beaucoup.
tripleee
0

J'utilise ceci pour mon processus npm

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