为什么后台进程在 ssh 会话关闭后终止

为什么后台进程在 ssh 会话关闭后终止

我使用XManager xshell作为ssh客户端,连接到远程服务器,然后执行命令:

nohup sleep 60 &
ps -ef | grep sleep
exit

然后再次登录

ps -ef | grep sleep

那个过程消失了!

有什么可能导致这个吗? ssh守护进程是openssh 8,服务器是redhat 7

答案1

对于systemd-logind,有(默认)设置:

KillUserProcesses=yes

在你的登录配置文件。用户注销后,它将终止用户在登录会话期间启动的所有进程。您可以将其设置为否,或为您的用户设置以下设置:

KillExcludeUsers=yourusername

答案2

看来您想要的是以“可分离”的方式运行远程命令,以便您将来可以连接回它。nohup确实是一个 hack,正如你所看到的,当用户会话终止时,systemd 会处理它。

您可以通过使用一些终端多路复用器(如tmuxscreenabduco其他)来更优雅地解决此问题,而不是通过禁用其清理机制来在系统级别解决此问题。

终端多路复用器可以创建分离的会话,即使您注销,该会话也将继续运行。您可以稍后重新连接到会话并调整其内容。

在 jsbillings@ 评论后编辑:

我承认我最初的回复虽然是正确的,但看起来相当简洁,因此您(可能)迷失了它的含义,并且还促使 jsbillings@ 写了无用的评论和次优答案。

问题的关键在于,您丢失了相当多的“幕后信息”,并且 jsbillings@ 不愿意详细说明如何正确修复/解决它。重大责任也归咎于 systemd 开发人员。

最后我承认错误也出在我身上,因为我没有给你完整的答复,这是相当疲惫和乏味的,因为大多数现代Linux的工作方式(因为提到的systemd - 所以是的,我也放弃了很快)。

首先,正如您现在所看到的,即使您的问题的标题也是技术上错误因为 bash 甚至没有进入这里。它的使用在这里完全无关紧要——任何 shell 都可以,因为这是系统级问题而不是与命令解释器相关的问题。

其次,我的回复仍然有效:不要使用 nohup(即使它适用于 jsbillings 解决方案),它是过去时代的蹩脚黑客,在你出生之前很久就结束了,可能

现在准备一个解决方案,这将相当长,但只是因为它必须如此。

首先,正如你所知,我并不是 systemd 的忠实粉丝,但不幸的是,在大多数普通的 Linux 上,这就是你现在所使用的 init 件。

另外,永远记住一个好建议:“了解你的敌人”。有趣的是,尽管我反对 systemd,但我对它了解很多,而且我向你保证,我的回答仍然是非常合格的。

systemd 的主要问题是,至少在这种情况下,它“太聪明”了,它认为自己肯定比你聪明。可能就是这样。除非不是:)。

这也是一种模式,当 systemd 的“适当性”(实际上是在旁观者的眼中)变得广泛存在问题时,它的核心开发团队并不羞于将一堆黑客堆积起来,并做一些愚蠢的事情“快点”解决它。

你看,systemd 就是我们现在所说的“固执己见”。我也很固执己见,所以我们两个相处得不太好。我比systemd还要聪明,所以我确实能理解systemd是从哪里来的。

现在,通过使了解systemd从何而来,我也可以给你一个真正的解决方案。 Systemd 和它的团队并不真正关心给你这个(教育),因为它“知道得更好”(而且,因为它只是想控制你的系统,因为你甚至不知道 - 教育不是最重要的该项目的重点,即使他们的文档相当出色)。

因此,长话短说:为了不破坏数百万人的屏幕和 tmux 会话,他们没有进行教育,而是引入了前面提到的蹩脚黑客,例如:

KillUserProcesses=yes
KillExcludeUsers=yourusername

但我的目标与他们不同:我希望通过给予你理解和知识来掌控一切。因此,您正在阅读这篇冗长的回复。

