¿Por qué finaliza mi proceso en segundo plano de Python cuando finaliza la sesión SSH?

19

Tengo un script bash que inicia un script python3 (llamémoslo startup.sh), con la línea clave:

nohup python3 -u <script> &

Cuando entro sshdirectamente y llamo a este script, el script de Python continúa ejecutándose en segundo plano después de salir. Sin embargo, cuando ejecuto esto:

ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh"

El proceso termina tan pronto como sshhaya terminado de ejecutarse y cierra la sesión.

¿Cuál es la diferencia entre los dos?

EDIT: El script en Python, se está ejecutando un servicio web a través de la botella.

EDIT2: también intenté crear un script de inicio que startup.shejecuta y ejecuta ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "sudo service start <servicename>", pero obtuve el mismo comportamiento.

EDITAR3: Tal vez sea algo más en el guión. Aquí está la mayor parte del guión:

chmod 700 ${key_loc}

echo "INFO: Syncing files."
rsync -azP -e "ssh -i ${key_loc} -o StrictHostKeyChecking=no" ${source_client_loc} ${remote_user}@${remote_hostname}:${destination_client_loc}

echo "INFO: Running startup script."
ssh -i ${key_loc} -o StrictHostKeyChecking=no ${remote_user}@${remote_hostname} "cd ${destination_client_loc}; chmod u+x ${ctl_script}; ./${ctl_script} restart"

EDIT4: Cuando ejecuto la última línea con un sueño al final:

ssh -i ${key_loc} -o StrictHostKeyChecking=no ${remote_user}@${remote_hostname} "cd ${destination_client_loc}; chmod u+x ${ctl_script}; ./${ctl_script} restart; sleep 1"

echo "Finished"

Nunca llega echo "Finished", y veo el mensaje del servidor Botella, que nunca vi antes:

Bottle vx.x.x server starting up (using WSGIRefServer())...
Listening on <URL>
Hit Ctrl-C to quit.

Veo "Terminado" si manualmente SSH y matar el proceso mismo.

EDIT5: Utilizando EDIT4, si hago una solicitud a cualquier punto final, obtengo una página de regreso, pero los errores de la botella se eliminan:

Bottle vx.x.x server starting up (using WSGIRefServer())...
Listening on <URL>
Hit Ctrl-C to quit.


----------------------------------------
Exception happened during processing of request from ('<IP>', 55104)
interminables
fuente
¿Hay alguna manera de que podamos obtener una descripción más detallada de lo que hace el script de Python? Probablemente todavía obtendrá conjeturas sin el código fuente completo, pero saber más sobre lo que hace el script python podría ayudarnos a hacer conjeturas mejor informadas.
Bratchley
Sí, agregado a la pregunta.
neverendingqs
El script podría estar haciendo algo desde el principio que de alguna manera depende del terminal conectado o algo así y podría ser un problema de tiempo: si la sesión dura más de los primeros segundos, funciona, de lo contrario no lo hace. Su mejor opción podría ser ejecutarlo stracesi está utilizando Linux o trusssi está ejecutando Solaris y ver cómo / por qué termina. Como por ejemplo ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> strace -fo /tmp/debug ./startup.sh.
Celada
¿Intentaste usar el &al final del script de inicio? Agregar el le &quita la dependencia de su sesión ssh de ser la identificación principal (cuando los identificadores principales mueren, también lo hacen sus hijos). También creo que esta es una pregunta duplicada basada en esta publicación anterior. La publicación que le envié en la oración anterior es un duplicado de esta publicación que podría proporcionar mejores detalles.
Jacob Bryan
Lo he intentado nohup ./startup.sh &antes, pero tuvo el mismo comportamiento. startup.shya contiene un tenedor ( nohup python3 -u <script> &), así que estoy bastante seguro de que no necesito bifurcar nuevamente.
neverendingqs

Respuestas:

11

Desconectaría el comando de su entrada / salida estándar y flujos de error:

nohup python3 -u <script> </dev/null >/dev/null 2>&1 &  

sshnecesita un indicador que no tenga más salida y que no requiera más entrada. Tener otra cosa que sea la entrada y redirigir los medios de salida sshpuede salir de forma segura, ya que la entrada / salida no viene ni va al terminal. Esto significa que la entrada debe provenir de otro lugar y la salida (tanto STDOUT como STDERR) debe ir a otro lugar.

