Pourquoi mon processus d'arrière-plan Python se termine-t-il à la fin de la session SSH?

19

J'ai un script bash qui démarre un script python3 (appelons-le startup.sh), avec la ligne de clé:

nohup python3 -u <script> &

Lorsque j'entre sshdirectement et appelle ce script, le script python continue de s'exécuter en arrière-plan après ma sortie. Cependant, lorsque je lance ceci:

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

Le processus se termine dès qu'il ssha fini de l'exécuter et ferme la session.

Quelle est la différence entre les deux?

EDIT: Le script python exécute un service Web via Bottle.

EDIT2: J'ai également essayé de créer un script d'initialisation qui appelle startup.shet s'exécute ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "sudo service start <servicename>", mais obtient le même comportement.

EDIT3: C'est peut-être autre chose dans le script. Voici l'essentiel du script:

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: Lorsque je lance la dernière ligne avec un sommeil à la fin:

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"

Il n'atteint jamais echo "Finished", et je vois le message du serveur de bouteille, que je n'ai jamais vu auparavant:

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

Je vois "Terminé" si j'ouvre manuellement SSH et tue le processus moi-même.

EDIT5: En utilisant EDIT4, si je fais une demande à n'importe quel point de terminaison, je récupère une page, mais les erreurs de bouteille sortent:

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)
Neverendingqs
la source
Existe-t-il un moyen d'obtenir plus de description de ce que fait le script python? Vous auriez probablement encore des suppositions sans le code source complet, mais en savoir plus sur ce que fait le script python pourrait nous aider à faire des suppositions mieux éduquées.
Bratchley
Oui - ajouté à la question.
neverendingqs
Le script peut faire quelque chose au début qui dépend en quelque sorte du terminal attaché ou quelque chose comme ça et cela pourrait être un problème de timing: si la session dure après les premières secondes, cela fonctionne, sinon ce n'est pas le cas. Votre meilleure option pourrait être de l'exécuter sous stracesi vous utilisez Linux ou trusssi vous exécutez Solaris et voyez comment / pourquoi il se termine. Comme par exemple ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> strace -fo /tmp/debug ./startup.sh.
Celada
Avez-vous essayé d'utiliser le &à la fin du script de démarrage? L'ajout du &supprime la dépendance de votre session ssh d'être l'ID parent (lorsque les ID parents meurent, leurs enfants aussi). Je pense également que c'est une question en double basée sur ce post précédent. Le message que je vous ai soumis dans la phrase précédente est un double de ce message qui pourrait fournir de meilleurs détails.
Jacob Bryan
J'ai déjà essayé nohup ./startup.sh &, mais il avait le même comportement. startup.shcontient déjà un fork ( nohup python3 -u <script> &), donc je suis sûr que je n'ai pas besoin de fork encore.
neverendingqs

Réponses:

11

Je voudrais déconnecter la commande de ses flux d'entrée / sortie standard et d'erreur:

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

ssha besoin d'un indicateur qui n'a plus de sortie et qui ne nécessite plus d'entrée. Le fait d'avoir autre chose comme entrée et de rediriger les moyens de sortie sshpeut sortir en toute sécurité, car l'entrée / sortie ne vient pas ou ne va pas au terminal. Cela signifie que l'entrée doit provenir d'un autre endroit et que la sortie (STDOUT et STDERR) doit aller ailleurs.

La </dev/nullpièce spécifie /dev/nullcomme entrée pour <script>. Pourquoi cela est utile ici:

Rediriger / dev / null vers stdin donnera un EOF immédiat à tout appel de lecture de ce processus. Ceci est généralement utile pour détacher un processus d'un tty (un tel processus est appelé démon). Par exemple, lorsque vous démarrez un processus d'arrière-plan à distance via ssh, vous devez rediriger stdin pour empêcher le processus d'attendre une entrée locale. /programming/19955260/what-is-dev-null-in-bash/19955475#19955475

Alternativement, la redirection à partir d'une autre source d'entrée devrait être relativement sûre tant que la sshsession en cours n'a pas besoin d'être maintenue ouverte.

