我们有一个启动第三方代理的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 status
CLD_*
CLD_*
enum
1
ExecMainCode
0
systemd
1
_exit(2)
2
kill(2)
不是由systemd
) 等
笔记然而上述字段确实不是对应于服务的当前的说明systemd
在服务启动时是否无法检测到服务的主进程。(请参阅下面的解释)。他们宁愿对应于最近的运行systemd
曾是能够完全完成检测。
进一步的见解
在你的推理中,我可以看到两个值得额外澄清的关键点:
type=forking
服务
type=forking
服务对于 来说特别棘手systemd
,尤其是在使用时GuessMainPID=yes
(默认值,因此您当前正在为代理使用什么)。对于这些服务类型,命令本身ExecStart
应该是fork(2)
一次然后退出,而其分叉进程预计会像MainPID
服务一样长期存在并繁荣发展。别的:
- 如果这样的分叉进程再次分叉然后退出,将充当实际服务的责任委托给它自己的“第二个”分叉进程,则
GuessMainPID
只会失去跟踪并systemd
简单地认为服务已定期且自发地完成,因此完成清洁一切的职责(即运行ExecStop
等)但是没有记录Stopping service...
消息,因为就其而言systemd
,它仅对服务的故意退出做出反应 - 如果改为
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=yes
未type=dbus
检测到服务的主进程(见type=forking
上面的解释)或者对照组不健康。
至于为什么人们systemd
决定这样做,我不知道,因为我不是systemd
开发人员,但我可以看到这种行为的有用性,以便为服务的所有仍然存在但“未知”的进程提供获得通知的机会systemd
在关闭整个控制组时,按照最后的手段得到严厉的 SIGTERM 和 SIGKILL 之前,以尽可能最好的方式终止它们。此措施对于服务特别有用,type=forking
因为这些服务是最难systemd
正确追踪的,如type=
的段落中所述systemd.service(5)
,并且因为systemd
在退出之前未正常关闭的遗留/惰性/实施不良的服务后尝试进行清理。
华泰
1.code=
后面跟着一个代表进程“退出原因”的词:无论是 itexited
还是 has waskilled
或trapped
Even dumped
;在实践中:字面意思是翻译各种CLD_*
有效值的词siginfo_t.si_code
中描述的字段sigaction(2)