La </dev/nullparte se especifica /dev/nullcomo entrada para <script>. Por qué eso es útil aquí:

Redirigir / dev / null a stdin dará un EOF inmediato a cualquier llamada de lectura de ese proceso. Esto suele ser útil para separar un proceso de un tty (dicho proceso se llama demonio). Por ejemplo, al iniciar un proceso en segundo plano de forma remota a través de ssh, debe redirigir stdin para evitar que el proceso espere la entrada local. /programming/19955260/what-is-dev-null-in-bash/19955475#19955475

Alternativamente, la redirección desde otra fuente de entrada debería ser relativamente segura siempre que sshno sea necesario mantener abierta la sesión actual .

Con el >/dev/null parte, el shell redirige la salida estándar a / dev / null esencialmente descartándola. >/path/to/fileTambién funcionará.

La ultima parte 2>&1 es redirigir STDERR a STDOUT.

Hay tres fuentes estándar de entrada y salida para un programa. La entrada estándar generalmente proviene del teclado si es un programa interactivo, o de otro programa si está procesando la salida del otro programa. El programa generalmente imprime en salida estándar y, a veces, imprime en error estándar. Estos tres descriptores de archivos (se puede pensar en ellos como “los canales de datos”) a menudo son llamados stdin, stdout y stderr.

¡A veces no se nombran, se numeran! Las numeraciones integradas para ellos son 0, 1 y 2, en ese orden. Por defecto, si no nombra o numera uno explícitamente, está hablando de STDOUT.

Dado ese contexto, puede ver que el comando anterior está redirigiendo la salida estándar a / dev / null, que es un lugar donde puede volcar todo lo que no desea (a menudo llamado bit-bucket), luego redirige el error estándar a la salida estándar ( tienes que poner un & delante del destino cuando hagas esto).

La explicación breve, por lo tanto, es que "todos los resultados de este comando deben introducirse en un agujero negro". ¡Esa es una buena manera de hacer que un programa sea realmente silencioso!
¿Qué significa> / dev / null 2> & 1? El | Xaprb

jlliagre
fuente
nohup python3 -u <script> >/dev/null 2>&1 &y nohup python3 -u <script> > nohup.out 2>&1 &trabajado Sin embargo, pensé que nohup redirige automáticamente toda la salida: ¿cuál es la diferencia?
neverendingqs
@neverendingqs, ¿qué versión de nohuptiene en su host remoto? nohupNo se requiere un POSIX para redirigir stdin, lo cual me perdí, pero aún así debería redirigir stdouty stderr.
Graeme
Parece que estoy trabajando nohup (GNU coreutils) 8.21.
neverendingqs
@neverendingqs, no nohupimprimir los mensajes, como nohup: ignoring input and appending output to ‘nohup.out’?
Graeme
Sí, ese es el mensaje exacto.
neverendingqs
3

Mira man ssh:

 ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
     [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file] [-L [bind_address:]port:host:hostport]
     [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]
     [-R [bind_address:]port:host:hostport] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]
     [user@]hostname [command]

Cuando corres ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh" , está ejecutando el script de shell startup.sh como un comando ssh.

De la descripción:

Si se especifica comando, se ejecuta en el host remoto en lugar de una cáscara de inicio de sesión.

En base a esto, debería ejecutar el script de forma remota.

La diferencia entre eso y correr nohup python3 -u <script> & en su terminal local es que esto se ejecuta como un proceso de fondo local mientras el comando ssh intenta ejecutarlo como un proceso de fondo remoto.

Si tiene la intención de ejecutar el script localmente, no ejecute startup.sh como parte del comando ssh. Puedes intentar algo comossh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> && "./startup.sh"

Si su intención es ejecutar el script de forma remota y desea que este proceso continúe después de que finalice su sesión ssh, primero deberá iniciar una screensesión en el host remoto. Luego, debe ejecutar el script de Python dentro de la pantalla y continuará ejecutándose después de que finalice su sesión ssh.

Consulte el Manual del usuario de la pantalla

