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

19

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

nohup python3 -u <script> &

Quando eu entro sshdiretamente e chamo esse script, o script python continua sendo executado em segundo plano depois que eu saio. No entanto, quando eu executo isso:

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

O processo termina assim que sshterminar de executá-lo e fecha a sessão.

Qual é a diferença entre os dois?

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

EDIT2: Também tentei criar um script init que chame startup.she execute ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "sudo service start <servicename>", mas tenha o mesmo comportamento.

EDIT3: Talvez seja algo mais 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 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 chega echo "Finished"e vejo a mensagem do servidor 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 fizer o SSH manualmente e matar o processo pessoalmente.

EDIT5: Utilizando EDIT4, se eu fizer uma solicitação para qualquer terminal, eu recebo uma página de volta, mas o Bottle erro:

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
fonte
Existe alguma maneira de obter uma descrição mais detalhada do que o script python faz? Você provavelmente ainda teria palpites sem o código fonte completo, mas saber mais sobre o que o script python faz pode nos ajudar a fazer palpites com melhor educação.
Bratchley
Sim - adicionado à pergunta.
neverendingqs
O script pode estar fazendo algo desde o início, que de alguma forma depende do terminal conectado ou algo assim, e isso pode ser um problema de tempo: se a sessão durar os primeiros segundos, ela funciona, caso contrário, não. Sua melhor opção pode ser executá-lo stracese você estiver usando Linux ou trussse estiver executando o Solaris e ver como / por que ele termina. Como por exemplo ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> strace -fo /tmp/debug ./startup.sh.
Celada
Você tentou usar o &no final do script de inicialização? A adição de &remove a dependência da sua sessão ssh de ser o ID pai (quando os IDs pais morrem, seus filhos também). Também acho que essa é uma pergunta duplicada com base neste post anterior. A postagem que enviei para você na frase anterior é uma duplicata desta postagem, que pode fornecer mais detalhes.
Jacob Bryan
Eu tentei nohup ./startup.sh &antes, mas tinha o mesmo comportamento. startup.shjá contém um garfo ( nohup python3 -u <script> &), então tenho certeza de que não preciso bifurcar novamente.
neverendingqs

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 &  

sshprecisa de um indicador que não tenha mais saída e que não exija mais entrada. Ter outra coisa como entrada e redirecionar os meios de saída sshpodem sair com segurança, pois a entrada / saída não vem nem vai para o terminal. Isso significa que a entrada precisa vir de outro lugar e a saída (STDOUT e STDERR) deve ir para outro lugar.

A </dev/nullpeça especifica /dev/nullcomo a entrada para <script>. Por que isso é útil aqui:

Redirecionar / dev / null para stdin fornecerá um EOF imediato a qualquer chamada de leitura desse processo. Isso geralmente é útil para desanexar um processo de um tty (esse processo é chamado de daemon). Por exemplo, ao iniciar um processo em segundo plano remotamente pelo ssh, você deve redirecionar o stdin para impedir que o processo aguarde entrada local. /programming/19955260/what-is-dev-null-in-bash/19955475#19955475

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

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

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

Existem três fontes padrão de entrada e saída para um programa. A entrada padrão geralmente vem do teclado se for um programa interativo ou de outro programa se estiver processando a saída do outro programa. O programa normalmente imprime na saída padrão e, às vezes, imprime no erro padrão. Esses três descritores de arquivo (você pode pensar neles como "data pipes") são frequentemente chamados STDIN, STDOUT e STDERR.

Às vezes, eles não são nomeados, são numerados! As numerações internas para eles são 0, 1 e 2, nessa ordem. Por padrão, se você não nomear ou número um explicitamente, estará falando de STDOUT.

Dado esse contexto, você pode ver que o comando acima está redirecionando a saída padrão para / dev / null, que é um local onde você pode despejar o que não desejar (geralmente chamado de bit-bucket) e depois redirecionar o erro padrão para a saída padrão ( você deve colocar um & na frente do destino ao fazer isso).

A breve explicação, portanto, é “toda a saída desse comando deve ser empurrada para um buraco negro”. Essa é uma boa maneira de fazer um programa ficar realmente quieto!
O que significa> / dev / null 2> & 1? | Xaprb

