我有一个非常简单的Go应用程序侦听端口8080
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Header().Set("Content-Type", "text-plain")
w.Write([]byte("Hello World!"))
})
log.Fatal(http.ListenAndServe(":8080", http.DefaultServeMux))
我将它安装在Docker容器中并像这样启动它:
FROM golang:alpine
ADD . /go/src/github.com/myuser/myapp
RUN go install github.com/myuser/myapp
ENTRYPOINT ["/go/bin/myapp"]
EXPOSE 8080
然后我使用docker run
:
docker run --publish 8080:8080 first-app
我希望像大多数程序一样,我可以将SIGTERM发送到运行docker run
的进程,这将导致容器停止运行。我发现发送SIGTERM没有效果,而是需要使用docker kill
或docker stop
之类的命令。
这是预期的行为吗?我已经问in the forums和IRC并没有得到答案。
答案 0 :(得分:10)
默认情况下SIGTERM
docker run
传播到Docker守护程序,但除非您专门处理Docker运行的主进程中的信号,否则它不会生效。
容器中run
的第一个进程在该容器上下文中将具有PID 1。这被linux内核视为特殊进程。除非进程为该信号安装了处理程序,否则不将发送信号。将信号转发到其他子进程也是PID 1的工作。
docker run
和其他命令是docker守护程序托管的Remote API的API客户端。 docker守护程序作为单独的进程运行,并且是您在容器上下文中运行的命令的父级。这意味着在run
和守护进程之间没有以标准的unix方式直接发送信号。
docker run
和docker attach
命令有一个--sig-proxy
标志,默认信号代理到true
。你可以根据需要关闭它。
docker exec
does not proxy signals。
在Dockerfile
中,在指定CMD
和ENTRYPOINT
默认值时请务必使用“exec表单”,以便sh
不会成为PID 1进程({{ 3}}):
CMD ["executable", "param1", "param2"]
在此处使用示例Go代码:Kevin Burke
运行一个不处理信号的常规进程和一个捕获信号并将它们放在后台的Go守护进程。我正在使用sleep
因为它很容易并且不处理“守护进程”信号。
$ docker run busybox sleep 6000 &
$ docker run gosignal &
使用具有“树”视图的ps
工具,您可以看到两个不同的流程树。一个用于docker run
下的sshd
流程。另一个用于实际容器流程,位于docker daemon
。
$ pstree -p
init(1)-+-VBoxService(1287)
|-docker(1356)---docker-containe(1369)-+-docker-containe(1511)---gitlab-ci-multi(1520)
| |-docker-containe(4069)---sleep(4078)
| `-docker-containe(4638)---main(4649)
`-sshd(1307)---sshd(1565)---sshd(1567)---sh(1568)-+-docker(4060)
|-docker(4632)
`-pstree(4671)
docker hosts进程的详细信息:
$ ps -ef | grep "docker r\|sleep\|main"
docker 4060 1568 0 02:57 pts/0 00:00:00 docker run busybox sleep 6000
root 4078 4069 0 02:58 ? 00:00:00 sleep 6000
docker 4632 1568 0 03:10 pts/0 00:00:00 docker run gosignal
root 4649 4638 0 03:10 ? 00:00:00 /main
我无法杀死docker run busybox sleep
命令:
$ kill 4060
$ ps -ef | grep 4060
docker 4060 1568 0 02:57 pts/0 00:00:00 docker run busybox sleep 6000
我可以杀死具有陷阱处理程序的docker run gosignal
命令:
$ kill 4632
$
terminated
exiting
[2]+ Done docker run gosignal
docker exec
如果我在已经运行的docker exec
容器中sleep
新sleep
进程,我可以发送一个ctrl-c并中断docker exec
本身,但这并不是'转到实际过程:
$ docker exec 30b6652cfc04 sleep 600
^C
$ docker exec 30b6652cfc04 ps -ef
PID USER TIME COMMAND
1 root 0:00 sleep 6000 <- original
97 root 0:00 sleep 600 <- execed still running
102 root 0:00 ps -ef
任何使用泊坞窗的run
进程必须自行处理信号。
答案 1 :(得分:2)
所以这里有两个因素:
1)如果为入口点指定字符串,请执行以下操作:
ENTRYPOINT /go/bin/myapp
Docker使用/bin/sh -c 'command'
运行脚本。此中间脚本获取SIGTERM,但不会将其发送到正在运行的服务器应用程序。
要避开中间层,请将输入点指定为字符串数组。
ENTRYPOINT ["/go/bin/myapp"]
2)我使用以下字符串构建了我试图运行的应用程序:
docker build -t first-app .
这标记了名为first-app的容器。不幸的是,当我尝试重建/重新运行我运行的容器时:
docker build .
没有覆盖标签,因此我的更改未被应用。
一旦我做了这两件事,我就能用ctrl + c杀死进程,并关闭正在运行的容器。
答案 2 :(得分:0)
有关此问题和解决方案的非常全面的描述,可以在这里找到: https://vsupalov.com/docker-compose-stop-slow
在我的情况下,我的应用程序预期会收到SIGTERM信号,无法正常关闭,因为该进程由bash脚本启动,该脚本从dockerfile中以以下形式调用:ENTRYPOINT [“ /path/to/script.sh “]
因此脚本没有将SIGTERM传播到应用程序。 解决方案是使用脚本中的exec运行启动应用程序的命令: 例如exec java -jar ...