我关于以“可分离的方式”运行命令的说法仍然有效(最终),但是“现在”的 linux 不是您的“二十年前的 linux”。根据我的经验,大多数管理员和用户的生活比实际的“linux now”时间晚了 5-20 年。这无助于教育信息的扩散。

在处理 unice,甚至 linux 时,历史视角是最好的。

十年前,Linux 开始支持所谓的 cgroup,这是基本的进程标记和组织技术。在此之前,一旦进程生成,您就很难观察和审核其原始上下文(成本低廉),至少在标准的类 UNIX 系统上是这样。

Cgroup 是一个 Linux 进程管理扩展。

可以从这些 cgroup 原语构建很多东西(它们可用于资源修剪和分配、容器实现等),但对您来说最重要的一点是,cgroup 无法触及非系统软件 - 即每个进程在系统上有 cgroup 标记,但进程本身(除非在非常特殊的情况下,如浏览器沙箱等)无法操纵它们所属的 cgroup。这是整个 cgroup 系统非常重要的属性。

即,只有系统软件可以创建 cgroup 并将进程“移动”到 cgroup 中(除了提到的 ofc 例外)。

在系统化的Linux上,systemd“巧妙地使用”(或者更确切地说滥用这些cgroup属性)来跟踪给定进程的“对象”起源的上下文(即“机器”(容器),服务,会话或您拥有的东西)。由于 cgroup 可以嵌套,激进的神经质自闭症患者对可以实现的流程分类的无限官僚潜力感到非常兴奋:机器、切片、范围……火箭筒。当然,你对此没有发言权,因为你固执己见。

简化:一旦 systemd 将 cgroup(从现在开始为 CG)分配给进程,该进程就会被卡住。

这样,几十年来我们第一次(即自 9 年前)可以可靠地告诉“谁”实际上产生了给定的进程:它是一个服务、一个登录会话、外星人......?

systemd-cgls您可以通过调用(或systemctl status在任何“最近的”linux 中)查看系统的 CG 布局。现在您可以亲眼看到 CG 是如何嵌套在 systemd 系统中的(tsssh 没有告诉任何人,但是这些 GS,它们实际上是真正的伪文件系统对象,即文件和目录,位于某处)。

因此,systemd 首先将进程(真实)机器群体分为所谓的system.slice(只是 CG 名称)和user.slice.system.slice进一步细分为无限数量的 CG,每个运行的服务都有一个 CG,因此您会得到名为 的 CG crond.servicesshd.service依此类推。

这实际上是一个非常漂亮的功能,因为现在,即使服务的主进程(“守护进程”)突然死亡,我们仍然知道如何识别它产生的每个蹩脚进程:每个后代进程都会分配给它相同的 CG,即something.service

该算法允许 systemd 终止服务可能在服务停止事件上产生的所有内容(每个进程)。

user.slice情况要复杂得多。它不是user.slice任意分割,而是根据每个登录的本地用户进行分割(即每个用户帐户为自己获取一个 user.slice,一旦给定用户第一次登录就会实例化)。

可以说这也非常聪明,因为它允许 systemd 限制给定用户的所有进程(每个 user.slice)的内存消耗 - 通过在某处搞乱某些 systemd 旋钮 - 但这对我们来说并不有趣无论如何在这种情况下。

无论如何,对于交互式登录的用户来说,这样的粒度在 systemd 设计者看来仍然太粗了。

因此,systemd 采用了更奇特的设计方案:给定用户的每个 user.slice 被分割成更小的 CG,所谓的 CG session.scope。每次交互式用户生成登录会话时都会创建一个 session.scope。

问题就在这里:问题是不加区别地作用于这些会话CG。

对于会话范围,systemd 对它们采用与服务相同的终止逻辑:一旦用户注销,他就没有权利让任何东西在后台运行(我已经说过固执己见了吗?)。拧紧 10 多年之前使用的 screen 和 tmux(早在 systemd 存在之前)。

现在,我们可以无限争论,这是正确的解决方案:是否让交互式会话生成的后台进程运行,或者是否将它们全部杀死。好吧,至少在我看来,正确的解决方案是:视情况而定。