Si bien creo que la pantalla es su mejor opción, si debe usar nohup, considere configurar shopt -s huponexiten el host remoto antes de ejecutar el comando nohup. Alternativamente, puede usar disown -h [jobID]para marcar el proceso para que SIGHUP no se le envíe. 1

¿Cómo puedo mantener un trabajo en ejecución después de la salida I a partir de un intérprete de comandos en segundo plano?

La señal SIGHUP (Hangup) es utilizada por su sistema en el terminal de control o la muerte del proceso de control. Puede usar SIGHUP para volver a cargar archivos de configuración y abrir / cerrar archivos de registro también. En otras palabras, si usted cierra sesión en sus terminales puestos de trabajo que se ejecutan todos se dará por terminado. Para evitar esto, puede pasar la opción -h al comando renegar. Esta opción marca cada ID de trabajo para que SIGHUP no se envíe al trabajo si el shell recibe un SIGHUP.

Además, vea este resumen de cómo huponexitfunciona cuando se sale de una concha, mató o se ha caído. Supongo que su problema actual está relacionado con cómo termina la sesión de shell. 2

  1. Todos los procesos secundarios, backgrounded o no de una concha abierta sobre una conexión SSH se matan con SIGHUP cuando la conexión ssh se cierra sólo si la opción se establece huponexit: plazo shopt huponexit para ver si esto es cierto.

  2. Si huponexit es verdadero, entonces puede usar nohup o renegar para disociar el proceso del shell para que no se mate cuando salga. O bien, ejecute las cosas con la pantalla.

  3. Si huponexit es falso, que es el valor predeterminado en al menos algunos Linux en estos días, los trabajos en segundo plano no se eliminarán en el cierre de sesión normal.

  4. Pero incluso si huponexit es falsa, entonces, si la conexión ssh muere, o gotas (diferentes de cierre de sesión normal), entonces los procesos backgrounded seguirá recibiendo matado. Esto puede evitarse por disown o nohup como en (2).

Por último, he aquí algunos ejemplos de cómo utilizar huponexit shopt. 3

$ shopt -s huponexit; shopt | grep huponexit
huponexit       on
# Background jobs will be terminated with SIGHUP when shell exits

$ shopt -u huponexit; shopt | grep huponexit
huponexit       off
# Background jobs will NOT be terminated with SIGHUP when shell exits
iyrin
fuente
Según la bashpágina del manual, huponexitsolo debería afectar a los shells interactivos y no a los scripts: "Si la opción de shell huponexit se ha configurado con shopt, bash envía un SIGHUP a todos los trabajos cuando sale un shell de inicio de sesión interactivo".
Graeme
2

Tal vez vale la pena probar la -nopción al comenzar un ssh? Se evitará la dependencia proceso remoto a nivel local stdin, que por supuesto se cierra en cuanto ssh sessionextremos. Y esto hará que los precios de terminación remotos cada vez que intenta acceder a su stdin.

