Perché il mio processo in background di Python termina al termine della sessione SSH?


19

Ho uno script bash che avvia uno script python3 (chiamiamolo startup.sh), con la linea chiave:

nohup python3 -u <script> &

Quando sshentro direttamente e chiamo questo script, lo script Python continua a essere eseguito in background dopo che esco. Tuttavia, quando eseguo questo:

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

Il processo termina non appena sshtermina l'esecuzione e chiude la sessione.

Qual è la differenza tra i due?

EDIT: lo script python esegue un servizio web tramite Bottle.

EDIT2: ho anche provato a creare uno script init che chiama startup.shed è in esecuzione ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "sudo service start <servicename>", ma ha avuto lo stesso comportamento.

EDIT3: Forse è qualcos'altro nella sceneggiatura. Ecco la maggior parte dello 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: Quando eseguo l'ultima riga con un sonno alla fine:

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"

Non arriva mai echo "Finished"e vedo il messaggio del Bottle Server, che non avevo mai visto prima:

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

Vedo "Fine" se inserisco manualmente SSH e interrompo il processo da solo.

EDIT5: Utilizzando EDIT4, se invio una richiesta a qualsiasi endpoint, ottengo una pagina indietro, ma gli errori Bottle:

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)

Esiste un modo per ottenere più di una descrizione di ciò che fa lo script Python? Probabilmente avresti ancora ipotesi senza il codice sorgente completo, ma sapere di più su ciò che fa lo script Python potrebbe aiutarci a fare ipotesi più istruite.
Bratchley,

Sì - aggiunto alla domanda.
infinite dal

Lo script potrebbe fare qualcosa all'inizio che in qualche modo dipende dal terminale allegato o qualcosa del genere e potrebbe essere un problema di temporizzazione: se la sessione dura oltre i primi secondi funziona, altrimenti non funziona. La tua migliore opzione potrebbe essere quella di eseguirlo stracese stai usando Linux o trussse stai eseguendo Solaris e vedi come / perché termina. Come per esempio ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> strace -fo /tmp/debug ./startup.sh.
Celada,

Hai provato a utilizzare il &alla fine dello script di avvio? L'aggiunta di &toglie la dipendenza della sessione ssh dall'essere l'id genitore (quando gli id ​​genitore muoiono, così fanno i loro figli). Inoltre penso che questa sia una domanda duplicata basata su questo post precedente. Il post che ti ho inviato nella frase precedente è un duplicato di questo post che potrebbe fornire dettagli migliori.
Jacob Bryan,

Ho provato nohup ./startup.sh &prima, ma aveva lo stesso comportamento. startup.shcontiene già un fork ( nohup python3 -u <script> &), quindi sono abbastanza sicuro di non aver bisogno di fork di nuovo.
infinite dal

Risposte:


11

Disconnetterei il comando dal suo input / output standard e dai flussi di errore:

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

sshnecessita di un indicatore che non abbia più output e che non richieda ulteriori input. Avere qualcos'altro come input e reindirizzare i mezzi di output sshpuò uscire in modo sicuro, poiché input / output non provengono o arrivano al terminale. Ciò significa che l'input deve provenire da qualche altra parte e l'output (sia STDOUT che STDERR) dovrebbe andare altrove.

La </dev/nullparte specifica /dev/nullcome input per <script>. Perché questo è utile qui:

Il reindirizzamento / dev / null su stdin darà un EOF immediato a qualsiasi chiamata in lettura da quel processo. Ciò è in genere utile per staccare un processo da un tty (tale processo è chiamato demone). Ad esempio, quando si avvia un processo in background in remoto tramite ssh, è necessario reindirizzare lo stdin per impedire al processo in attesa di input locale. /programming/19955260/what-is-dev-null-in-bash/19955475#19955475

In alternativa, il reindirizzamento da un'altra fonte di input dovrebbe essere relativamente sicuro fino a quando la sshsessione corrente non deve essere mantenuta aperta.

Con la >/dev/nullparte la shell reindirizza l'output standard in / dev / null essenzialmente scartandolo. >/path/to/filefunzionerà anche.

L'ultima parte 2>&1reindirizza STDERR a STDOUT.

Esistono tre fonti standard di input e output per un programma. L'input standard di solito viene dalla tastiera se è un programma interattivo o da un altro programma se sta elaborando l'output dell'altro programma. Il programma di solito stampa sull'output standard e talvolta stampa sull'errore standard. Questi tre descrittori di file (puoi considerarli come "pipe di dati") sono spesso chiamati STDIN, STDOUT e STDERR.

A volte non hanno un nome, sono numerati! Le numerazioni integrate per loro sono 0, 1 e 2, in quell'ordine. Per impostazione predefinita, se non si nomina o si numera esplicitamente uno, si parla di STDOUT.

Dato quel contesto, puoi vedere che il comando sopra sta reindirizzando l'output standard in / dev / null, che è un posto dove puoi scaricare tutto ciò che non vuoi (spesso chiamato bit-bucket), quindi reindirizzare l'errore standard nell'output standard ( devi mettere un & davanti alla destinazione quando lo fai).