jlliagre
fonte
nohup python3 -u <script> >/dev/null 2>&1 &e nohup python3 -u <script> > nohup.out 2>&1 &trabalhou. Eu pensei que o nohup redireciona automaticamente toda a saída - qual é a diferença?
neverendingqs
@neverendingqs, qual versão nohupvocê tem no seu host remoto? Um POSIX nohupnão é necessário para redirecionar stdin, o que eu perdi, mas ainda deve redirecionar stdoute stderr.
Graeme
Parece que estou trabalhando nohup (GNU coreutils) 8.21.
neverendingqs
@neverendingqs, nohupimprime alguma mensagem, como nohup: ignoring input and appending output to ‘nohup.out’?
Graeme
Sim - essa é a mensagem exata.
neverendingqs
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"você está executando o shell script startup.sh como um comando ssh.

A partir da descrição:

Se o comando for especificado, ele será executado no host remoto em vez de no shell de logon.

Com base nisso, ele deve executar o script remotamente.

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

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

Se sua intenção é executar o script remotamente e você desejar que esse processo continue após o término da sua sessão ssh, você deverá primeiro iniciar uma screensessão no host remoto. Então você deve executar o script python na tela e ele continuará sendo executado após o término da sessão ssh.

Consulte o manual do usuário da tela

Enquanto eu acho que a tela é sua melhor opção, se você deve usar nohup, considere a configuração shopt -s huponexitno host remoto antes de executar o comando nohup. Como alternativa, você pode usar disown -h [jobID]para marcar o processo para que o SIGHUP não seja enviado a ele. 1 1

Como continuo executando o trabalho depois que saio de um prompt de shell em segundo plano?

O sinal SIGHUP (Hangup) é usado pelo seu sistema no terminal de controle ou na morte do processo de controle. Você pode usar o SIGHUP para recarregar arquivos de configuração e abrir / fechar arquivos de log também. Em outras palavras, se você sair do terminal, todos os trabalhos em execução serão encerrados. Para evitar isso, você pode passar a opção -h para desautorizar o comando. Essa opção marca cada jobID para que o SIGHUP não seja enviado ao trabalho se o shell receber um SIGHUP.

Além disso, consulte este resumo de como huponexitfunciona quando um shell é encerrado, morto ou descartado. Suponho que o seu problema atual esteja relacionado ao final da sessão do shell. 2

  1. Todos os processos filhos, em segundo plano ou não de um shell aberto em uma conexão ssh, são eliminados com SIGHUP quando a conexão ssh é fechada apenas se a opção huponexit estiver configurada: execute shopt huponexit para verificar se isso é verdade.

  2. Se huponexit for verdadeiro, você poderá usar nohup ou disown para dissociar o processo do shell, para que não seja morto quando você sair. Ou, execute as coisas com a tela.

  3. Se huponexit for false, o que é o padrão em pelo menos alguns linuxes atualmente, os trabalhos em segundo plano não serão eliminados no logout normal.

  4. Mas, mesmo que o huponexit seja falso, se a conexão ssh for interrompida ou cair (diferente do logout normal), os processos em segundo plano ainda serão eliminados. Isso pode ser evitado com rejeição ou nohup como em (2).

Finalmente, 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
iyrin
fonte
De acordo com a bashpágina do manual, huponexitdeve afetar apenas shells interativos e não scripts - 'Se a opção do shell huponexit tiver sido definida com o shopt, o bash envia um SIGHUP para todos os trabalhos quando um shell de logon interativo sai.'
Graeme
2

Talvez valha a pena tentar a -nopção ao iniciar um ssh? Isso evitará a dependência remota do processo em um local stdin, que é claro que fecha assim que ssh sessiontermina. E isso causará o cancelamento remoto dos preços sempre que ele tentar acessar seus stdin.

