通过发送SIGTERM停止正在运行的Docker容器

时间:2016-05-29 22:48:43

标签: docker

我有一个非常简单的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 killdocker stop之类的命令。

这是预期的行为吗?我已经问in the forums和IRC并没有得到答案。

3 个答案:

答案 0 :(得分:10)

默认情况下SIGTERM docker run传播到Docker守护程序,但除非您专门处理Docker运行的主进程中的信号,否则它不会生效。

容器中run的第一个进程在该容器上下文中将具有PID 1。这被linux内核视为特殊进程。除非进程为该信号安装了处理程序,否则将发送信号。将信号转发到其他子进程也是PID 1的工作。

docker run和其他命令是docker守护程序托管的Remote API的API客户端。 docker守护程序作为单独的进程运行,并且是您在容器上下文中运行的命令的父级。这意味着在run和守护进程之间没有以标准的unix方式直接发送信号。

docker rundocker attach命令有一个--sig-proxy标志,默认信号代理到true。你可以根据需要关闭它。

docker exec does not proxy signals

Dockerfile中,在指定CMDENTRYPOINT默认值时请务必使用“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容器中sleepsleep进程,我可以发送一个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

TL; DR

任何使用泊坞窗的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 ...