La breve spiegazione, quindi, è "tutto l'output di questo comando dovrebbe essere inserito in un buco nero". Questo è un buon modo per rendere un programma molto silenzioso!
Cosa significa> / dev / null 2> & 1? | Xaprb


nohup python3 -u <script> >/dev/null 2>&1 &e ha nohup python3 -u <script> > nohup.out 2>&1 &funzionato. Ho pensato che nohup reindirizza automaticamente tutti gli output, ma qual è la differenza?
neverendingqs

@neverendingqs, quale versione nohuphai sul tuo host remoto? Un POSIX nohupnon è necessario per reindirizzare stdin, che ho perso, ma dovrebbe comunque reindirizzare stdoute stderr.
Graeme,

Sembra che sto lavorando con nohup (GNU coreutils) 8.21.
neverendingqs

@neverendingqs, nohupstampa qualche messaggio, come nohup: ignoring input and appending output to ‘nohup.out’?
Graeme,

Sì, questo è il messaggio esatto.
neverendingqs

3

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

Quando esegui, esegui ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh"lo script shell startup.sh come comando ssh.

Dalla descrizione:

Se viene specificato un comando, viene eseguito sull'host remoto anziché su una shell di accesso.

Sulla base di questo, dovrebbe eseguire lo script in remoto.

La differenza tra quello e l'esecuzione nohup python3 -u <script> &nel terminale locale è che questo viene eseguito come processo in background locale mentre il comando ssh tenta di eseguirlo come processo in background remoto.

Se si intende eseguire lo script localmente, non eseguire startup.sh come parte del comando ssh. Potresti provare qualcosa del generessh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> && "./startup.sh"

Se la tua intenzione è quella di eseguire lo script in remoto e vuoi che questo processo continui dopo che la tua sessione ssh è terminata, dovresti prima iniziare una screensessione sull'host remoto. Quindi devi eseguire lo script Python all'interno dello schermo e continuerà a funzionare dopo aver terminato la tua sessione SSH.

Vedere il Manuale dell'utente dello schermo

Mentre penso che lo schermo sia l'opzione migliore, se devi usare nohup, considera l'impostazione shopt -s huponexitsull'host remoto prima di eseguire il comando nohup. In alternativa, è possibile utilizzare disown -h [jobID]per contrassegnare il processo in modo che SIGHUP non gli venga inviato. 1

Come posso continuare a eseguire il lavoro dopo essere uscito da un prompt della shell in background?

Il segnale SIGHUP (Hangup) viene utilizzato dal sistema sul controllo terminale o sulla morte del processo di controllo. È possibile utilizzare SIGHUP per ricaricare i file di configurazione e aprire / chiudere anche i file di registro. In altre parole, se ci si disconnette dal proprio terminale, tutti i lavori in esecuzione verranno chiusi. Per evitare ciò, è possibile passare l'opzione -h al comando disown. Questa opzione contrassegna ciascun ID lavoro in modo che SIGHUP non venga inviato al lavoro se la shell riceve un SIGHUP.

Inoltre, consulta questo riepilogo di come huponexitfunziona quando una shell viene chiusa, uccisa o rilasciata. Immagino che il tuo problema attuale sia legato al modo in cui termina la sessione di shell. 2

  1. Tutti i processi figlio, in background o meno di una shell aperta su una connessione ssh, vengono interrotti con SIGHUP quando la connessione ssh viene chiusa solo se è impostata l'opzione huponexit: eseguire shopt huponexit per vedere se questo è vero.

  2. Se huponexit è vero, allora puoi usare nohup o disown per dissociare il processo dalla shell in modo che non venga ucciso quando esci. Oppure, esegui le cose con lo schermo.

  3. Se huponexit è falso, che è l'impostazione predefinita su almeno alcuni Linux in questi giorni, i lavori in background non verranno interrotti al logout normale.

  4. Ma anche se huponexit è falso, allora se la connessione ssh viene uccisa, o cade (diverso dal normale logout), i processi in background verranno comunque uccisi. Questo può essere evitato rinnegando o nohup come in (2).

Infine, ecco alcuni esempi su come utilizzare 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

Secondo la bashpagina man, huponexitdovrebbe interessare solo le shell interattive e non gli script - "Se l'opzione della shell huponexit è stata impostata con shopt, bash invia un SIGHUP a tutti i lavori quando esce una shell di accesso interattiva".
Graeme,

2

Forse vale la pena provare -nun'opzione quando si avvia un ssh? Impedirà la dipendenza del processo remoto da un locale stdin, che ovviamente si chiude non appena ssh sessiontermina. E questo causerà la chiusura dei prezzi a distanza ogni volta che tenta di accedervi stdin.