Avec la >/dev/nullpartie, le shell redirige la sortie standard vers / dev / null en la rejetant essentiellement. >/path/to/filefonctionnera également.

La dernière partie 2>&1redirige STDERR vers STDOUT.

Il existe trois sources standard d'entrée et de sortie pour un programme. L'entrée standard provient généralement du clavier s'il s'agit d'un programme interactif, ou d'un autre programme s'il traite la sortie de l'autre programme. Le programme imprime généralement sur une sortie standard et parfois sur une erreur standard. Ces trois descripteurs de fichiers (vous pouvez les considérer comme des «canaux de données») sont souvent appelés STDIN, STDOUT et STDERR.

Parfois, ils ne sont pas nommés, ils sont numérotés! Les numérotations intégrées pour eux sont 0, 1 et 2, dans cet ordre. Par défaut, si vous ne nommez pas ou ne numéro un explicitement, vous parlez de STDOUT.

Dans ce contexte, vous pouvez voir que la commande ci-dessus redirige la sortie standard vers / dev / null, qui est un endroit où vous pouvez vider tout ce que vous ne voulez pas (souvent appelé le bit-bucket), puis redirige l'erreur standard vers la sortie standard ( vous devez mettre un & devant la destination lorsque vous faites cela).

La courte explication est donc: «toutes les sorties de cette commande doivent être placées dans un trou noir». C'est une bonne façon de rendre un programme vraiment silencieux!
Que signifie> / dev / null 2> & 1? | Xaprb

jlliagre
la source
nohup python3 -u <script> >/dev/null 2>&1 &et nohup python3 -u <script> > nohup.out 2>&1 &travaillé. Je pensais que nohup redirige automatiquement toutes les sorties - quelle est la différence?
neverendingqs
@neverendingqs, quelle version nohupavez-vous sur votre hôte distant? Un POSIX nohupn'est pas requis pour rediriger stdin, ce que j'ai manqué, mais il doit toujours rediriger stdoutet stderr.
Graeme
On dirait que je travaille avec nohup (GNU coreutils) 8.21.
neverendingqs
@neverendingqs, nohupimprime-t-il des messages comme nohup: ignoring input and appending output to ‘nohup.out’?
Graeme
Oui - c'est le message exact.
neverendingqs
3

Regardez 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]

Lorsque vous exécutez, ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh"vous exécutez le script shell startup.sh en tant que commande ssh.

D'après la description:

Si la commande est spécifiée, elle est exécutée sur l'hôte distant au lieu d'un shell de connexion.

Sur cette base, il devrait exécuter le script à distance.

La différence entre cela et l'exécution nohup python3 -u <script> &dans votre terminal local est que cela s'exécute en tant que processus d'arrière-plan local tandis que la commande ssh tente de l'exécuter en tant que processus d'arrière-plan distant.

Si vous avez l'intention d'exécuter le script localement, n'exécutez pas startup.sh dans le cadre de la commande ssh. Vous pourriez essayer quelque chose commessh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> && "./startup.sh"

Si votre intention est d'exécuter le script à distance et que vous souhaitez que ce processus se poursuive après la fin de votre session ssh, vous devez d'abord démarrer une screensession sur l'hôte distant. Ensuite, vous devez exécuter le script python dans l'écran et il continuera à fonctionner après la fin de votre session ssh.

Voir le manuel d'utilisation de l'écran

Bien que je pense que l'écran est votre meilleure option, si vous devez utiliser nohup, envisagez de définir shopt -s huponexitsur l'hôte distant avant d'exécuter la commande nohup. Alternativement, vous pouvez utiliser disown -h [jobID]pour marquer le processus afin que SIGHUP ne lui soit pas envoyé. 1

Comment continuer à exécuter le travail après avoir quitté une invite du shell en arrière-plan?