杀死普通用户进程可能会很好:如果您有多用户计算机并且用户是“普通”人,它会定期修剪他们上次注销时留下的所有垃圾。但您还希望 tmux 具有长时间后台作业(一次性备份)。

幸运的是,有一种方法,但这些都是可憎的:

KillUserProcesses=yes
KillExcludeUsers=yourusername

为什么?

两者都是核选项(全有或全无):第一个允许所有用户用剩余的垃圾占用机器(不好),第二个允许特定用户用剩余的垃圾占用机器(也不好)。如果您要禁用它,为什么还要费心进行可配置的清理呢?

更“正确”的解决方案是两全其美的:默认情况下让 systemd 修剪交互式生成的进程,但也使用 tmux 作为真正的 systemd 服务:这允许在注销时自动清理任务,但仍然保留长时间运行进程的能力在 tmux 中。

您会看到,通过套接字交换的数据可以跨越“系统化”的 CG 边界。您可以httpd.service从进程连接到进程套接字session-yourblabla.scope

这就是你如何拥有适当的 tmuxified“背景”,一旦你从终端注销,它就不会被杀死。 tmux“渲染器”(在登录会话中)和 tmux“服务器”(systemd 服务)之间的 tmux 通信通过 unix 套接字流动,它可以自然地穿过 systemd 的 CG 边界。

如何实现这一目标?

嗯,这在过去有点问题,因为 screen 和 tmux 都是在 systemd 垃圾存在之前设计的。

这也是当前自动杀戮行为发生的原因。

screen 和 tmux 都有一个漂亮的功能,允许多路复用器守护进程在您调用第一个 tmux 命令时自动启动。这几十年来一直运行良好(因为之前没有人考虑登录会话中的进程)......直到 systemd 出现。

从现在开始,我们将只专注于 tmux 解决方案,因为 screen 只是一个意大利面条编码的玩具,也许它缺乏所需的 tmux 功能。

在系统化系统中,当您的终端的 tmux 自动生成一个 tmux 服务器守护进程来保存您的实际 tmux 会话时,由于已经描述的 CG 继承,给定的服务器守护进程仍然被标记为属于给定的登录会话(因为它继承了 session-xx.scope CG)。正如我们所知,如果没有 systemd,进程就无法改变其 CG,除此之外,systemd 故意不提供这样做的工具,因为你知道,它是固执己见的。

因此,当交互式注销的时间到来时,由于 systemd 的清理算法,属于给定登录会话的所有进程都会终止,甚至是完全独立的 tmux 服务器守护进程(因为 CG 标签仍然将其链接到给定会话) )。

在这方面,jsbillings@ 的回复有些正确:您无法在自动会话 CG 标签杀手中幸存(当然,除非在 systemd 级别禁用它),但只有当您的 tmux 服务器是从任何登录会话生成时,这才是正确的。因此,在自己的 systemd CG 中生成的独立 tmux 服务器与登录会话无关,因此不受修剪的影响。

正如我们所说,我们希望保留 Killer 的原样,因为它可能对于剔除机器用户进程很有用。

user.slice那么,正确的解决方案是将您的 tmux 服务器守护进程从并分别“驱逐”session.scope到它自己的[email protected].

正如我们已经了解到的,服务不绑定到登录会话,并且不受 systemd 登录会话杀死的影响,因为它们毕竟是系统服务。

不幸的是,除非你正在运行最近的 tmux(3.2a 后),否则这并不是那么容易(它总是可以完成,相信我,但这需要一些思考 - 但这超出了这个已经超长的回复的范围) 。

另一方面,我可以说,从 tmux 版本 3.2a 开始,使用 systemd 就可以非常非常可靠地工作,并且无需黑客攻击。

在 tmux 版本之前,问题在于 tmux(和 screen)的另一个功能,一旦没有“tmux session(!)”(即作为 in-tmux 终端+bash shell 进程 - 这有与给定用户关联的 systemd 登录会话无关)。

