通过发送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
,在指定CMD
和ENTRYPOINT
默认值时要小心使用“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杀死进程,并打开正在运行的容器。