docker stop 或者 docker kill 不能停止容器

docker stop 或者 docker kill 不能停止容器

原因

這幾天在生產環境發現有幾個容器一直不能正常的stop,或者rm 掉,而且查看docker daemon 日誌裏面會出現很多 msg="Container 5054f failed to exit within 10 seconds of<br/>signal 15 - using the force" 這樣的報錯,使用的命令爲journalctl -xe -u docker
然後在短暫的時間內 docker ps查看到的容器還在運行中,過了一會沒有了我們在創建的時候會提示這個容器已經存在(如果建立同樣名稱的容器)

docker stop 主流程

1,docker 通過 containerd 向容器主進程發送
SIGTERM(終止進程)信號後等待一段時間後(默認是10s,可以通過-t 參數來修改),如果從containerd 收到了容器退出消息,那麼容器退出成功。
2,如果超過等待的時間之後,還是沒收到容器退出的消息,那麼docker 將使用docker kill方式試圖終止容器。

但是對於容器來說,init 系統進程並不是必須的,所以當我們停止容器的時候,docker 通過 containerd 向容器Pid 爲 1 的進程發送 SIGTERM信號並不一定會被採納。其實可以分爲以下兩種情況來說明:

1,如果 PID==1 的進程是 init 進程:

那麼 PID==1 會將 SIGTERM 信號轉發給子進程,然後子進程開始關閉,最後容器終止

2,如果PID==1 的進程不是 init 進程:

那麼容器中的應用進程(Dockerfile 中的 ENTRYPOINT 或 CMD 指令指定的應用)的 PId 就是 1,應用進程直接負責響應 SIGTERM 信號。這個時候又分爲兩種情況

1,應用不處理 SIGTERM 信號:

​ 應用沒有監聽 SIGTERM 信號,或者應用中沒有事先處理 SIGTERM 信號的邏輯,應用就不會停止,容器也不會正常終止,會被 調用 docker kill 方式殺死(我們的程序目前就是這種)

2,容器停止時間很長:

​ 運行命令 docker stop 之後,docker 會默認等待 10S(默認值,可以修改 docker stop -t 指令),如果 10s後容器還沒有終止,docker 就會繞過容器應用直接向內核發送 SIGKILL,內核強行殺死應用,從而終止容器。

docker kill主流程

1,docker 引擎通過containerd 使用 SIGKILL 發向容器主進程,等待一段時間後,如果從containerd收到容器退出消息,那麼容器kill成功
2,在上一步中如果等待超時,Docker引擎將跳過 containerd 自己親自動手通過kill系統調用向容器主進程發送 SIGKILL 信號。如果此時 kill 系統調用返回主進程不存在,那麼 Docker Kill 成功。否則引擎將一直死等到 containerd 通過引擎,容器退出.

docker 中 PID 進程不能處理 SIGTERM 信號的危害

上面我們講到如果容器內的 PID 進程不能處理 SIGTERM 信號的時候,docker 會等 10S(默認時間),然後調用 kill 去殺死容器的進程,其實這樣會造成下面兩個問題

1,進程不能正常終止

Linux 內核中其實會對 PID 1 進程發送特殊的信號量。一般情況下,當給一個進程發送信號時,內核會先檢查是否有用戶定義的處理函數,如果沒有,就會回退到默認行爲。例如使用 SIGTERM 直接殺死進程。然而,如果進程的 PID 是 1,那麼內核就會特殊對待它。如果沒有註冊用戶處理函數,內核不會回退到默認行爲,什麼也不做,換句話說,如果你的進程沒有處理信號的函數,給他發送 SIGTERM 會一點效果也沒有,這個我們在上面講過了。

常見的使用是 docker run my-container script. 給 docker run 進程發送SIGTERM 信號會殺掉 docker run 進程,但是容器還在後臺運行。

2,孤兒殭屍進程不能正常回收