因此,对于较旧的 tmux,即使您从服务启动了守护进程,没有对其进行一些修改,它也会立即终止,从而使您的 systemd tmux 用户服务变得毫无用处。

最后,使用最新的 linux 和 tmux 来实现这一点需要哪些神奇的步骤:

  1. 您需要直接以服务器模式启动 tmux 服务器,即作为前台“守护进程”,而不是使用它的自动启动功能(它会生成实际的守护进程,因为它是后代)。
  2. 同时,您需要禁用 tmux 服务器的“无 tmux 会话时自动退出”功能,否则即使在前台模式下,服务器也会立即退出。值得庆幸的是,在最近的 tmux 中,步骤 1. 也自动处理了这个问题。
  3. 那么您需要将此设置配置为系统级 tmux 用户服务器服务。
  4. 一旦系统级 tmux 用户服务器服务启动,您就可以在该 tmux 用户服务器服务 CG 内启动长时间运行的 tmux 会话,并且在您注销计算机后,systemd 将让这些 tmux 会话无限期地在那里运行。

对于 1. 和 2. 请参阅新的 tmux-D标志。对于3.这里有一个非常基本的食谱:

# cat /etc/systemd/system/tmux-myuser.service
[Unit]
Description=Permanent tmux server for myuser user
 
[Service]
Type=exec
User=myuser
Group=myuser
ExecStart=/usr/bin/tmux -D
 
[Install]
WantedBy=multi-user.target

创建该服务然后启动它。调用后,systemd status您现在应该会看到 tmux 服务器作为“守护进程”或更确切地说是 systemd 服务运行。从同一用户下的任何其他登录 shell 运行 tmux 命令,应该在 tmux-myuser.service cgroup 内生成它的进程树(再次检查 systemctl status)。

一旦你开始工作,如果你感觉很有趣,你现在可以使用 @systemd 变量和诸如此类的东西将你的 tmux 用户服务调整为 autogen 用户服务。

事实上,这个解决方案可以应用于任何终端多路复用器,只要它可以在服务器模式下受监督运行,并且您将其封装到独立的 systemd 服务中即可。

与 nohup 相比,该解决方案有哪些优点?

相对于 nohup 有很多优点。

首先,您可能不知道 nohup 的作用是什么,但是只有当传统的 UNIX 范式(每个进程都在没有原始上下文跟踪终止的情况下保持运行)成立时,这种魔法才起作用。在 Linux 上,这种范例已经不适用超过 9 年了。系统化的 tmux 服务完全避免了这种情况。

其次,您无法访问 nohupped 进程输出流及其完整的进程控制。 Nohup 允许您将进程输出“重定向”到“日志文件”(实际上它只是在那里转换标准输出描述符),但这是次优的(观察、旋转等)。正确地做这件事需要大量的计划,但即便如此,它也非常脆弱。

主要进程控制将永远丢失:在原始生成 shell 消失后,控制 nohupped 进程的唯一方法是通过 PID 和信号。没有其他 shell 会记住 nohupped 进程的进程组,它无法通过jobs命令选择和可见。

从 tmux 运行这样的后台任务可以自动处理所有这些事情,并且还为您提供了其他几个非常强大的优势:

  1. 您可以将 tmux“日志缓冲区”保存到文件(即仅按需记录需要的内容)
  2. 更重要的是,tmux 后台允许您在需要时仍然将输入传递到后台进程(即仍然可以输入密码)
  3. 提前运行正确的后台 tmux 服务允许您稍后生成无限数量的后台任务:意外备份、大文件传输、临时守护进程等命令
  4. 您可以组织多个意外的后台任务上下文,只需让它们在命名的 tmux 会话中运行即可。一个 tmux 会话中的多个窗格甚至允许您运行一群协作进程。此外,tmux 窗格布局可以有效地表达其重要性或内部依赖性或层次结构。

正如您所看到的,您从多路复用到 systemd 服务中获得了很多好处。

相关内容