Georgiy
fonte
Tentei sem sucesso = [.
neverendingqs
2

Eu suspeito que você tem uma condição de corrida. Seria algo como isto:

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

Se o ssh não abreviasse, o seguinte teria acontecido (não tenho certeza sobre a ordem dos dois):

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

Portanto, as duas etapas críticas finais não acontecem, porque o startup.sh e o ssh terminam antes que o nohup tenha tempo para fazer suas coisas.

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

mc0e
fonte
Bom ponto, não pense que a janela para isso será muito longa - provavelmente apenas alguns milissegundos. Você pode verificar /proc/$!/commse não é nohupou de forma mais portável a saída de ps -o comm= $!.
Graeme
Isso deve funcionar no logout normal, mas e quando a sessão é interrompida ou interrompida? Você ainda não precisaria negar o trabalho, para que seja totalmente ignorado pelo suspiro?
iyrin
@RyanLoremIpsum: O script de inicialização precisa esperar apenas o tempo suficiente para que o processo filho seja totalmente desanexado. Depois disso, não importa o que acontece com a sessão ssh. Se outra coisa matar sua sessão ssh na breve janela enquanto isso acontecer, não há muito o que fazer.
Mc0e
@ Graeme sim, eu presumo que seja muito rápido, mas eu simplesmente não sei o suficiente sobre exatamente o que o nohup faz para ter certeza. Um ponteiro para uma fonte autorizada (ou pelo menos conhecedora e detalhada) sobre isso seria útil.
Mc0e
1

Isso parece mais um problema com o que o pythonscript ou pythonele próprio está fazendo. Tudo o que nohuprealmente faz (bar simplificando os redirecionamentos) é apenas definir o manipulador para o HUPsinal SIG_IGN(ignorar) antes de executar o programa. Não há nada para parar o programa de configurá-lo novamente SIG_DFLou instalar seu próprio manipulador quando ele começar a ser executado.

Uma coisa que você pode querer tentar é colocar seu comando entre parênteses, para obter um efeito de bifurcação dupla e seu pythonscript não é 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 bashe não outro shell) é usar o disownbuiltin em vez de nohup. Se tudo estiver funcionando como documentado, isso não deve realmente fazer diferença, mas em um shell interativo isso impediria a HUPpropagação do sinal para o seu pythonscript. Você pode adicionar o rejeitado na próxima linha ou na mesma linha abaixo (observe que a adição de um ;após a &é um erro bash):

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

Se as opções acima ou alguma combinação delas não funcionarem, certamente o único lugar para resolver o problema está no pythonpróprio script.

Graeme
fonte
O efeito do garfo duplo seria suficiente (com base na resposta de @ RyanLoremIpsum)?
neverendingqs
Ambos não resolveram o problema = [. Se for um problema do Python, você tem uma idéia de por onde começar a investigar (não pode postar muito do script Python aqui)?
precisa
@neverendingqs, se você está falando sério huponexit, a execução em um subshell deve ter o mesmo efeito, disownpois o processo não será adicionado à lista de tarefas.
Graeme
@neverendingqs, atualizei minha resposta. Esqueceu que você deve usar redirecionamentos com disown. Não espere que isso faça muita diferença. Eu acho que sua melhor aposta é alterar o pythonscript para que ele lhe diga por que está saindo.
Graeme
Redirecionar a saída funcionou ( unix.stackexchange.com/a/176610/52894 ), mas não tenho certeza de qual é a diferença entre fazê-lo explicitamente e nohupfazê-lo.
precisa
0

Eu acho que é porque o trabalho está vinculado à sessão. Uma vez terminado, os trabalhos do usuário também serão encerrados.

user208145
fonte
2
Mas por que isso é diferente de obter um terminal, digitar e executar o comando e sair? Ambas as sessões são fechadas quando eu a fecho.
neverendingqs
Concordo, gostaria de entender por que isso não é diferente de fechar seu próprio terminal manualmente.
Avindra Goolcharan
0

Se nohuppode abrir seu arquivo de saída, você pode ter uma pista nohup.out. É possível que pythonnão esteja no caminho quando você executa o script via ssh.

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

nohup /usr/bin/python3 -u <script> &>logfile &
BillThor
fonte
Eu uso sshpara executar o script manualmente, então estou assumindo que python3 está no caminho.
neverendingqs
@neverendingqs O arquivo de log contém alguma coisa?
BillThor
Nada fora do comum - a inicialização parece normal.
neverendingqs