¿Cómo escribo un script bash para reiniciar un proceso si muere?

226

Tengo un script de Python que verificará una cola y realizará una acción en cada elemento:

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

¿Cómo escribo un script bash que verificará si se está ejecutando y, si no, inícielo? Aproximadamente el siguiente pseudocódigo (¿o tal vez debería hacer algo así ps | grep?):

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

run checkqueue.py
write processid to processidfile

Llamaré eso desde un crontab:

# crontab
*/5 * * * * /path/to/keepalivescript.sh
Tom
fuente
44
Solo para agregar esto para 2017. Use supervisor. crontab no significa hacer este tipo de tarea. Un script bash es terrible al emitir el error real. stackoverflow.com/questions/9301494/…
mootmoot
¿Qué tal usar inittab y reaparecer en lugar de otras soluciones que no son del sistema? Ver superuser.com/a/507835/116705
Lars Nordin

Respuestas:

635

Evite los archivos PID, crons o cualquier otra cosa que intente evaluar procesos que no sean sus hijos.

Hay una muy buena razón por la cual en UNIX, SOLO puede esperar a sus hijos. Cualquier método (análisis ps, pgrep, almacenamiento de un PID, ...) que intente solucionarlo es defectuoso y tiene agujeros enormes. Solo di que no .

En su lugar, necesita que el proceso que supervisa su proceso sea el padre del proceso. ¿Qué significa esto? Significa que solo el proceso que inicia su proceso puede esperar confiablemente a que termine. En bash, esto es absolutamente trivial.

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

La parte anterior del código bash se ejecuta myserveren un untilbucle. La primera línea comienza myservery espera a que termine. Cuando termina, untilverifica su estado de salida. Si el estado de salida es 0, significa que terminó con gracia (lo que significa que le pidió que se cerrara de alguna manera, y lo hizo con éxito). En ese caso, no queremos reiniciarlo (¡solo le pedimos que se apague!). Si el estado de salida no es 0, untilejecutará el cuerpo del bucle, que emite un mensaje de error en STDERR y reinicia el bucle (de vuelta a la línea 1) después de 1 segundo .

¿Por qué esperamos un segundo? Porque si algo está mal con la secuencia de inicio myservery se bloquea de inmediato, tendrá un ciclo muy intenso de reinicio constante y bloqueo en sus manos. El sleep 1quita la tensión de eso.

Ahora todo lo que necesita hacer es iniciar este script bash (probablemente de forma asincrónica), y lo monitoreará myservery reiniciará según sea necesario. Si desea iniciar el monitor en el arranque (haciendo que el servidor "sobreviva" los reinicios), puede programarlo en el cron de su usuario (1) con una @rebootregla. Abra sus reglas cron con crontab:

crontab -e

Luego agregue una regla para iniciar su script de monitor:

@reboot /usr/local/bin/myservermonitor

Alternativamente; mira inittab (5) y / etc / inittab. Puede agregar una línea allí para myservercomenzar en un cierto nivel de inicio y reaparecer automáticamente.


Editar.

Permítanme agregar información sobre por qué no usar archivos PID. Si bien son muy populares; también son muy defectuosos y no hay razón para que no lo hagas de la manera correcta.

Considera esto:

  1. Reciclaje de PID (matar el proceso incorrecto):

    • /etc/init.d/foo start: inicio foo, escriba fooel PID en/var/run/foo.pid
    • Un rato después: foomuere de alguna manera.
    • Un tiempo después: cualquier proceso aleatorio que se inicie (llámelo bar) toma un PID aleatorio, imagínelo tomando fooel PID anterior.
    • Te das cuenta foode que se ha ido: /etc/init.d/foo/restartlee /var/run/foo.pid, verifica si todavía está vivo, lo encuentra bar, piensa que lo foomata, comienza un nuevo foo.
  2. Los archivos PID quedan obsoletos. Necesita una lógica demasiado complicada (o debería decir, no trivial) para verificar si el archivo PID está obsoleto y si dicha lógica es nuevamente vulnerable 1..

  3. ¿Qué sucede si ni siquiera tiene acceso de escritura o está en un entorno de solo lectura?

  4. Es una sobrecomplicación sin sentido; vea cuán simple es mi ejemplo anterior. No hay necesidad de complicar eso, en absoluto.

