systemd がシェル スクリプトで記述されたユーザー サービスのサブプロセスからの出力をキャプチャしないのはなぜですか?

systemd がシェル スクリプトで記述されたユーザー サービスのサブプロセスからの出力をキャプチャしないのはなぜですか?

以下はシェル スクリプトで記述された私の素晴らしいサービスです。

$ 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 を使用してこのサービスを実行すると、ログ出力にサブシェルとサブプロセスによって生成された行が欠落します。

$ 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 は通常ここでサブプロセスからの出力をキャプチャするようです。これは、これと相互作用しているシェルが行っている特定の操作なのでしょうか?

Google で検索してみると、Python でバッファリングが原因でこのような問題を抱えている人がいるようですが、それがここでどのように関係するのかわかりません。

編集: シェル スクリプトを取り除いて、2 つの単純な C プログラムを使用した後も、まったく同じ動作が見られます。単純な親プロセスが systemd の代わりになり、パイプ経由で出力を収集すると、この動作は見られません。これは、systemd が何かおかしなことをしていることを強く示しています。参照:https://github.com/tomwhoiscontrary/child-stdout-demo

編集2: ルート権限を持つ観察力のある同僚が、(a)サブプロセスの出力がジャーナルでは、それはサービスと関連付けられていないだけであり、(b)彼はこの行動をユーザーサービス;彼がシステム同じコードを持つサービスでは、サブプロセスの出力がそれに関連付けられています。これは systemd のバグでしょうか?

答え1

編集 2: ルート権限を持つ注意深い同僚が、(a) サブプロセスの出力はジャーナルにあるが、サービスに関連付けられていない、(b) この動作はユーザー サービスでのみ確認されている、同じコードでシステム サービスを設定すると、サブプロセスの出力がそれに関連付けられる、と報告しています。これは systemd のバグでしょうか?

これは既知の長年の問題です。問題は、カーネルがソケットクライアントをcgroupに関連付ける十分な手段を提供していないことです(たとえば、クライアントのPIDを取得する機能とは異なります)。そのため、journaldがメッセージを受信するたびに、送信者のPIDしかわかりませんが、非同期的にからそのユニット名を検索します/proc/<pid>/cgroup。プロセスの存続期間が非常に短い場合 (サブシェルなど)、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

関連情報