[英]Why systemd process running in docker container with PID 1 not forwarding SIGTERM to child processes on docker stop
[英]Stop a running Docker container by sending SIGTERM
我有一個非常簡單的 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
這樣的命令。
默認情況下, SIGTERM
由docker run
命令傳播到 Docker 守護程序,但除非在 Docker 正在運行的主進程中專門處理該信號,否則它不會生效。
您在容器中run
的第一個進程將在該容器上下文中具有 PID 1。 這被 linux 內核視為一個特殊的進程。 除非進程為該信號安裝了處理程序,否則它不會被發送信號。 將信號轉發到其他子進程也是 PID 1 的工作。
docker run
和其他命令是由 docker 守護進程托管的遠程 API 的API 客戶端。 docker 守護進程作為一個單獨的進程運行,並且是您在容器上下文中運行的命令的父進程。 這意味着在run
和守護進程之間沒有以標准的 unix 方式直接發送信號。
--sig-proxy
docker run
和--sig-proxy
docker attach
命令有一個--sig-proxy
標志,默認信號代理為true
。 如果需要,您可以關閉此功能。
docker exec
不代理信號。
在Dockerfile
,如果您不希望sh
成為 PID 1 進程 ( Kevin Burke ),請在指定CMD
和ENTRYPOINT
默認值時小心使用“exec 形式”:
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
。
$ 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
具有陷阱處理程序的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
您使用 docker run
任何進程都必須自己處理信號。
或使用--init
標志將tini
作為 PID 1 運行
所以這里有兩個因素在起作用:
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 終止進程,並關閉正在運行的容器。
可以在此處找到此問題的非常全面的描述和解決方案: https : //vsupalov.com/docker-compose-stop-slow
在我的情況下,我的應用程序希望收到 SIGTERM 信號以進行正常關閉,但沒有收到它,因為該進程由 bash 腳本啟動,該腳本從 dockerfile 以這種形式調用:ENTRYPOINT ["/path/to/script.sh"]
所以腳本沒有將 SIGTERM 傳播到應用程序。 解決方案是使用腳本中的 exec 運行啟動應用程序的命令:例如 exec java -jar ...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.