Por que meu processo de segundo plano em Python termina quando a sessão SSH é finalizada?

16

Eu tenho um script bash que inicia um script python3 (vamos chamá-lo de startup.sh ), com a linha chave:

nohup python3 -u <script> &

Quando eu ssh in diretamente e chamo esse script, o script python continua sendo executado em segundo plano depois que eu saio. No entanto, quando eu corro isso:

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

O processo termina assim que ssh terminar de executá-lo e fechar a sessão.

Qual é a diferença entre os dois?

EDIT: O script python está executando um serviço da web via Bottle.

EDIT2: Eu também tentei criar um init script que chama startup.sh e executou ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "sudo service start <servicename>" , mas obteve o mesmo comportamento.

EDIT3: Talvez seja outra coisa no script. Aqui está a maior parte do 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 eu corro a última linha com um sono no 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"

Ele nunca alcança echo "Finished" e vejo a mensagem do servidor do Bottle, que nunca vi antes:

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

Eu vejo "Concluído" se eu manualmente SSH entrar e matar o processo sozinho.

EDIT5: Usando o EDIT4, se eu fizer um pedido para qualquer endpoint, recebo uma página de volta, mas os erros do Bottle out:

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)
    
por neverendingqs 14.11.2014 / 14:58

7 respostas

11

Eu desconectaria o comando de seus fluxos padrão de entrada / saída e erro:

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

ssh precisa de um indicador que não tenha mais saída e que não precise de mais entradas. Tendo alguma outra coisa sendo a entrada e redirecionando os meios de saída ssh pode sair com segurança, como entrada / saída não está vindo ou indo para o terminal. Isso significa que a entrada deve vir de algum outro lugar, e a saída (STDOUT e STDERR) deve ir para outro lugar.

A parte </dev/null especifica /dev/null como entrada para <script> . Por que isso é útil aqui:

Redirecting /dev/null to stdin will give an immediate EOF to any read call from that process. This is typically useful to detach a process from a tty (such a process is called a daemon). For example, when starting a background process remotely over ssh, you must redirect stdin to prevent the process waiting for local input. https://stackoverflow.com/questions/19955260/what-is-dev-null-in-bash/19955475#19955475

Como alternativa, o redirecionamento de outra fonte de entrada deve ser relativamente seguro, contanto que a sessão ssh atual não precise ser mantida aberta.

Com a parte >/dev/null , o shell redireciona a saída padrão para / dev / null essencialmente descartando-a. >/path/to/file também funcionará.

A última parte 2>&1 está redirecionando STDERR para STDOUT.

There are three standard sources of input and output for a program. Standard input usually comes from the keyboard if it’s an interactive program, or from another program if it’s processing the other program’s output. The program usually prints to standard output, and sometimes prints to standard error. These three file descriptors (you can think of them as “data pipes”) are often called STDIN, STDOUT, and STDERR.

Sometimes they’re not named, they’re numbered! The built-in numberings for them are 0, 1, and 2, in that order. By default, if you don’t name or number one explicitly, you’re talking about STDOUT.

Given that context, you can see the command above is redirecting standard output into /dev/null, which is a place you can dump anything you don’t want (often called the bit-bucket), then redirecting standard error into standard output (you have to put an & in front of the destination when you do this).

The short explanation, therefore, is “all output from this command should be shoved into a black hole.” That’s one good way to make a program be really quiet!
What does > /dev/null 2>&1 mean? | Xaprb

    
por 30.12.2014 / 15:59
3

Veja 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 você executa ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh" , está executando o shell script startup.sh como um comando ssh.

A partir da descrição:

If command is specified, it is executed on the remote host instead of a login shell.

Com base nisso, ele deve estar executando o script remotamente.

A diferença entre isso e a execução de nohup python3 -u <script> & em seu terminal local é que ele é executado como um processo de segundo plano local, enquanto o comando ssh tenta executá-lo como um processo de segundo plano remoto.

Se você pretende executar o script localmente, não execute o arquivo startup.sh como parte do comando ssh. Você pode tentar algo como ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> && "./startup.sh"

Se a sua intenção for executar o script remotamente e você quiser que esse processo continue depois que sua sessão ssh for encerrada, você deve primeiro iniciar uma sessão screen no host remoto. Então você tem que executar o script python dentro da tela e ele continuará a rodar depois que você terminar sua sessão ssh.

Veja o Manual do usuário da tela

