我可以配置或启用什么来确定 systemd 对服务采取“停止”操作的原因?

我可以配置或启用什么来确定 systemd 对服务采取“停止”操作的原因?

我们有一个启动第三方代理的systemd服务单元;称之为“服务c”。服务单元运行正常——至少据我所知!在一个修补周期之后,systemd 启动这个服务单元(如预期),但随后它又转过来并停止服务单元在成功启动后大约两秒。我有充分的理由相信服务第一次就成功启动了。重启后登录,可以看到该服务确实没有运行;此时,我可以手动启动服务单元 ( systemctl start service-c),它会按预期启动服务。

我想知道为什么 systemd 认为它应该停止服务单元。我可以配置或启用什么来确定 systemd 采取“停止”操作的原因?

我知道systemd 日志级别选项并已将其设置为“调试”,而不是默认的“信息”。

类似的想法是在服务单元文件中设置Environment=SYSTEMD_LOG_LEVEL=debug,但我不是特别需要服务调试,而是 systemd 本身。

服务单元配置为:

# /etc/systemd/system/service-c.service
[Unit]
Description=service c
After=network-online.target local-fs.target

[Service]
Type=forking
ExecStart=/local-path/start.service-c
ExecStop=/local-path/stop.service-c
Restart=on-failure

[Install]
WantedBy=multi-user.target

...证据是:

$ systemctl status service-c
● service-c.service - service c
   Loaded: loaded (/etc/systemd/system/service-c.service; enabled; vendor preset: disabled)
   Active: inactive (dead) since Wed 2021-04-07 17:49:30 EDT; 14h ago
  Process: 3162 ExecStop=/local-path/stop.service-c (code=exited, status=0/SUCCESS)
  Process: 1319 ExecStart=/local-path/start.service-c (code=exited, status=0/SUCCESS)
 Main PID: 1478 (code=exited, status=0/SUCCESS)

/local-path是系统上本地目录的模糊版本。

由于这是一个持续存在的问题,因此在上次重新启动后,我使用“停止”包装器脚本来记录进程父树(使用pstree -a -A -l -p -s $$));该日志文件显示:

04/07/2021 17:49:19  stop.service-c:  
systemd,1 --switched-root --system --deserialize 22
  `-stop.service-c,3162 /local-path/stop.service-c
      `-pstree,3178 -a -A -l -p -s 3162

...其中 PID 3162 对应于 systemd 对停止脚本的调用。在我看来,systemd 正在调用该服务的 ExecStop。

systemd 在完成启动后大约两秒停止该服务;代理的日志文件具有以下时间戳:

04/07/2021 17:49:12  start.service-c:  Starting agent
04/07/2021 17:49:17  start.service-c:  startup success
04/07/2021 17:49:19  stop.service-c:  Executing from /agent/home as user

... 结束于 ...

04/07/2021 17:49:30  stop.service-c:  Finished with RC=0

...对应于 systemd 的“死亡”时间戳 17:49:30。

“Restart=on-failure”指令将重新启动服务,但 systemd 告诉我服务已成功启动:

Apr 07 17:49:10 hostname systemd[1]: Starting service c...
Apr 07 17:49:17 hostname systemd[1]: Started service c.

由于该服务干净地启动,并且 systemd 没有尝试重新开始该服务,我认为重新启动参数不会发挥作用。

也许有趣的是,journalctl 没有相应的“停止服务...”日志(当我手动停止服务时),但有证据表明 systemd 调用了 ExecStop。

我目前运行的是 systemd 219。

答案1

我想知道为什么 systemd 认为它应该停止服务单元。我可以配置或启用什么来确定 systemd 采取“停止”操作的原因?

为了查看服务的实时状态,您可以:

  • 使用systemd-cgls -l <service-cgroup-path>命令:您将看到当时所有服务的进程。可以使用命令检索服务的 cgroup 路径systemctl show -p ControlGroup <service-name>。在更新的版本中systemd(不在 v219 中),您还可以使用方便的-u <service-name>选项systemd-cgls代替服务的 cgroup 路径
  • 要获得详细的见解,您可以使用非常详细的systemctl show <service-name>命令:这将提供有关 已知的服务状态的大量信息systemd,并且从该信息中您可以尝试更详细地推断正在发生的情况

ExecStop要调查“可疑停止”情况,将这些命令添加为命令是正确的。您只需添加它们即可一开始您自己的stop.service-c脚本(如果它确实是一个脚本)。

或者您也可以将它们添加ExecStop为自己的附加命令你的stop.service-c命令,如:

[Service]
Type=forking
ExecStart=/local-path/start.service-c
ExecStop=-/bin/sh -c 'systemd-cgls -l -u %n && systemctl show %n'
ExecStop=/local-path/stop.service-c
Restart=on-failure

请注意,当说明符出现在带引号的字符串中时,也%n可以正确处理说明符。systemd

或者您也可以:

[Service]
Type=forking
ExecStart=/local-path/start.service-c
ExecStop=-/usr/bin/systemd-cgls -l -u %n
ExecStop=-/bin/systemctl show %n
ExecStop=/local-path/stop.service-c
Restart=on-failure

另请注意-命令的前缀,以便忽略它们的退出状态,以防它们因难以理解的原因失败。

