Почему 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 обычно захватывает вывод из подпроцессов здесь. Это что-то конкретное, что делает оболочка, взаимодействующая с этим?

Погуглив, я вижу, что у людей возникают подобные проблемы с Python, где виновата буферизация, но я не понимаю, какое отношение это может иметь к данному случаю.

EDIT: Я вижу точно такое же поведение после того, как убрал скрипт оболочки из уравнения, используя две простые программы на C. Я не вижу такого поведения с простым родительским процессом, заменяющим systemd и собирающим вывод через канал. Это явно указывает на то, что systemd делает что-то странное. Смотрите:https://github.com/tomwhoiscontrary/child-stdout-demo

EDIT 2: Наблюдательный коллега, имеющий root, сообщает, что (a) вывод подпроцессаявляетсяв журнале это просто не связано с услугой, и (б) он видит это поведение только спользовательуслуга; если он устанавливаетсистемаслужба с тем же кодом, вывод подпроцесса связан с ней! Это, конечно, системный баг?

решение1

EDIT 2: Наблюдательный коллега, имеющий root, сообщает, что (a) вывод подпроцесса находится в журнале, просто он не связан со службой, и (b) он видит такое поведение только с пользовательской службой; если он настраивает системную службу с тем же кодом, вывод подпроцесса связан с ней! Это наверняка ошибка systemd?

Это известная, давно существующая проблема; проблема в том, что ядро ​​не предоставляет достаточных средств для связывания сокет-клиента с cgroup (в отличие, например, от возможности извлечения PID клиента). Поэтому всякий раз, когда journald получает сообщение, он знает только PID отправителя, но долженасинхроннонайдите имя его модуля в /proc/<pid>/cgroup. Если процесс очень кратковременный (например, подоболочка), вполне может быть, что он завершится еще до того, как journald будет разбужен, и к тому времени, когда его сообщение будет обработано, информация, необходимая для связывания его вывода со службой, уже не будет доступна.

Я немного не в курсе деталей, но, насколько я помню, в последних версиях systemd есть частичное решение, которое работает только в том случае, если «канал» stdout к journald (который на самом деле является парой сокетов) был настроен привилегированным процессом, тогда как ваши «пользовательские» службы настроены другим экземпляром 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

Связанный контент