Ver también: ¿Los archivos PID todavía tienen fallas cuando lo hacen 'bien'?

Por cierto; incluso peor que los archivos PID está analizando ps! Nunca hagas esto.

  1. psEs muy inportable. Si bien lo encuentra en casi todos los sistemas UNIX; sus argumentos varían mucho si desea una salida no estándar. ¡Y la salida estándar es SOLO para consumo humano, no para análisis con secuencias de comandos!
  2. El análisis psconduce a MUCHOS falsos positivos. ¡Tome el ps aux | grep PIDejemplo, y ahora imagine que alguien comienza un proceso con un número en algún lugar como argumento que resulta ser el mismo que el PID con el que miró a su demonio! Imagina a dos personas que comienzan una sesión X y estás buscando X para matar a la tuya. Es todo tipo de cosas malas.

Si no desea administrar el proceso usted mismo; Existen algunos sistemas perfectamente buenos que actuarán como monitor de sus procesos. Mira en runit , por ejemplo.

lhunath
fuente
1
@Chas. Ownes: No creo que sea necesario. Simplemente complicaría la implementación sin una buena razón. La simplicidad siempre es más importante; y si se reinicia con frecuencia, la suspensión evitará que tenga un impacto negativo en los recursos del sistema. Ya hay un mensaje de todos modos.
lhunath
2
@orschiro No hay consumo de recursos cuando el programa se comporta. Si existe inmediatamente en el lanzamiento, de forma continua, el consumo de recursos con un sueño 1 sigue siendo completamente insignificante.
lhunath
77
Puedo creer que solo estoy viendo esta respuesta. ¡Muchas gracias!
getWeberForStackExchange
2
@ TomášZato puede hacer el ciclo anterior sin probar el código de salida del proceso, while true; do myprocess; donepero tenga en cuenta que ahora no hay forma de detener el proceso.
lhunath
2
@ SergeyP.akaazure La única manera de obligar a los padres a matar al niño en la salida en bash es convertir al niño en un puesto de trabajo y señalar que:trap 'kill $(jobs -p)' EXIT; until myserver & wait; do sleep 1; done
lhunath
33

Echa un vistazo a monit ( http://mmonit.com/monit/ ). Maneja el inicio, la detención y el reinicio de su secuencia de comandos y puede realizar comprobaciones de estado y reinicios si es necesario.

O haz un script simple:

while true
do
/your/script
sleep 1
done
Bernd
fuente
44
Monit es exactamente lo que estás buscando.
Sarke
44
"while 1" no funciona. Necesita "while [1]" o "while true" o "while:". Ver unix.stackexchange.com/questions/367108/what-does-while-mean
Curtis Yallop
8

La forma más fácil de hacerlo es usar flock en el archivo. En el script Python que harías

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

En shell puedes probar si se está ejecutando:

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

Pero, por supuesto, no tiene que probar, porque si ya se está ejecutando y lo reinicia, saldrá con 'other instance already running'

Cuando el proceso muere, todos sus descriptores de archivo se cierran y todos los bloqueos se eliminan automáticamente.

vartec
fuente
eso podría simplificarlo un poco eliminando el script bash. ¿Qué sucede si el script de Python se bloquea? ¿Está desbloqueado el archivo?
Tom
1
El bloqueo de archivos se libera tan pronto como la aplicación se detiene, ya sea por matar, naturalmente o bloquearse.
Christian Witts
@Tom ... para ser un poco más preciso: el bloqueo ya no está activo tan pronto como se cierra el identificador de archivo que está activado. Si la secuencia de comandos de Python nunca cierra el identificador del archivo por intención, y se asegura de que no se cierre automáticamente a través del objeto de archivo que se está recolectando basura, entonces el cierre probablemente significa que la secuencia de comandos salió / fue eliminada. Esto funciona incluso para reinicios y tal.
Charles Duffy
1
Hay formas mucho mejores de usar flock... de hecho, la página del manual demuestra explícitamente cómo. exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"es el bash equivalente a su Python, y deja el bloqueo retenido (por lo tanto, si luego ejecuta un proceso, el bloqueo permanecerá retenido hasta que el proceso salga).
Charles Duffy el
Te rechacé porque tu código está equivocado. Usar flockes la forma correcta, pero sus scripts están equivocados. El único comando que debe configurar en crontab es:flock -n /tmp/script.lock -c '/path/to/my/script.py'
Rutrus
6

Debe usar monit, una herramienta estándar de Unix que puede monitorear diferentes cosas en el sistema y reaccionar en consecuencia.

De los documentos: http://mmonit.com/monit/documentation/monit.html#pid_testing

compruebe el proceso checkqueue.py con pidfile /var/run/checkqueue.pid
       si se cambia pid entonces exec "checkqueue_restart.sh"

También puede configurar monit para que le envíe un correo electrónico cuando reinicie.

clofresh
fuente
2
Monit es una gran herramienta, pero es no estándar en el sentido formal de ser especificados en POSIX o SUSV.
Charles Duffy el
5
if ! test -f $PIDFILE || ! psgrep `cat $PIDFILE`; then
    restart_process
    # Write PIDFILE
    echo $! >$PIDFILE
fi
soulmerge
fuente
genial, eso está desarrollando algo de mi pseudocódigo bastante bien. dos qns: 1) ¿Cómo genero PIDFILE? 2) ¿Qué es psgrep? no está en el servidor ubuntu.
Tom
ps grep es solo una pequeña aplicación que hace lo mismo que ps ax|grep .... Puede instalarlo o escribir una función para eso: function psgrep () {ps ax | grep -v grep | grep -q "$ 1"}
soulmerge
Acabo de notar que no había respondido tu primera pregunta.
soulmerge
77
En un servidor realmente ocupado, es posible que el PID se recicle antes de verificar.
vartec
2