当然,您也可以将它们用作ExecStartPost命令,以便在服务被认为“成功启动”后立即思考活动状态systemd。 (再次忽略它们的退出状态,或者systemd如果它们失败,将拆除整个服务)。

对于systemd-cgls的输出 run asExecStop命令,您应该注意该MainPID进程当时是否仍然显示:如果确实显示,则证明ExecStop确实已按照systemd您的建议自主执行。否则(如果MainPID过程是不是出现在systemd-cgls“停止”时间的输出中)这意味着ExecStop已经运行因此进程MainPID自行退出。(更多推理请参见下文)。您可能还需要注意服务进程的 PID 号以及(现已失效)ExecStart命令的 PID 号,以尝试推断fork(2)自服务启动以来一直在发生的情况,因为这非常重要与服务相关type=forking,以评估其是否表现良好。(更多推理请参见下文)

关于systemctl show作为命令运行的输出ExecStop,我想说最需要注意的相关属性在你的具体情况下是:

  • MainPID:读取0服务的主进程是否已自行退出,否则读取服务的主进程的 PID(如果它仍然存在,因此确实被停止)systemd
  • ExecMainExitTimestamp:如果服务的主进程已自行退出,则以格式读取退出时间date,否则如果进程仍然存在,则根本不读取,因此确实被停止systemd
  • ExecMainExitTimestampMonotonic:如上所述,但读取 Linux 的单调时钟并读取0进程是否仍然存在
  • ExecMainCode:这对应于1code=中的字符串,只是它报告符号的十进制值,而不是它们翻译成英文单词:根据 Linux 的当前符号值(从 开始),该字段读取进程是否仍然存在因此确实将被 停止,否则读取进程是否已经自行 -ed,如果它已被-ed(在这个用例中显然systemctl statusCLD_*CLD_*enum1ExecMainCode0systemd1_exit(2)2kill(2)不是systemd) 等

笔记然而上述字段确实不是对应于服务的当前的说明systemd在服务启动时是否无法检测到服务的主进程。(请参阅下面的解释)。他们宁愿对应于最近的运行systemd 曾是能够完全完成检测。


进一步的见解

在你的推理中,我可以看到两个值得额外澄清的关键点:

type=forking服务

type=forking服务对于 来说特别棘手systemd,尤其是在使用时GuessMainPID=yes(默认值,因此您当前正在为代理使用什么)。对于这些服务类型,命令本身ExecStart应该是fork(2)一次然后退出,而其分叉进程预计会像MainPID服务一样长期存在并繁荣发展。别的:

  1. 如果这样的分叉进程再次分叉然后退出,将充当实际服务的责任委托给它自己的“第二个”分叉进程,则GuessMainPID只会失去跟踪并systemd简单地认为服务已定期且自发地完成,因此完成清洁一切的职责(即运行ExecStop等)但是没有记录Stopping service...消息,因为就其而言systemd,它仅对服务的故意退出做出反应
  2. 如果改为ExecStart 原来的在退出之前处理fork(2)两次(或更多次),然后GuessMainPID投降并systemd克制在退出时不拆除所有内容ExecStart 原来的进程最终退出。这是一个更好的情况,因为服务的实际进程仍然存在,但它还不理想,因为这样systemd也不会完全跟踪事件,从而至少导致日志日志不一致/不完整。

ExecStop执行

命令ExecStop已运行MainPID进程自行成功退出时,只要主进程也已退出开始成功(这是你手头的情况)。我知道这似乎违反直觉,但这只是正常行为systemd:它只是认为服务的ExecStop命令是在该服务之后进行清理的首选方式,然后先发送 SIGTERM(默认情况下,请参阅systemd.kill(5)),然后可能发送 SIGKILL。

它并没有在systemd.service(5)联机帮助页的任何地方如此明确地说明这一点,但可以通过一些文档来推断,特别是那些与命令可用的环境变量有关的文档Exec*。请参阅$SERVICE_RESULT$EXIT_CODE$EXIT_STATUS变量可以取什么值,它们具有什么语义意义,以及它们精确地可供命令使用的ExecStop事实ExecStopPost

除了非明确的(或个人解释的)文档之外,让我们看看执行该行为的来源。取自 v219,这里service_sigchld_event()调用的是service_enter_running()在涉及已知处于“运行”状态的子级的事件上,然后后一个函数调用service_enter_stop()在所有情况下“停止”操作,除非RemainAfterExit=yestype=dbus检测到服务的主进程(见type=forking上面的解释)或者对照组不健康。

至于为什么人们systemd决定这样做,我不知道,因为我不是systemd开发人员,但我可以看到这种行为的有用性,以便为服务的所有仍然存在但“未知”的进程提供获得通知的机会systemd在关闭整个控制组时,按照最后的手段得到严厉的 SIGTERM 和 SIGKILL 之前,以尽可能最好的方式终止它们。此措施对于服务特别有用,type=forking因为这些服务是最难systemd正确追踪的,如type=的段落中所述systemd.service(5),并且因为systemd在退出之前未正常关闭的遗留/惰性/实施不良的服务后尝试进行清理。

华泰


1.code=后面跟着一个代表进程“退出原因”的词:无论是 itexited还是 has waskilledtrappedEven dumped;在实践中:字面意思是翻译各种CLD_*有效值的词siginfo_t.si_code中描述的字段sigaction(2)

相关内容