一只乌龙,关于systemd的

今天一个同事告诉我遇到了一个问题:有一系列的sh和golang写的脚本,相互之间是通过stdout传递结果,在手工调用的时候工作良好,但是在托管给systemd的时候却不工作。怀疑是systemd截取了stdout,导致程序失败。

这个怀疑不无道理,因为在systemd的配置文件中有个很可以的配置项: StandardOutput=journey, 顺便贴出来官网对这个字段的解释:

StandardOutput=

Controls where file descriptor 1 (STDOUT) of the executed processes is connected to. Takes one of inherit, null, tty, journal, syslog, kmsg, journal+console, syslog+console, kmsg+console, file:path, socket or fd:name.

inherit duplicates the file descriptor of standard input for standard output.

null connects standard output to /dev/null, i.e. everything written to it will be lost.

tty connects standard output to a tty (as configured via TTYPath=, see below). If the TTY is used for output only, the executed process will not become the controlling process of the terminal, and will not fail or wait for other processes to release the terminal.

journal connects standard output with the journal which is accessible via journalctl(1). Note that everything that is written to syslog or kmsg (see below) is implicitly stored in the journal as well, the specific two options listed below are hence supersets of this one.

syslog connects standard output to the syslog(3) system syslog service, in addition to the journal. Note that the journal daemon is usually configured to forward everything it receives to syslog anyway, in which case this option is no different from journal.

kmsg connects standard output with the kernel log buffer which is accessible via dmesg(1), in addition to the journal. The journal daemon might be configured to send all logs to kmsg anyway, in which case this option is no different from journal.

journal+console, syslog+console and kmsg+console work in a similar way as the three options above but copy the output to the system console as well.

The file:path option may be used to connect a specific file system object to standard output. The semantics are similar to the same option of StandardInputText=, see above. If standard input and output are directed to the same file path, it is opened only once, for reading as well as writing and duplicated. This is particular useful when the specified path refers to an AF_UNIX socket in the file system, as in that case only a single stream connection is created for both input and output.

socket connects standard output to a socket acquired via socket activation. The semantics are similar to the same option of StandardInput=, see above.

The fd:name option connects standard output to a specific, named file descriptor provided by a socket unit. A name may be specified as part of this option, following a ":" character (e.g. "fd:foobar"). If no name is specified, the name "stdout" is implied (i.e. "fd" is equivalent to "fd:stdout"). At least one socket unit defining the specified name must be provided via the Sockets= option, and the file descriptor name may differ from the name of its containing socket unit. If multiple matches are found, the first one will be used. See FileDescriptorName= in systemd.socket(5) for more details about named descriptors and their ordering.

If the standard output (or error output, see below) of a unit is connected to the journal, syslog or the kernel log buffer, the unit will implicitly gain a dependency of type After= on systemd-journald.socket (also see the "Implicit Dependencies" section above). Also note that in this case stdout (or stderr, see below) will be an AF_UNIX stream socket, and not a pipe or FIFO that can be re-opened. This means when executing shell scripts the construct echo "hello" > /dev/stderr for writing text to stderr will not work. To mitigate this use the construct echo "hello" >&2 instead, which is mostly equivalent and avoids this pitfall.

This setting defaults to the value set with DefaultStandardOutput= in systemd-system.conf(5), which defaults to journal. Note that setting this parameter might result in additional dependencies to be added to the unit (see above).

很详细,但是并没有说会截取stdout,继续google,发现没有什么明确的结果。一个老司机的直觉告诉我,如果google都不知道,这个事情要么很尖端,要么方向就不对,对于这个问题,明显属于后者。怎么办呢,实操呗。

准备两个脚本 a.sh 和 b.sh

a.sh:

#!/bin/sh

b=`./b.sh`
echo $b
echo "in a.sh foo foo foo" >> /root/test/log.bak
echo $b >> /root/test/log.bak
b.sh

#!/bin/sh
echo "in b.sh xxxxxxxxxxx"
OK, 代码很简单,运行结果如下:

[root@myhost test]#./a.sh
b.sh xxxxxxxxxxx
[root@myhost test]# cat log.bak

in a.sh foo foo foo
in b.sh xxxxxxxxxxx
结果看起来确实不错,再试试托管给systemd, 配置文件test-for-me.service如下:

[Unit]
Description=Test

[Service]
WorkingDirectory=/

User=root
Group=root

ExecStart=/root/test/a.sh

Restart=always

StandardOutput=journal
StandardError=inherit

Nice=19
LimitNOFILE=16384

# Disable timeout logic and wait until process is stopped
TimeoutStopSec=25

# SIGTERM signal is used to stop the Java process
KillSignal=SIGTERM

# Send the signal only to the JVM rather than its control group
KillMode=process

# When a JVM receives a SIGTERM signal it exits with code 143
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target
Enable这service:

systemctl enable test-for-me

启动这个service:

systemctl start test-for-me
在看看log.bak

[root@suitetest test]# cat log.bak

in a.sh foo foo foo
in b.sh xxxxxxxxxxx
in a.sh foo foo foo

in a.sh foo foo foo

in a.sh foo foo foo

in a.sh foo foo foo

in a.sh foo foo foo
果然不妙啊,“in b.sh xxxxxx”哪里去了,真的被systemd截取了?

赶紧查查systemd的log:

发现了猫腻:

Jan 15 10:35:31 suitetest a.sh[32038]: /root/test/a.sh: line 3: ./b.sh: No such file or directory

找不到b.sh,怎么会,查看a.sh,不就是在同一路径下的调用吗:

b=`./b.sh`
嗯,老司机在这里估计会心里一动,虎躯一震,难道是路径的问题?

改!

b=`/root/test/b.sh`
呵呵,结果如您所愿:
[root@suitetest test]# cat log.bak

in a.sh foo foo foo
in b.sh xxxxxxxxxxx
in a.sh foo foo foo

in a.sh foo foo foo

in a.sh foo foo foo

in a.sh foo foo foo

in a.sh foo foo foo

in a.sh foo foo foo
in b.sh xxxxxxxxxxx
in a.sh foo foo foo
in b.sh xxxxxxxxxxx
in a.sh foo foo foo
in b.sh xxxxxxxxxxx
in a.sh foo foo foo
in b.sh xxxxxxxxxxx
in a.sh foo foo foo
in b.sh xxxxxxxxxxx

总结:

以为是什么难解的问题,最后是个路径问题,所以呢,如果遇到问题在google查不到,那么先怀疑下解决问题的方向。

随手记下,以供后查。



猜你喜欢

转载自blog.csdn.net/pushme_pli/article/details/79069288