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

通过发送SIGTERM停止正在运行的Docker容器,docker,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.D

我有一个非常简单的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
我希望,像大多数程序一样,我可以向运行
docker run
的进程发送SIGTERM,这将导致容器停止运行。我发现发送SIGTERM没有效果,相反,我需要使用类似于
docker kill
docker stop
的命令


这是故意的行为吗?我在IRC上问了又问,但没有得到回答。

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

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

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

docker run
docker attach
命令有一个
--sig proxy
标志,默认信号代理为
true
。如果你想的话,可以把它关掉

docker exec

Dockerfile
中,如果不希望
sh
成为PID 1进程(),请在指定时小心使用“exec表单”,并使用默认值:

信号处理示例 在此处使用示例Go代码:

运行不处理信号的常规进程和捕获信号并将其置于后台的Go守护进程。我使用的是
sleep
,因为它很简单,而且不处理“守护进程”信号

使用具有“树”视图的
ps
工具,您可以看到两个不同的流程树。一个用于
sshd
下的
docker运行
过程。另一个用于实际容器进程,在
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主机进程的详细信息:

$ 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
$docker exec 30b6652cfc04睡眠600
^C
$docker exec 30b6652cfc04 ps-ef
PID用户时间命令

1根0:00睡眠6000因此有两个因素在起作用:

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终止进程,并关闭正在运行的容器。

关于此问题的非常全面的描述和解决方案可以在这里找到:

在我的例子中,我的应用程序希望收到SIGTERM信号,但没有收到它,因为进程是由一个bash脚本启动的,该脚本以以下形式从dockerfile调用:ENTRYPOINT[“/path/to/script.sh”]

所以脚本没有将SIGTERM传播到应用程序。 解决方案是使用脚本中的exec运行启动应用程序的命令:
e、 g.exec java-jar…

老实说,我不是100%确定,尽管我确实记得,向PID 1的进程发送SIGTERM实际上并没有像您预期的那样杀死它,因为您需要显式捕获信号并对其采取行动(更多信息,请参阅dumb init背后的原理)。尝试通过
sh
(将入口点指定为字符串,而不是使用数组语法)或使用
dumb init
启动进程,看看是否有帮助。我不认为OP是在谈论向容器中的PID1发送SIGTERM。我想他说的是把西格特姆送到主机上的pidof docker run?@JHarris-我也这么认为;我只是不知道发送
docker run
信号到底是什么,暗中试探。我刚刚看了一下,似乎默认情况下,
docker run
进程代理所有信号(对于
sig proxy
)。@JHarris这是正确的,将SIGTERM发送到主机上docker run的pid。我没有想到Dockerfile中的shell表单而不是exec表单!由于人们对另一个A投了更高的票,我加了一条关于它的说明。
docker build -t first-app .
docker build .