Le signal SIGHUP (Hangup) est utilisé par votre système lors du contrôle du terminal ou de la fin du processus de contrôle. Vous pouvez également utiliser SIGHUP pour recharger les fichiers de configuration et ouvrir / fermer les fichiers journaux. En d'autres termes, si vous vous déconnectez de votre terminal, tous les travaux en cours seront interrompus. Pour éviter cela, vous pouvez passer l'option -h à la commande disown. Cette option marque chaque jobID afin que SIGHUP ne soit pas envoyé au travail si le shell reçoit un SIGHUP.

Consultez également ce résumé du huponexitfonctionnement d'un shell lorsque vous le quittez, le tuez ou le lâchez. Je suppose que votre problème actuel est lié à la fin de la session shell. 2

  1. Tous les processus enfants, en arrière-plan ou non d'un shell ouvert sur une connexion ssh sont supprimés avec SIGHUP lorsque la connexion ssh est fermée uniquement si l'option huponexit est définie: exécutez shopt huponexit pour voir si cela est vrai.

  2. Si huponexit est vrai, vous pouvez utiliser nohup ou disown pour dissocier le processus du shell afin qu'il ne soit pas tué lorsque vous quittez. Ou, exécutez les choses avec l'écran.

  3. Si huponexit est faux, ce qui est la valeur par défaut sur au moins certains linux ces jours-ci, les travaux en arrière-plan ne seront pas supprimés lors de la déconnexion normale.

  4. Mais même si huponexit est faux, alors si la connexion ssh est interrompue ou tombe (différente de la déconnexion normale), les processus en arrière-plan seront toujours supprimés. Cela peut être évité en renonçant ou en rien comme dans (2).

Enfin, voici quelques exemples d'utilisation de shopt huponexit. 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
la source
Selon la bashpage de manuel, cela huponexitne devrait affecter que les shells interactifs et non les scripts - 'Si l'option de shell huponexit a été définie avec shopt, bash envoie un SIGHUP à toutes les tâches lorsqu'un shell de connexion interactif se termine.'
Graeme
2

Peut-être vaut-il la peine d'essayer une -noption lors du démarrage d'un ssh? Cela empêchera la dépendance des processus distants à un local stdin, qui se ferme bien sûr dès la ssh sessionfin. Et cela entraînera la résiliation à distance des prix chaque fois qu'il tentera d'accéder à son stdin.

Georgiy
la source
Je l'ai essayé sans succès = [.
neverendingqs
2

Je soupçonne que vous avez une condition de course. Cela ressemblerait à quelque chose comme ceci:

  • La connexion SSH démarre
  • SSH démarre startup.sh
  • startup.sh démarre un processus en arrière-plan (nohup)
  • startup.sh se termine
  • ssh se termine, et cela tue les processus enfants (ie nohup)

Si ssh n'avait pas raccourci les choses, les événements suivants se seraient produits (je ne suis pas sûr de l'ordre de ces deux):

  • nohup démarre votre script python
  • nohup se déconnecte du processus parent et du terminal.

Ainsi, les deux dernières étapes critiques ne se produisent pas, car startup.sh et ssh se terminent avant que nohup n'ait le temps de faire son travail.

J'espère que votre problème disparaîtra si vous mettez quelques secondes de sommeil à la fin de startup.sh. Je ne sais pas exactement combien de temps vous avez besoin. S'il est important de le garder au minimum, alors vous pouvez peut-être regarder quelque chose dans proc pour voir quand il est sûr.

mc0e
la source
Bon point, ne pense pas que la fenêtre sera très longue - probablement seulement quelques millisecondes. Vous pouvez vérifier si vous n'utilisez /proc/$!/commpas nohupou plus facilement la sortie de ps -o comm= $!.
Graeme
Cela devrait fonctionner pour une déconnexion normale, mais qu'en est-il lorsque la session est interrompue ou supprimée? N'auriez-vous pas encore besoin de renier le travail afin qu'il soit entièrement ignoré par le soupir?
iyrin
@RyanLoremIpsum: Le script de démarrage doit seulement attendre suffisamment longtemps pour que le processus enfant soit complètement détaché. Après cela, peu importe ce qui arrive à la session ssh. Si quelque chose d'autre tue votre session ssh dans la brève fenêtre pendant que cela se produit, vous ne pouvez pas faire grand-chose.
mc0e
@Graeme ouais, je suppose que c'est très rapide, mais je n'en sais pas assez sur ce que fait rien pour être sûr. Un pointeur vers une source faisant autorité (ou au moins bien informée et détaillée) à ce sujet serait utile.
mc0e
Que diriez-vous de celui-ci - lingrok.org/xref/coreutils/src/nohup.c
Graeme
1