當進程退出時,它會變成殭屍進程,直到它的父進程調用 wait() ( 或其變種 ) 的系統調用。process table 裏面會把它的標記爲 defunct 狀態。一般情況下,父進程應該立即調用 wait(), 以防殭屍進程時間過長。

如果父進程在子進程之前退出,子進程會變成孤兒進程, 它的父進程會變成 PID 1。因此,init 進程就要對這些進程負責,並在適當的時候調用 wait() 方法。

但是,通常情況下,大部分進程不會處理偶然依附在自己進程上的隨機子進程,所以在容器中,會出現許多殭屍進程。

解決容器進程收不到 SIGTERM 信號

通過上面的解釋應該能明白,我們不能正常退出,或者等 10s 才能退出的主要原因就是 PID 1 的進程不能處理/不處理 SIGTERM 信號造成的,知道問題所在了,那麼久好辦了,有如下幾種解決方案:

1,讓你們公司的程序代碼支持處理 SIGTERM 信號。

當我們 pid 1 的進程(自己公司的代碼)能處理 SIGTERM 信號,那麼這個問題不就解決了嗎?比較推薦這種方式,但是涉及到開發有一定的開發量,還是我們自己先用下面的方式解決。

2,構建 docker 包的時候使用 exec 模式的 ENTRYPOINT 指令

docker 官方文檔指出:

You can specify a plain string for the ENTRYPOINT and it will execute in /bin/sh -c. This form will use shell processing to substitute shell environment variables, and will ignore any CMD or docker run command line arguments. To ensure that docker stop will signal any long running ENTRYPOINT executable correctly, you need to remember to start it with exec:

你可以爲ENTRYPOINT指定一個普通字符串,它將在/bin/sh -c中執行。這個形式將使用shell處理來替代shell環境變量,並且會忽略任何CMD或docker運行命令行參數。爲了確保docker stop會正確地提示任何長期運行的ENTRYPOINT可執行文件,你需要記得用exec啓動它。

使用方式很簡單,我們只需要按照如下格式編寫 Dockerfile 即可

ENTRYPOINT exec COMMAND param1 param2

以這種方式啓動,exec 就會將 shell 進程替換爲 COMMAND 進程,

但是這種方式還是需要程序支持 SIGTERM,所以不推薦

3,在容器中使用 init 進程

當上面兩種情況我都不推薦的時候,那我們就只能用這種方式了。

在容器裏面添加一個 init 系統,讓他去處理 SIGTERM 信號。

init 系統有很多,這推薦下面兩種

1,tini

FROM alpine:3.7
...
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--", "COMMAND"]

現在 tini 就是 PID 1,它會將收到的系統信號轉發給子進程 COMMAND。

使用 tini 後應用還需要處理 SIGTERM 嗎?

答案是肯定不需要啊,如果需要那我們還大費周章的來講上面這麼多廢話嗎?

當一個進程爲普通進程,只要他收到系統信號,就會執行與該信號相關的默認動作,不需要再代碼中顯示實現邏輯,因此容器可以優雅的終止,而不需要強制 kill

2,dumb-init

他也是一個小型的 init 服務,他啓動一個子進程並轉發所有接收到的信號量給子進程。而且不需要修改應用代碼。

FROM alpine:3.7
...
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 &&\
    chmod +x /usr/local/bin/dumb-init
# Runs "/usr/bin/dumb-init -- /my/script --with --args"
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/my/script", "--with", "--args"]

需要注意的一點是:

雖然現在 PID 1 進程不是應用進程了,應用的行爲和在沒有 init 進程時是一樣的。如果應用進程死掉,那麼 init進程也會死掉,並會清理所有其他的子進程。

總結

開始說的那種情況就是應用進程沒有正常退出而造成的問題,

ENTRYPOINT的兩種模式

參考文檔:
docker init https://xcodest.me/docker-init-process.html
https://www.jianshu.com/p/813d8362d497
https://www.coder.work/article/41140
https://blog.csdn.net/shanzhizi/article/details/47320595
http://shareinto.github.io/2019/01/30/docker-init(1)/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章