Embora eu ache que a tela seja sua melhor opção, se você precisar usar nohup, considere configurar shopt -s huponexit no host remoto antes de executar o comando nohup. Alternativamente, você pode usar disown -h [jobID] para marcar o processo para que o SIGHUP não seja enviado a ele. 1

How do I keep running job after I exit from a shell prompt in background?

The SIGHUP (Hangup) signal is used by your system on controlling terminal or death of controlling process. You can use SIGHUP to reload configuration files and open/close log files too. In other words if you logout from your terminal all running jobs will be terminated. To avoid this you can pass the -h option to disown command. This option mark each jobID so that SIGHUP is not sent to the job if the shell receives a SIGHUP.

Além disso, veja este resumo de como huponexit funciona quando um shell é encerrado, morto ou descartado. Eu estou supondo que seu problema atual está relacionado a como termina a sessão do shell. 2

  1. All child processes, backgrounded or not of a shell opened over an ssh connection are killed with SIGHUP when the ssh connection is closed only if the huponexit option is set: run shopt huponexit to see if this is true.

  2. If huponexit is true, then you can use nohup or disown to dissociate the process from the shell so it does not get killed when you exit. Or, run things with screen.

  3. If huponexit is false, which is the default on at least some linuxes these days, then backgrounded jobs will not be killed on normal logout.

  4. But even if huponexit is false, then if the ssh connection gets killed, or drops (different than normal logout), then backgrounded processes will still get killed. This can be avoided by disown or nohup as in (2).

Por fim, aqui estão alguns exemplos de como usar o 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
    
por 26.12.2014 / 01:27
2

Talvez valha a pena tentar a opção -n ao iniciar um ssh ? Isso impedirá a dependência do processo remoto em um local stdin , que obviamente fecha assim que ssh session terminar. E isso causará a finalização remota dos preços sempre que tentar acessar seu stdin .

    
por 24.12.2014 / 00:44
2

Eu suspeito que você tenha uma condição de corrida. Seria algo assim:

  • A conexão SSH é iniciada
  • O SSH inicia o startup.sh
  • startup.sh inicia um processo em segundo plano (nohup)
  • startup.sh termina
  • ssh termina, e isso mata os processos filhos (ou seja, nohup)

Se o ssh não tivesse reduzido as coisas, o seguinte teria acontecido (não tenho certeza sobre a ordem desses dois):

  • nohup inicia seu script python
  • nohup desconecta do processo pai e do terminal.

Portanto, os dois últimos passos críticos não acontecem, porque o startup.sh e o ssh terminam antes que o nohup tenha tempo para fazer o que ele faz.

Espero que o seu problema desapareça se você colocar alguns segundos de inatividade no final de startup.sh. Não sei exatamente quanto tempo você precisa. Se for importante mantê-lo no mínimo, talvez você possa procurar algo no proc para ver quando é seguro.

    
por 29.12.2014 / 14:49
1

Isso soa mais como um problema com o que o script python ou python está fazendo. Tudo o que o nohup realmente faz (simplificando os redirecionamentos) é apenas definir o manipulador para o sinal HUP para SIG_IGN (ignorar) antes de executar o programa. Não há nada que impeça o programa de voltar para SIG_DFL ou instalar seu próprio manipulador quando ele começar a ser executado.

Uma coisa que você pode querer tentar é colocar o seu comando entre parênteses para que você obtenha um efeito duplo e seu script python não seja mais um filho do processo do shell. Por exemplo:

( nohup python3 -u <script> & )

Outra coisa que pode valer a pena tentar (se você estiver usando bash e não outro shell) é usar o disown builtin em vez de nohup . Se tudo estiver funcionando conforme documentado, isso não deve fazer diferença, mas em um shell interativo isso impediria que o sinal HUP fosse propagado para o script python . Você pode adicionar a rejeição na próxima linha ou na mesma linha abaixo (observe a adição de um ; depois que um & for um erro em bash ):

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

Se a combinação acima ou alguma não funcionar, certamente o único lugar para resolver o problema é o próprio script python .

    
por 26.12.2014 / 00:17
0

Eu acho que é porque o trabalho está ligado à sessão. Uma vez terminado, qualquer trabalho do usuário é finalizado também.

    
por 14.11.2014 / 15:07
0

Se nohup puder abrir seu arquivo de saída, você poderá ter uma pista em nohup.out . É possível que python não esteja no caminho quando você executar o script via ssh .

Eu tentaria criar um arquivo de log para o comando. Tente usar:

nohup /usr/bin/python3 -u <script> &>logfile &
    
por 15.11.2014 / 01:21