原文連接:Docker 容器優雅終止方案html
做爲一名系統重啓工程師(SRE),你可能常常須要重啓容器,畢竟 Kubernetes 的優點就是快速彈性伸縮和故障恢復,遇到問題先重啓容器再說,幾秒鐘便可恢復,實在不行再重啓系統,這就是系統重啓工程師的殺手鐗。然而現實並無理論上那麼美好,某些容器須要花費 10s
左右才能中止,這是爲啥?有如下幾種可能性:node
對於第 3 種可能性咱們無能爲力,本文主要解決 1 和 2。git
若是要構建一個新的 Docker 鏡像,確定但願鏡像越小越好,這樣它的下載和啓動速度都很快,通常咱們都會選擇一個瘦了身的操做系統(例如 Alpine
,Busybox
等)做爲基礎鏡像。github
問題就在這裏,這些基礎鏡像的 init 系統也被抹掉了,這就是問題的根源!docker
init
系統有如下幾個特色:shell
對於容器來講,init
系統不是必須的,當你經過命令 docker stop mycontainer
來中止容器時,docker CLI 會將 TERM
信號發送給 mycontainer 的 PID
爲 1 的進程。bash
ENTRYPOINT
或 CMD
指定的應用)就是 PID 1,應用進程直接負責響應 TERM
信號。這時又分爲兩種狀況:
SIGTERM
信號,或者應用中沒有實現處理 SIGTERM
信號的邏輯,應用就不會中止,容器也不會終止。docker stop mycontainer
以後,Docker 會等待 10s
,若是 10s
後容器尚未終止,Docker 就會繞過容器應用直接向內核發送 SIGKILL
,內核會強行殺死應用,從而終止容器。若是容器中的進程沒有收到 SIGTERM
信號,頗有多是由於應用進程不是 PID 1
,PID 1 是 shell
,而應用進程只是 shell
的子進程。而 shell 不具有 init
系統的功能,也就不會將操做系統的信號轉發到子進程上,這也是容器中的應用沒有收到 SIGTERM
信號的常見緣由。post
問題的根源就來自 Dockerfile
,例如:優化
FROM alpine:3.7 COPY popcorn.sh . RUN chmod +x popcorn.sh ENTRYPOINT ./popcorn.sh
ENTRYPOINT
指令使用的是 shell 模式,這樣 Docker 就會把應用放到 shell
中運行,所以 shell
是 PID 1。ui
解決方案有如下幾種:
與其使用 shell 模式,不如使用 exec 模式,例如:
FROM alpine:3.7 COPY popcorn.sh . RUN chmod +x popcorn.sh ENTRYPOINT ["./popcorn.sh"]
這樣 PID 1 就是 ./popcorn.sh
,它將負責響應全部發送到容器的信號,至於 ./popcorn.sh
是否真的能捕捉到系統信號,那是另外一回事。
舉個例子,假設使用上面的 Dockerfile 來構建鏡像,popcorn.sh
腳本每過一秒打印一第二天期:
#!/bin/sh while true do date sleep 1 done
構建鏡像並建立容器:
🐳 → docker build -t truek8s/popcorn . 🐳 → docker run -it --name corny --rm truek8s/popcorn
打開另一個終端執行中止容器的命令,並計時:
🐳 → time docker stop corny
由於 popcorn.sh
並無實現捕獲和處理 SIGTERM
信號的邏輯,因此須要 10s 左右才能中止容器。要想解決這個問題,就要往腳本中添加信號處理代碼,讓它捕獲到 SIGTERM
信號時就終止進程:
#!/bin/sh # catch the TERM signal and then exit trap "exit" TERM while true do date sleep 1 done
注意:下面這條指令與 shell 模式的 ENTRYPOINT 指令是等效的:
ENTRYPOINT ["/bin/sh", "./popcorn.sh"]
若是你就想使用 shell
模式的 ENTRYPOINT 指令,也不是不能夠,只需將啓動命令追加到 exec
後面便可,例如:
FROM alpine:3.7 COPY popcorn.sh . RUN chmod +x popcorn.sh ENTRYPOINT exec ./popcorn.sh
這樣 exec
就會將 shell 進程替換爲 ./popcorn.sh
進程,PID 1 仍然是 ./popcorn.sh
。
若是容器中的應用默認沒法處理 SIGTERM
信號,又不能修改代碼,這時候方案 1 和 2 都行不通了,只能在容器中添加一個 init
系統。init 系統有不少種,這裏推薦使用 tini,它是專用於容器的輕量級 init 系統,使用方法也很簡單:
tini
tini
設爲容器的默認應用popcorn.sh
做爲 tini
的參數具體的 Dockerfile 以下:
FROM alpine:3.7 COPY popcorn.sh . RUN chmod +x popcorn.sh RUN apk add --no-cache tini ENTRYPOINT ["/sbin/tini", "--", "./popcorn.sh"]
如今 tini
就是 PID 1,它會將收到的系統信號轉發給子進程 popcorn.sh
。
若是你想直接經過 docker 命令來運行容器,能夠直接經過參數
--init
來使用 tini,不須要在鏡像中安裝 tini。若是是Kubernetes
就不行了,還得老老實實安裝 tini。
最後一個問題:若是移除 popcorn.sh
中對 SIGTERM 信號的處理邏輯,容器會在咱們執行中止命令後當即終止嗎?
答案是確定的。在 Linux 系統中,PID 1
和其餘進程不太同樣,準確地說應該是 init
進程和其餘進程不同,它不會執行與接收到的信號相關的默認動做,必須在代碼中明確實現捕獲處理 SIGTERM
信號的邏輯,方案 1 和 2 乾的就是這個事。
普通進程就簡單多了,只要它收到系統信號,就會執行與該信號相關的默認動做,不須要在代碼中顯示實現邏輯,所以能夠優雅終止。
Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12離線安裝包發佈地址http://store.lameleg.com ,歡迎體驗。 使用了最新的sealos v3.3.6版本。 做了主機名解析配置優化,lvscare 掛載/lib/module解決開機啓動ipvs加載問題, 修復lvscare社區netlink與3.10內核不兼容問題,sealos生成百年證書等特性。更多特性 https://github.com/fanux/sealos 。歡迎掃描下方的二維碼加入釘釘羣 ,釘釘羣已經集成sealos的機器人實時能夠看到sealos的動態。