最近把 Docker 官方的 Docker Reference 文檔又讀了一遍,發現有些細節深究起來,還是有很多可挖的。針對寫 Dockerfile ,大部分時候只要照葫蘆畫瓢,基本也不會有什么大的問題,但是如果再深入理解一下那就更有意思了。
要說如何優雅的關閉容器,那就不得不提到信號(Signal)的理念,以及 Dockerfile 中 ENTRYPOINT 和 CMD 指令了。在具體說優雅關閉之前,先了解一下信號這個 Linux 中的基礎概念。
1 信號
信號是事件發生時對進程的通知機制,有時也稱之為軟件中斷。
信號有不同的類型,Linux 對標準信號的編號為 1~31,可以通過 kill -l 獲取信號名稱:
#kill-l 1)SIGHUP2)SIGINT3)SIGQUIT 4)SIGILL5)SIGTRAP6)SIGABRT 7)SIGBUS8)SIGFPE9)SIGKILL 10)SIGUSR111)SIGSEGV12)SIGUSR2 13)SIGPIPE14)SIGALRM15)SIGTERM ......
實際列出的信號超過了 31 個,有些是其它名稱的同義詞,有些則是定義但未使用的。以下介紹幾個常用的信號:
SIGHUP 當終端斷開(掛機)時,將發送該信號給終端控制進程。SIGHUP 信號還可用于守護進程(比如,init 等)。許多守護進程會在收到 SIGHUP 信號時重新進行初始化并重讀配置文件。
SIGINT 當用戶鍵入終端中斷字符(通常為 Control-C ) 時,終端驅動程序將發送該信號給前臺進程組。該信號的默認行為是終止進程。
SIGQUIT 當用戶在鍵盤上鍵入退出字符(通常為 Control- )時,該信號將發往前臺進程組。默認情況下,該信號終止進程,并生成用于調試的核心轉儲文件。進程如果陷入無限循環,或者不再響應時,使用 SIGQUIT 信號就很合適。
SIGKILL 此信號為 “必殺(sure kill)” 信號,處理器程序無法將其阻塞、忽略或者捕獲,故而 “一擊必殺”,總能終止程序。
SIGTERM 這是用來終止進程的標準信號,也是 kill 、 killall 、 pkill 命令所發送的默認信號。精心設計的應用程序應當為 SIGTERM 信號設置處理器程序,以便其能夠預先清除臨時文件和釋放其它資源,從而全身而退。因此,總是應該先嘗試使用 SIGTERM 信號來終止進程,而把 SIGKILL 作為最后手段,去對付那些不響應 SIGTERM 信號的失控進程。
SIGTSTP 這是作業控制的停止信號,當用戶在鍵盤上輸入掛起字符(通常為 Control-Z )時,將該信號給前臺進程組,使其停止運行。
值得注意的是, Control-D 不會發起信號,它表示 EOF(End-Of-File),關閉標準輸入(stdin)管道(比如可以通過 Control-D 退出當前 shell)。如果程序不讀取當前輸入的話,是不受 Control-D 影響的。
程序可以針對信號捕捉,然后執行相應函數:
以上知識大部分都來自 《Linux/UNIX 系統編程手冊》,想要了解更多的,可以查看該書上冊的 20、21、22 章節。
2 ENTRYPOINT 、 CMD
可能有人會問,說了半天,那信號和優雅的關閉容器有半毛錢的關系啊?話說,這和錢確實沒關系,但是和如何優雅關閉容器卻關系密切。
接著說 Dockerfile 中的 ENTRYPOINT 和 CMD 指令,它們的主要功能是指定容器啟動時執行的程序。
CMD 有三種格式:
CMD ["executable","param1","param2"] (exec 格式, 推薦使用這種格式)
CMD ["param1","param2"] (作為 ENTRYPOINT 指令參數)
CMD command param1 param2 (shell 格式,默認 /bin/sh -c )
ENTRYPOINT 有兩種格式:
ENTRYPOINT ["executable", "param1", "param2"] (exec 格式,推薦優先使用這種格式)
ENTRYPOINT command param1 param2 (shell 格式)
其中,不管你 Dockerfile 用其中哪個指令,兩個指令都推薦使用 exec 格式,而不是 shell 格式。原因就是因為使用 shell 格式之后,程序會以 /bin/sh -c 的子命令啟動,并且 shell 格式下不會傳遞任何信號給程序。這也就導致,在 docker stop 容器的時候,以這種格式運行的程序捕捉不到發送的信號,也就談不上優雅的關閉了。
?~dockerstop--help Usage:dockerstop[OPTIONS]CONTAINER[CONTAINER...] Stoponeormorerunningcontainers Options: --helpPrintusage -t,--timeintSecondstowaitforstopbeforekillingit(default10)
docker stop 停掉容器的時候,默認會發送一個 SIGTERM 的信號,默認 10s 后容器沒有停止的話,就 SIGKILL 強制停止容器。通過 -t 選項可以設置等待時間。
?~dockerkill--help Usage:dockerkill[OPTIONS]CONTAINER[CONTAINER...] Killoneormorerunningcontainers Options: --helpPrintusage -s,--signalstringSignaltosendtothecontainer(default"KILL")
通過 docker kill的-s選項還可以指定給容器發送的信號。
所以,說了那么多,只要 Dockerfile 中通過 exec 格式執行容器啟動命令就相安無事了?那當然是,沒有那么簡單的了,接下來我們通過實例來看看具體的效果是怎么樣的。
3 實例
通過 Go 寫一個簡單的信號處理器:
?~catsignals.go packagemain import( "fmt" "os" "os/signal" "syscall" ) funcmain(){ sigs:=make(chanos.Signal,1) done:=make(chanbool,1) signal.Notify(sigs,syscall.SIGINT,syscall.SIGTERM) gofunc(){ sig:=<-sigs ????????fmt.Println() ????????fmt.Println(sig) ????????done?<-?true ????}() ????fmt.Println("awaiting?signal") ????<-done ????fmt.Println("exiting") }
3.1 實例 1
?~GOOS=linuxGOARCH=amd64gobuildsignals.go ?~ls Dockerfilesignalssignals.go ?~catDockerfile FROMbusybox COPYsignals/signals CMD["/signals"]#exec格式執行 ?~dockerbuild-tsignals.
通過 tmux 開啟兩個面板,一個運行容器,一個執行 docker stop :
?~dockerrun-it--rm--namesignalssignals awaitingsignal terminated exiting
?~timedockerstopsignals signals dockerstopsignals0.01suser0.02ssystem4%cpu0.732total ?~
可以發現,容器停止之前,程序接收到信號并輸出相應信息,并且停止總耗時為 0.732 s,達到了優雅的效果。
修改 Dockerfile 中 CMD 執行格式,執行相同操作:
?~catDockerfile FROMbusybox COPYsignals/signals CMD/signals#shell格式執行 ?~dockerbuild-tsignals.
?~dockerrun-it--rm--namesignalssignals awaitingsignal ?~
?~timedockerstopsignals signals dockerstopsignals0.01suser0.01ssystem0%cpu10.719total
通過 shell 格式之后,可以發現容器停止之前,程序并未接收到任何信號,并且停止時間為 10.719s,說明該容器是被強制停止的。
結論很明顯,為了優雅的退出容器,我們應該采用 exec 這種格式。
3.2 實例 2
通過實例 1 我們都會在 Dockerfile 中都會通過 exec 這種格式來執行程序了,那如果執行的程序本身也是一個 shell 腳本呢?
?~ls Dockerfilesignalssignals.gostart.sh ?~catDockerfile FROMbusybox COPYsignals/signals COPYstart.sh/start.sh#引入shell腳本啟動 CMD["/start.sh"] ?~catstart.sh #!/bin/sh /signals ?~
測試依然引用實例 1 中的方法:
?~dockerrun-it--rm--namesignalssignals awaitingsignal ?~
?~timedockerstopsignals signals dockerstopsignals0.01suser0.02ssystem0%cpu10.765total ?~
可以發現,即使 Dockerfile 中的 CMD 指令使用的是 exec 格式,容器中的程序依然沒有接收到信號,最后被強制關閉。因為 shell 腳本中執行的原因,導致信號依然沒有被傳遞,我們需要針對 shell 腳本做一些改造:
?~catstart.sh #!/bin/sh exec/signals#加入exec執行 ?~dockerbuild-tsignals.
?~dockerrun-it--rm--namesignalssignals awaitingsignal terminated exiting
?~timedockerstopsignals signals dockerstopsignals0.02suser0.02ssystem4%cpu0.744total ?~
可以看到,加入 exec 命令之后,程序又可以接收到信號正常退出了。當然,如果你 Dockerfile 中的 CMD 是以 shell 格式運行的,即使啟動腳本中加入 exec 也是無效的。再者,如果你的程序本身不能針對信號做一些處理,也就談不上優雅關閉了。
審核編輯:湯梓紅
-
Linux
+關注
關注
87文章
11326瀏覽量
209961 -
信號
+關注
關注
11文章
2797瀏覽量
76939 -
容器
+關注
關注
0文章
498瀏覽量
22086 -
命令
+關注
關注
5文章
692瀏覽量
22063 -
Docker
+關注
關注
0文章
489瀏覽量
11887
原文標題:如何優雅地關閉容器
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論