为什么当 SSH 会话终止时我的 Python 后台进程也会结束?

为什么当 SSH 会话终止时我的 Python 后台进程也会结束?

我有一个 bash 脚本,它启动一个 python3 脚本(我们称之为startup.sh),其关键行是:

nohup python3 -u <script> &

当我ssh直接进入并调用这个脚本时,python 脚本在我退出后继续在后台运行。但是,当我运行这个时:

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

该进程在运行完成后立即结束ssh并关闭会话。

两者有什么区别?

编辑:python 脚本正在通过 Bottle 运行 Web 服务。

EDIT2:我也尝试过创建初始化脚本调用startup.sh并运行ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "sudo service start <servicename>",但得到了相同的行为。

EDIT3:也许是脚本中的其他内容。这是脚本的大部分内容:

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:当我运行最后一行并在最后睡眠时:

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"

它永远不会到达echo "Finished",我看到了 Bottle 服务器消息,这是我以前从未见过的:

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

如果我手动通过 SSH 登录并自行终止该进程,我会看到“已完成”。

EDIT5:使用 EDIT4,如果我向任何端点发出请求,我会返回一个页面,但 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)

答案1

我会断开命令与其标准输入/输出和错误流的连接:

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

ssh需要一个不再有任何输出且不需要任何更多输入的指标。将其他内容作为输入并重定向输出意味着ssh可以安全退出,因为输入/输出不是来自或去往终端。这意味着输入必须来自其他地方,并且输出(STDOUT 和 STDERR)应该来自其他地方。

</dev/null部分指定/dev/null为 的输入<script>。为什么这在这里有用:

将 /dev/null 重定向到 stdin 将为来自该进程的任何读取调用提供立即 EOF。这通常对于从 tty 分离进程很有用(这样的进程称为守护进程)。例如,当通过 ssh 远程启动后台进程时,必须重定向 stdin 以防止进程等待本地输入。 https://stackoverflow.com/questions/19955260/what-is-dev-null-in-bash/19955475#19955475

或者,只要当前ssh会话不需要保持打开状态,从另一个输入源重定向应该相对安全。

使用该>/dev/null部分,shell 将标准输出重定向到 /dev/null ,实质上将其丢弃。>/path/to/file也会起作用。

最后一部分2>&1是将 STDERR 重定向到 STDOUT。

程序有三种标准的输入和输出源。如果它是交互式程序,则标准输入通常来自键盘;如果它正在处理另一个程序的输出,则标准输入通常来自另一个程序。程序通常打印到标准输出,有时也打印到标准错误。这三个文件描述符(您可以将它们视为“数据管道”)通常称为 STDIN、STDOUT 和 STDERR。

有时它们没有被命名,而是被编号!它们的内置编号依次为 0、1 和 2。默认情况下,如果您没有明确命名或编号第一,那么您正在谈论 STDOUT。

在这种情况下,您可以看到上面的命令将标准输出重定向到 /dev/null,您可以在其中转储不需要的任何内容(通常称为位桶),然后将标准错误重定向到标准输出(执行此操作时,必须在目的地前面添加 &)。

因此,简短的解释是“该命令的所有输出都应该被推入黑洞”。这是让程序真正安静的一种好方法!
> /dev/null 2>&1 是什么意思? | Xaprb

答案2

看着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]

当您运行时,ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh"您正在将 shell 脚本startup.sh 作为 ssh 命令运行。

从描述来看:

如果指定了 command,则它将在远程主机上执行,而不是在登录 shell 上执行。

基于此,应该是远程运行脚本。

它与在本地终端中运行的区别nohup python3 -u <script> &在于,它作为本地后台进程运行,而 ssh 命令尝试将其作为远程后台进程运行。

如果您打算在本地运行该脚本,则不要将startup.sh 作为 ssh 命令的一部分运行。你可以尝试类似的事情ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> && "./startup.sh"

如果您打算远程运行脚本,并且希望在 ssh 会话终止后继续此过程,则必须首先screen在远程主机上启动会话。然后你必须在屏幕中运行 python 脚本,它会在你结束 ssh 会话后继续运行。

屏幕使用手册

虽然我认为 screen 是最好的选择,但如果必须使用 nohup,请考虑shopt -s huponexit在运行 nohup 命令之前在远程主机上进行设置。或者,您可以使用disown -h [jobID]来标记进程,这样就不会向它发送 SIGHUP。1

从后台 shell 提示符退出后如何继续运行作业?

SIGHUP(挂断)信号由系统在控制终端或控制进程死亡时使用。您也可以使用 SIGHUP 重新加载配置文件并打开/关闭日志文件。换句话说,如果您从终端注销,所有正在运行的作业都将终止。为了避免这种情况,您可以将 -h 选项传递给 disown 命令。此选项标记每个 jobID,以便在 shell 收到 SIGHUP 时不会向作业发送 SIGHUP。

huponexit另外,请参阅有关 shell 退出、终止或删除时如何工作的摘要。我猜您当前的问题与 shell 会话如何结束有关。2

  1. 仅当设置了 huponexit 选项时,当 ssh 连接关闭时,通过 ssh 连接打开的 shell 的所有子进程(无论是否处于后台)都会通过 SIGHUP 终止:运行 shopt huponexit 以查看是否属实。

  2. 如果 huponexit 为 true,则可以使用 nohup 或 disown 将进程与 shell 分离,这样退出时它就不会被杀死。或者,用屏幕运行东西。

  3. 如果 huponexit 为 false(目前至少在某些 Linux 上这是默认值),那么后台作业将不会在正常注销时被终止。

  4. 但即使 huponexit 为 false,如果 ssh 连接被终止或断开(与正常注销不同),那么后台进程仍然会被终止。这可以通过 (2) 中的 disown 或 nohup 来避免。

最后,这里有一些如何使用 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

答案3

也许值得 -n在开始时尝试选项ssh?它将防止远程进程对本地进程的依赖stdin,当然本地进程一旦ssh session结束就会关闭。每当它尝试访问其stdin.

答案4

这听起来更像是python脚本或其python本身正在做什么的问题。真正要做的(除了简化重定向)只是在运行程序之前将信号nohup的处理程序设置HUP为(忽略)。一旦程序开始运行SIG_IGN,就没有什么可以阻止程序将其设置回SIG_DFL或安装自己的处理程序。

您可能想要尝试的一件事是将命令括在括号中,以便获得双分叉效果,并且您的python脚本不再是 shell 进程的子进程。例如:

( nohup python3 -u <script> & )

另一件可能也值得尝试的事情(如果您正在使用bash而不是另一个 shell)是使用disown内置而不是nohup.如果一切都按记录工作,这实际上不会产生任何影响,但在交互式 shell 中,这会阻止HUP信号传播到python脚本。您可以在下一行或与下面相同的行添加 disown(注意在 a;后面添加 a&是错误的bash):

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

如果上述或某些组合不起作用,那么解决该问题的唯一位置肯定是python脚本本身。

相关内容