![为什么 systemd 不捕获用 shell 脚本编写的用户服务子进程的输出?](https://linux22.com/image/789196/%E4%B8%BA%E4%BB%80%E4%B9%88%20systemd%20%E4%B8%8D%E6%8D%95%E8%8E%B7%E7%94%A8%20shell%20%E8%84%9A%E6%9C%AC%E7%BC%96%E5%86%99%E7%9A%84%E7%94%A8%E6%88%B7%E6%9C%8D%E5%8A%A1%E5%AD%90%E8%BF%9B%E7%A8%8B%E7%9A%84%E8%BE%93%E5%87%BA%EF%BC%9F.png)
下面是我用 shell 脚本编写的出色服务:
$ cat ~/junk/demoapp
#! /bin/bash -eu
while true
do
echo "in shell"
( echo "in subshell" )
/usr/bin/echo "in subprocess"
sleep 1
done
它会产生一些重复的输出:
$ ~/junk/demoapp
in shell
in subshell
in subprocess
in shell
in subshell
in subprocess
in shell
in subshell
in subprocess
in shell
in subshell
in subprocess
^C
以下是它的用户服务配置:
$ cat ~/.config/systemd/user/demoapp.service
[Unit]
Description=Demo App
[Service]
Type=exec
ExecStart=/home/tomanderson/junk/demoapp
但是当我使用 systemd 239 运行此服务时,记录的输出缺少子 shell 和子进程生成的行:
$ systemctl --user daemon-reload
$ systemctl --user start demoapp
$ journalctl --user --unit demoapp
Sep 12 18:53:27 myhost systemd[539847]: Started Demo App.
Sep 12 18:53:27 myhost demoapp[559387]: in shell
Sep 12 18:53:28 myhost demoapp[559387]: in shell
Sep 12 18:53:29 myhost demoapp[559387]: in shell
Sep 12 18:53:30 myhost demoapp[559387]: in shell
Sep 12 18:53:31 myhost demoapp[559387]: in shell
Sep 12 18:53:32 myhost demoapp[559387]: in shell
Sep 12 18:53:33 myhost demoapp[559387]: in shell
Sep 12 18:53:34 myhost demoapp[559387]: in shell
Sep 12 18:53:35 myhost demoapp[559387]: in shell
知道为什么吗?从阅读来看,似乎 systemd 通常会在此处捕获子进程的输出。这是 shell 正在执行的与此交互的特定操作吗?
通过 Google 搜索,我发现人们在使用 Python 时遇到了类似的问题,其中缓冲是罪魁祸首,但我不明白这与这里有什么关系。
编辑:使用两个简单的 C 程序,在将 shell 脚本从方程式中移除后,我看到了完全相同的行为。我没有看到这种行为,因为一个简单的父进程取代了 systemd 并通过管道收集输出。这强烈表明是 systemd 做了一些奇怪的事情。参见:https://github.com/tomwhoiscontrary/child-stdout-demo
编辑 2:一位有根权限的观察同事报告说(a)子进程输出是在日志中,它只是与服务无关,并且(b)他只在用户服务;如果他设立了一个系统服务具有相同的代码,子进程输出与其相关联!这肯定是 systemd 错误?
答案1
编辑 2:一位有 root 权限的细心同事报告说:(a) 子进程输出在日志中,只是与服务无关,并且 (b) 他只在用户服务中看到这种行为;如果他用相同的代码设置系统服务,子进程输出就会与之关联!这肯定是 systemd 的一个错误吧?
这是一个已知的、长期存在的问题;问题在于内核没有提供足够的方法来将套接字客户端与 cgroup 关联起来(例如,检索客户端的 PID 的能力)。因此,每当 journald 收到一条消息时,它只知道发送者的 PID,但必须异步地从 查找其单元名称/proc/<pid>/cgroup
。如果该进程的寿命很短(例如子 shell),则很可能在 journald 被唤醒之前就退出了 - 而当其消息被处理时,将其输出与服务关联所需的信息已不再可用。
我对细节有点模糊,但据我记得,最近的 systemd 版本有一个部分解决方法,只有当到 journald 的 stdout“管道”(实际上是一个套接字对)已由特权进程设置时才有效,而您的“用户”服务由另一个仅具有与您相同权限的 systemd 实例设置。
答案2
虽然我还没有研究底层的技术细节,但适用于 Python 的相同解决方案(禁用缓冲)也适用于这种情况。如果我使用这个单元文件...
[Unit]
Description=Demo App
[Service]
Type=exec
ExecStart=/usr/bin/unbuffer %h/bin/demoapp
...然后预期的输出被记录在日志中。
该unbuffer
命令是包的一部分expect
。
此功能通过强制命令作为附加到 pty 设备的交互式进程执行来实现,从而禁用正常缓冲。
如果您手边没有该unbuffer
命令,则可以改用该script
命令:
ExecStart=/usr/bin/script -c %h/bin/demoapp /dev/null