No estoy seguro de cuán portátil es en todos los sistemas operativos, pero puede verificar si su sistema contiene el comando 'run-one', es decir, "man run-one". Específicamente, este conjunto de comandos incluye 'run-one-constantemente', que parece ser exactamente lo que se necesita.

Desde la página del manual:

ejecutar-uno-constantemente COMANDO [ARGS]

Nota: obviamente, esto podría llamarse desde su script, pero también elimina la necesidad de tener un script.

Daniel Bradley
fuente
¿Ofrece esto alguna ventaja sobre la respuesta aceptada?
tripleee
1
Sí, creo que es preferible usar un comando incorporado que escribir un script de shell que haga lo mismo que tendrá que mantenerse como parte de la base de código del sistema. Incluso si se requiere la funcionalidad como parte de un script de shell, el comando anterior también podría usarse, por lo que es relevante para una pregunta de script de shell.
Daniel Bradley
Esto no está "incorporado"; si está instalado por defecto en alguna distribución, su respuesta probablemente debería especificar la distribución (e idealmente incluir un puntero para saber dónde descargarla si la suya no es una de ellas).
tripleee
Parece que es una utilidad de Ubuntu; pero es opcional incluso en Ubuntu. manpages.ubuntu.com/manpages/bionic/man1/run-one.1.html
tripleee
Vale la pena señalar: las utilidades run-one hacen exactamente lo que dice su nombre: solo puede ejecutar una instancia de cualquier comando que se ejecute con run-one-nnnnn. Otras respuestas aquí son más agnósticas ejecutables: no me importa el contenido del comando en absoluto.
David Kohen
1

He utilizado el siguiente script con gran éxito en numerosos servidores:

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

notas:

  • Está buscando un proceso de Java, por lo que puedo usar jps, esto es mucho más consistente en todas las distribuciones que ps
  • $INSTALLATION contiene suficiente de la ruta del proceso que es totalmente inequívoca
  • Usa el modo de espera mientras esperas que el proceso muera, evita acaparar recursos :)

Este script se usa para cerrar una instancia en ejecución de tomcat, que quiero cerrar (y esperar) en la línea de comandos, por lo que iniciarlo como un proceso secundario simplemente no es una opción para mí.

Kevin Wright
fuente
1
grep | awksigue siendo un antipatrón : desea awk "/$INSTALLATION/ { print \$1 }"combinar lo inútil grepen el script Awk, que puede encontrar líneas por expresión regular en sí muy bien, muchas gracias.
tripleee
0

Lo uso para mi proceso npm

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