Ho provato senza successo = [.
neverendingqs

2

Ho il sospetto che tu abbia una condizione di razza. Andrebbe qualcosa del genere:

  • Viene avviata la connessione SSH
  • SSH avvia startup.sh
  • startup.sh avvia un processo in background (nohup)
  • startup.sh termina
  • ssh termina e questo uccide i processi figlio (cioè nohup)

Se ssh non avesse abbreviato le cose, sarebbe successo quanto segue (non sono sicuro dell'ordine di questi due):

  • nohup avvia il tuo script Python
  • nohup si disconnette dal processo principale e dal terminale.

Quindi gli ultimi due passaggi critici non accadono, perché startup.sh e ssh finiscono prima che nohup abbia il tempo di fare le sue cose.

Mi aspetto che il tuo problema scompaia se metti qualche secondo di sospensione alla fine di startup.sh. Non sono sicuro di quanto tempo ti serva. Se è importante ridurlo al minimo, forse puoi guardare qualcosa in proc per vedere quando è sicuro.


Buon punto, non pensare che la finestra per questo sarà molto lunga, probabilmente solo pochi millisecondi. È possibile verificare /proc/$!/commse non è nohuppiù possibile utilizzare l'output di ps -o comm= $!.
Graeme,

Questo dovrebbe funzionare per il normale logout, ma che dire di quando la sessione viene interrotta o interrotta? Non avresti ancora bisogno di rinnegare il lavoro, quindi è completamente ignorato da Sighup?
Iyrin,

@RyanLoremIpsum: lo script di avvio deve solo attendere abbastanza a lungo da separare completamente il processo figlio. Dopodiché, non importa cosa succede alla sessione ssh. Se qualcos'altro uccide la tua sessione ssh nella breve finestra mentre ciò accade, non c'è molto che puoi fare al riguardo.
MC0e

@Graeme sì, presumo sia molto veloce, ma non so abbastanza su cosa faccia esattamente il nohup per essere sicuro. Sarebbe utile un puntatore a una fonte autorevole (o almeno ben informata e dettagliata).
MC0e


1

Sembra più un problema con ciò che la pythonsceneggiatura o lo pythonstesso sta facendo. Tutto ciò nohupche fa davvero (barra che semplifica i reindirizzamenti) è semplicemente impostare il gestore per il HUPsegnale su SIG_IGN(ignora) prima di eseguire il programma. Non c'è nulla per fermare il programma ripristinandolo SIG_DFLo installando il proprio gestore una volta che inizia a funzionare.

Una cosa che potresti voler provare è racchiudere il tuo comando tra parentesi in modo da ottenere un doppio effetto fork e lo pythonscript non è più figlio del processo di shell. Per esempio:

( nohup python3 -u <script> & )

Un'altra cosa che può valere la pena provare (se stai usando bashe non un'altra shell) è usare il disownbuiltin invece di nohup. Se tutto funziona come documentato, questo in realtà non dovrebbe fare alcuna differenza, ma in una shell interattiva ciò impedirebbe al HUPsegnale di propagarsi allo pythonscript. È possibile aggiungere il rigetto sulla riga successiva o la stessa di seguito (notare l'aggiunta di un ;dopo a &è un errore bash):

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

Se la precedente o qualche combinazione di essa non funziona, sicuramente l'unico posto per affrontare il problema è nello pythonscript stesso.


L'effetto doppio fork sarebbe sufficiente (basato sulla risposta di @ RyanLoremIpsum)?
neverendingqs

Entrambi non hanno risolto il problema = [. Se si tratta di un problema di Python, hai idea di dove iniziare a indagare (non puoi pubblicare troppo dello script Python qui)?
neverendingqs

@neverendingqs, se intendi le huponexitcose, l'esecuzione in una subshell dovrebbe avere lo stesso effetto disowndel processo che non verrà aggiunto all'elenco dei lavori.
Graeme,

@neverendingqs, aggiornata la mia risposta. Hai dimenticato che dovresti usare i reindirizzamenti con disown. Non aspettarti che farà molta differenza però. Penso che la cosa migliore da fare sia modificare lo pythonscript in modo che ti dica perché sta uscendo.
Graeme,

Il reindirizzamento dell'output ha funzionato ( unix.stackexchange.com/a/176610/52894 ), ma non sono sicuro di quale sia la differenza tra farlo esplicitamente e ottenerlo nohup.
neverendingqs

0

Penso che sia perché il lavoro è legato alla sessione. Una volta terminato, vengono terminati anche tutti i lavori dell'utente.


2
Ma perché è diverso da ottenere un terminale, digitare ed eseguire il comando ed uscire? Entrambe le sessioni vengono chiuse una volta chiuse.
infinite dal

D'accordo, vorrei capire perché questo non è diverso dalla chiusura manuale del proprio terminale.
Avindra Goolcharan,

0

Se nohupriesci ad aprire il suo file di output, potresti avere un indizio nohup.out. È possibile che pythonnon si trovi sul percorso quando si esegue lo script tramite ssh.

Vorrei provare a creare un file di registro per il comando. Prova a usare:

nohup /usr/bin/python3 -u <script> &>logfile &

Uso sshper eseguire manualmente lo script, quindi suppongo che python3 sia nel percorso.
neverendingqs

@neverendingqs Il file di registro contiene qualcosa?
BillThor,

Niente di straordinario - l'avvio sembra normale.
Neverendingqs
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.