通过发送SIGTERM停止正在运行的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 kill或者docker stop这样的命令。

这是预期的行为? 我在论坛和IRC上问过,没有得到答案。

缺省情况下, docker run命令通过docker run命令将一个SIGTERM传播给Docker守护程序,除非您专门处理由Docker运行的主进程中的信号,否则它不会生效。

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

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

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

docker exec 不代理信号 。

Dockerfile ,在指定CMDENTRYPOINT默认值时要小心使用“execforms”,所以sh不会成为PID 1进程( Kevin Burke ):

 CMD ["executable", "param1", "param2"] 

信号处理的例子

在这里使用示例Go代码: https : //gobyexample.com/signals

运行一个不处理信号的常规进程和一个陷阱信号的Go守护进程,并把它们放在后台。 我使用sleep因为它很容易,不处理“守护进程”的信号。

 $ docker run busybox sleep 6000 & $ docker run gosignal & 

使用具有“树”视图的ps工具,可以看到两个不同的进程树。 一个用于sshd下的docker run进程。 另一个用于docker daemon进程下的实际容器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

如果我在正在运行的sleep容器中docker exec一个新的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 

TL; DR

你用docker run任何进程都必须处理信号本身。

所以这里有两个因素:

1)如果你为一个入口点指定一个string,像这样:

 ENTRYPOINT /go/bin/myapp 

Docker使用/bin/sh -c 'command'运行脚本。 此中间脚本获取SIGTERM,但不会将其发送到正在运行的服务器应用程序。

要避免中间层,请将入口点指定为string数组。

 ENTRYPOINT ["/go/bin/myapp"] 

2)我build立的应用程序,我试图用以下string运行:

 docker build -t first-app . 

这标记容器名称first-app。 不幸的是,当我试图重build/重新运行我跑的容器:

 docker build . 

没有覆盖标签,所以我的更改没有被应用。

一旦我做了这两件事情,我可以用ctrl + c杀死进程,并打开正在运行的容器。