Georgiy
fuente
Probé sin éxito = [.
neverendingqs
2

Sospecho que tienes una condición de carrera. Sería algo parecido a esto:

  • aperturas de conexión SSH
  • SSH inicia startup.sh
  • startup.sh inicia un proceso en segundo plano (nohup)
  • acabados startup.sh
  • ssh termina y esto mata los procesos secundarios (es decir, nohup)

Si ssh no hubiera acortado las cosas, habría sucedido lo siguiente (no estoy seguro sobre el orden de estos dos):

  • nohup inicia tu script de python
  • nohup se desconecta del proceso primario y del terminal.

Así que los dos últimos pasos críticos no suceden, porque startup.sh y ssh meta antes nohup tiene tiempo para hacer su cosa.

Espero que su problema desaparezca si coloca unos segundos de suspensión al final de startup.sh. No estoy seguro de cuánto tiempo necesitas exactamente. Si es importante mantenerlo al mínimo, entonces tal vez pueda mirar algo en proceso para ver cuándo es seguro.

mc0e
fuente
Buen punto, no creo que la ventana para esto sea muy larga, probablemente solo unos pocos milisegundos. Podrías comprobar si la salida de o /proc/$!/commno es nohupmás portátil ps -o comm= $!.
Graeme
Eso debería funcionar para el cierre de sesión normal, pero ¿qué pasa cuando la sesión se cae o se cierra? ¿Todavía no necesitarías rechazar el trabajo para que el suspiro lo ignore por completo?
iyrin
@RyanLoremIpsum: la secuencia de comandos de inicio solo necesita esperar el tiempo suficiente para que el proceso secundario esté completamente desconectado. Después de eso, no importa lo que pase con la sesión ssh. Si algo más mata su sesión ssh en la breve ventana mientras eso sucede, no hay mucho que pueda hacer al respecto.
mc0e
@Graeme, sí, supongo que es muy rápido, pero no sé lo suficiente sobre qué es lo que no hace para estar seguro. Sería útil un puntero a una fuente autorizada (o al menos bien informada y detallada) sobre esto.
mc0e
¿Qué tal este? - lingrok.org/xref/coreutils/src/nohup.c
Graeme
1

Esto suena más como un problema con lo que está haciendo el pythonscript o en pythonsí mismo. Todo lo que nohuprealmente hace (barras que simplifican los redireccionamientos) es simplemente configurar el controlador para que la HUPseñal SIG_IGN(ignorar) antes de ejecutar el programa. No hay nada que impida que el programa lo vuelva a configurar SIG_DFLo que instale su propio controlador una vez que comience a ejecutarse.

Una cosa que quizás desee probar es encerrar su comando entre paréntesis para que obtenga un efecto de doble tenedor y su pythonscript ya no sea un elemento secundario del proceso de shell. P.ej:

( nohup python3 -u <script> & )

Otra cosa que también puede valer la pena intentar (si está usando bashy no otro shell) es usar el disownincorporado en lugar de nohup. Si todo funciona según lo documentado, esto no debería hacer ninguna diferencia, pero en un shell interactivo esto evitaría que la HUPseñal se propague a su pythonscript. Puede agregar el disown en la siguiente línea o la misma que se muestra a continuación (tenga en cuenta que agregar un ;after a &es un error bash):

python3 -u <script> </dev/null &>/dev/null & disown

Si lo anterior o alguna combinación no funciona, entonces seguramente el único lugar para abordar el problema es en el pythonscript mismo.

Graeme
fuente
¿Sería suficiente el efecto de doble tenedor (basado en la respuesta de @ RyanLoremIpsum)?
neverendingqs
Ambos no resolvieron el problema = [. Si se trata de un problema de Python, ¿tiene una idea sobre dónde comenzar a investigar (no puede publicar demasiado del script de Python aquí)?
interminables
@neverendingqs, si te refieres a las huponexitcosas, ejecutarse en una subshell debería tener el mismo efecto ya disownque el proceso no se agregará a la lista de trabajos.
Graeme
@neverendingqs, actualicé mi respuesta. Olvidé que debería usar redirecciones con disown. Sin embargo, no esperes que haga mucha diferencia. Creo que lo mejor es alterar el pythonguión para que te diga por qué está saliendo.
Graeme
La redirección de la salida funcionó ( unix.stackexchange.com/a/176610/52894 ), pero no estoy seguro de cuál es la diferencia entre hacerlo explícitamente y nohuphacerlo.
neverendingqs
0

Creo que es porque el trabajo está vinculado a la sesión. Una vez que finaliza, también se finalizan los trabajos de los usuarios.

user208145
fuente
2
Pero, ¿por qué es diferente de obtener un terminal, escribir y ejecutar el comando y salir? Ambas sesiones se cierran una vez que lo cierro.
neverendingqs
De acuerdo, me gustaría entender por qué esto no es diferente de cerrar su propia terminal manualmente.
Avindra Goolcharan
0

Si nohuppuede abrir su archivo de salida, puede tener una pista nohup.out. Es posible pythonque no esté en el camino cuando ejecuta el script vía ssh.

Intentaría crear un archivo de registro para el comando. Intenta usar:

nohup /usr/bin/python3 -u <script> &>logfile &
BillThor
fuente
Yo uso sshpara ejecutar el script manualmente, por lo que estoy asumiendo python3 está en el camino.
neverendingqs
@neverendingqs ¿El archivo de registro contiene nada?
BillThor
Nada fuera de lo común: la puesta en marcha parece normal.
neverendingqs