為什麼 systemd 不捕獲以 shell 腳本編寫的使用者服務子程序的輸出?

為什麼 systemd 不捕獲以 shell 腳本編寫的使用者服務子程序的輸出?

這是我用 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 正在執行的與此互動的特定操作嗎?

谷歌搜尋後,我看到有人在使用 Python 時遇到這樣的問題,其中緩衝是罪魁禍首,但我不明白這與這裡有什麼關係。

編輯:使用兩個簡單的 C 程式將 shell 腳本從等式中取出後,我看到完全相同的行為。我沒有看到用簡單的父進程代替 systemd 並透過管道收集輸出的這種行為。這強烈表明 systemd 正在做一些奇怪的事情。看:https://github.com/tomwhoiscontrary/child-stdout-demo

編輯2:一位有根的觀察力同事報告說(a)子流程輸出在日記中,它只是與服務無關,以及 (b) 他只看到此行為使用者服務;如果他設立一個系統具有相同程式碼的服務,子進程輸出與之關聯!這肯定是系統錯誤嗎?

答案1

編輯2:一位具有root權限的觀察力同事報告說(a)子流程輸出在日誌中,它只是與服務無關,並且(b)他只在用戶服務中看到此行為;如果他使用相同的程式碼設定係統服務,則子進程輸出與之關聯!這肯定是系統錯誤嗎?

這是一個眾所周知的、長期存在的問題;問題在於核心沒有提供足夠的方法將套接字客戶端與 cgroup 關聯起來(例如,與檢索客戶端 PID 的能力不同)。因此,每當journald收到訊息時,它只知道發送者的PID,但必須知道非同步地從 中尋找其單位名稱/proc/<pid>/cgroup。如果進程的生命週期非常短(例如子shell),那麼它很可能會在journald被喚醒之前退出——並且在處理其訊息時,將其輸出與服務關聯起來所需的資訊不再被提供。

我對細節有點模糊,但據我記得,最近的 systemd 版本有一個部分解決方法,只有在特權進程設定了通往日誌的標準輸出「管道」(實際上是一個套接字對)時才有效,而您的「使用者」服務是由另一個僅具有與您相同權限的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

相關內容