쉘 스크립트로 작성된 놀라운 서비스는 다음과 같습니다.
$ 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에서 이와 같은 문제를 겪고 있는 사람들을 볼 수 있지만 여기서는 그것이 어떻게 관련될 수 있는지 알 수 없습니다.
편집: 두 개의 간단한 C 프로그램을 사용하여 방정식에서 쉘 스크립트를 가져온 후에도 정확히 동일한 동작을 볼 수 있습니다. systemd를 대신하고 파이프를 통해 출력을 수집하는 간단한 상위 프로세스에서는 이 동작을 볼 수 없습니다. 이는 뭔가 펑키한 일을 하고 있는 시스템이 있다는 것을 강하게 나타냅니다. 보다:https://github.com/tomwhoiscontrary/child-stdout-demo
편집 2: 루트가 있는 관찰 동료는 (a) 하위 프로세스 출력이 다음과 같이 보고됩니다.~이다저널에서는 서비스와 관련이 없으며 (b) 그는 이 동작을사용자서비스; 만약 그가 설정한다면체계동일한 코드를 사용하는 서비스에는 하위 프로세스 출력이 연결됩니다! 확실히 이것은 시스템 버그입니까?
답변1
편집 2: 루트가 있는 관찰 동료는 (a) 하위 프로세스 출력이 저널에 있고 서비스와 연결되어 있지 않으며 (b) 사용자 서비스에서만 이 동작을 본다고 보고합니다. 동일한 코드로 시스템 서비스를 설정하면 하위 프로세스 출력이 이와 연결됩니다! 확실히 이것은 시스템 버그입니까?
이는 오랫동안 알려진 문제입니다. 문제는 커널이 소켓 클라이언트를 cgroup과 연결하는 데 충분한 수단을 제공하지 않는다는 것입니다(예를 들어 클라이언트의 PID를 검색하는 기능과 달리). 따라서 저널이 메시지를 받을 때마다 보낸 사람의 PID만 알지만 이를 확인해야 합니다.비동기적으로에서 단위 이름을 찾아보세요 /proc/<pid>/cgroup
. 프로세스의 수명이 매우 짧은 경우(예: 서브셸) 저널이 깨어나기도 전에 프로세스가 종료될 가능성이 매우 높으며 메시지가 처리될 때 출력을 서비스와 연결하는 데 필요한 정보가 필요합니다. 더 이상 사용할 수 없습니다.
세부 사항이 약간 흐릿하지만 제가 기억하는 바에 따르면 최근 시스템 버전에는 저널에 대한 stdout "파이프"(실제로는 소켓 쌍)가 권한 있는 프로세스에 의해 설정된 경우에만 작동하는 부분적인 해결 방법이 있습니다. 귀하의 "사용자" 서비스는 귀하와 동일한 권한만 가진 다른 시스템 인스턴스에 의해 설정됩니다.
답변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