当主进程从 systemd 启动时无法分离 child 进程
Can't detach child process when main process is started from systemd
我想生成 long-running child 个在主进程 restarts/dies 时存活的进程。当来自终端的 运行 时,这工作正常:
$ cat exectest.go
package main
import (
"log"
"os"
"os/exec"
"syscall"
"time"
)
func main() {
if len(os.Args) == 2 && os.Args[1] == "child" {
for {
time.Sleep(time.Second)
}
} else {
cmd := exec.Command(os.Args[0], "child")
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
log.Printf("child exited: %v", cmd.Run())
}
}
$ go build
$ ./exectest
^Z
[1]+ Stopped ./exectest
$ bg
[1]+ ./exectest &
$ ps -ef | grep exectest | grep -v grep | grep -v vim
snowm 7914 5650 0 23:44 pts/7 00:00:00 ./exectest
snowm 7916 7914 0 23:44 ? 00:00:00 ./exectest child
$ kill -INT 7914 # kill parent process
[1]+ Exit 2 ./exectest
$ ps -ef | grep exectest | grep -v grep | grep -v vim
snowm 7916 1 0 23:44 ? 00:00:00 ./exectest child
请注意,child 进程在 parent 进程被终止后仍然存在。但是,如果我像这样从 systemd 启动主进程...
[snowm@localhost exectest]$ cat /etc/systemd/system/exectest.service
[Unit]
Description=ExecTest
[Service]
Type=simple
ExecStart=/home/snowm/src/exectest/exectest
User=snowm
[Install]
WantedBy=multi-user.target
$ sudo systemctl enable exectest
ln -s '/etc/systemd/system/exectest.service' '/etc/systemd/system/multi-user.target.wants/exectest.service'
$ sudo systemctl start exectest
...然后 child 在我终止主进程时也会死掉:
$ ps -ef | grep exectest | grep -v grep | grep -v vim
snowm 8132 1 0 23:55 ? 00:00:00 /home/snowm/src/exectest/exectest
snowm 8134 8132 0 23:55 ? 00:00:00 /home/snowm/src/exectest/exectest child
$ kill -INT 8132
$ ps -ef | grep exectest | grep -v grep | grep -v vim
$
如何让 child 存活下来?
运行 go version go1.4.2 linux/amd64 under CentOS Linux release 7.1.1503 (Core).
解决方法是添加
KillMode=process
到服务区。默认值为 control-group
,这意味着 systemd 会清理所有子进程。
KillMode= Specifies how processes of this unit shall be killed. One of
control-group, process, mixed, none.
If set to control-group, all remaining processes in the control group
of this unit will be killed on unit stop (for services: after the stop
command is executed, as configured with ExecStop=). If set to process,
only the main process itself is killed. If set to mixed, the SIGTERM
signal (see below) is sent to the main process while the subsequent
SIGKILL signal (see below) is sent to all remaining processes of the
unit's control group. If set to none, no process is killed. In this
case, only the stop command will be executed on unit stop, but no
process be killed otherwise. Processes remaining alive after stop are
left in their control group and the control group continues to exist
after stop unless it is empty.
如果您(像我一样)由于某种原因无法更改服务的 KillMode
,您可以尝试 at
命令(参见 man)。
您可以将命令安排在 运行 提前 1 分钟。看一个例子:
# this will remove all .tmp files from "/path/" in 1 minute ahead (this task will run once)
echo rm /path/*.tmp | at now + 1 minute
据我所知,解决此问题的唯一可行方法是使用不同的 cgroup 启动子进程。您可以使用 systemd-运行 命令和 --slice 参数来做到这一点。
systemd-run --user --scope --slice=app-firefox firefox
更改 KillMode 还意味着如果您的主进程崩溃,如果任何子进程仍在 运行ning,systemd 将不会重新启动它。
我想生成 long-running child 个在主进程 restarts/dies 时存活的进程。当来自终端的 运行 时,这工作正常:
$ cat exectest.go
package main
import (
"log"
"os"
"os/exec"
"syscall"
"time"
)
func main() {
if len(os.Args) == 2 && os.Args[1] == "child" {
for {
time.Sleep(time.Second)
}
} else {
cmd := exec.Command(os.Args[0], "child")
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
log.Printf("child exited: %v", cmd.Run())
}
}
$ go build
$ ./exectest
^Z
[1]+ Stopped ./exectest
$ bg
[1]+ ./exectest &
$ ps -ef | grep exectest | grep -v grep | grep -v vim
snowm 7914 5650 0 23:44 pts/7 00:00:00 ./exectest
snowm 7916 7914 0 23:44 ? 00:00:00 ./exectest child
$ kill -INT 7914 # kill parent process
[1]+ Exit 2 ./exectest
$ ps -ef | grep exectest | grep -v grep | grep -v vim
snowm 7916 1 0 23:44 ? 00:00:00 ./exectest child
请注意,child 进程在 parent 进程被终止后仍然存在。但是,如果我像这样从 systemd 启动主进程...
[snowm@localhost exectest]$ cat /etc/systemd/system/exectest.service
[Unit]
Description=ExecTest
[Service]
Type=simple
ExecStart=/home/snowm/src/exectest/exectest
User=snowm
[Install]
WantedBy=multi-user.target
$ sudo systemctl enable exectest
ln -s '/etc/systemd/system/exectest.service' '/etc/systemd/system/multi-user.target.wants/exectest.service'
$ sudo systemctl start exectest
...然后 child 在我终止主进程时也会死掉:
$ ps -ef | grep exectest | grep -v grep | grep -v vim
snowm 8132 1 0 23:55 ? 00:00:00 /home/snowm/src/exectest/exectest
snowm 8134 8132 0 23:55 ? 00:00:00 /home/snowm/src/exectest/exectest child
$ kill -INT 8132
$ ps -ef | grep exectest | grep -v grep | grep -v vim
$
如何让 child 存活下来?
运行 go version go1.4.2 linux/amd64 under CentOS Linux release 7.1.1503 (Core).
解决方法是添加
KillMode=process
到服务区。默认值为 control-group
,这意味着 systemd 会清理所有子进程。
KillMode= Specifies how processes of this unit shall be killed. One of control-group, process, mixed, none.
If set to control-group, all remaining processes in the control group of this unit will be killed on unit stop (for services: after the stop command is executed, as configured with ExecStop=). If set to process, only the main process itself is killed. If set to mixed, the SIGTERM signal (see below) is sent to the main process while the subsequent SIGKILL signal (see below) is sent to all remaining processes of the unit's control group. If set to none, no process is killed. In this case, only the stop command will be executed on unit stop, but no process be killed otherwise. Processes remaining alive after stop are left in their control group and the control group continues to exist after stop unless it is empty.
如果您(像我一样)由于某种原因无法更改服务的 KillMode
,您可以尝试 at
命令(参见 man)。
您可以将命令安排在 运行 提前 1 分钟。看一个例子:
# this will remove all .tmp files from "/path/" in 1 minute ahead (this task will run once)
echo rm /path/*.tmp | at now + 1 minute
据我所知,解决此问题的唯一可行方法是使用不同的 cgroup 启动子进程。您可以使用 systemd-运行 命令和 --slice 参数来做到这一点。
systemd-run --user --scope --slice=app-firefox firefox
更改 KillMode 还意味着如果您的主进程崩溃,如果任何子进程仍在 运行ning,systemd 将不会重新启动它。