Cela ressemble plus à un problème avec ce que le pythonscript ou pythonlui-même fait. Tout ce nohupqui fait vraiment (barre simplifiant les redirections) est simplement de définir le gestionnaire du HUPsignal sur SIG_IGN(ignorer) avant d'exécuter le programme. Il n'y a rien pour empêcher le programme de le réinitialiser SIG_DFLou d'installer son propre gestionnaire une fois qu'il a démarré.

Une chose que vous voudrez peut-être essayer est de placer votre commande entre parenthèses afin que vous obteniez un effet de double fork et que votre pythonscript ne soit plus un enfant du processus shell. Par exemple:

( nohup python3 -u <script> & )

Une autre chose qui peut également valoir la peine d'être essayée (si vous utilisez bashet non un autre shell) est d'utiliser la fonction disownintégrée à la place de nohup. Si tout fonctionne comme indiqué, cela ne devrait pas faire de différence, mais dans un shell interactif, cela empêcherait le HUPsignal de se propager vers votre pythonscript. Vous pouvez ajouter le désaveu sur la ligne suivante ou le même que ci-dessous (notez que l'ajout d'un ;après un &est une erreur bash):

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

Si ce qui précède ou une combinaison de ceux-ci ne fonctionne pas, le seul endroit pour résoudre le problème est sûrement dans le pythonscript lui-même.

Graeme
la source
L'effet double fork serait-il suffisant (basé sur la réponse de @ RyanLoremIpsum)?
neverendingqs
Les deux n'ont pas résolu le problème = [. S'il s'agit d'un problème Python, avez-vous une idée sur où commencer à enquêter (vous ne pouvez pas publier trop de script Python ici)?
neverendingqs
@neverendingqs, si vous voulez dire les huponexitchoses, l'exécution dans un sous-shell devrait avoir le même effet disownque le processus ne sera pas ajouté à la liste des travaux.
Graeme
@neverendingqs, a mis à jour ma réponse. Vous avez oublié d'utiliser des redirections avec disown. Ne vous attendez pas à ce que cela fasse beaucoup de différence. Je pense que votre meilleur pari est de modifier le pythonscript afin qu'il vous explique pourquoi il se termine.
Graeme
La redirection de la sortie a fonctionné ( unix.stackexchange.com/a/176610/52894 ), mais je ne sais pas quelle est la différence entre le faire explicitement et le nohupfaire.
neverendingqs
0

Je pense que c'est parce que le travail est lié à la session. Une fois cela terminé, tous les travaux utilisateur sont également terminés.

user208145
la source
2
Mais pourquoi est-ce différent que d'obtenir un terminal, de taper et d'exécuter la commande et de quitter? Les deux sessions sont fermées une fois que je l'ai fermé.
neverendingqs
D'accord, je voudrais comprendre pourquoi ce n'est pas différent de fermer votre propre terminal manuellement.
Avindra Goolcharan,
0

Si nohuppeut ouvrir son fichier de sortie, vous pouvez avoir un indice nohup.out. Il est possible pythonque vous ne soyez pas sur le chemin lorsque vous exécutez le script via ssh.

J'essaierais de créer un fichier journal pour la commande. Essayez d'utiliser:

nohup /usr/bin/python3 -u <script> &>logfile &
BillThor
la source
J'utilise sshpour exécuter le script manuellement, donc je suppose que python3 est dans le chemin.
neverendingqs
@neverendingqs Le fichier journal contient-il quelque chose?
BillThor
Rien d'extraordinaire - le démarrage semble normal.
neverendingqs