docker容器如何優(yōu)雅的終止詳解
前言
在Docker大行其道的今天,我們能夠非常方便的使用容器打包我們的應(yīng)用程序,并且將它在我們的服務(wù)器上部署并運行起來。但是,談?wù)摰饺绾瓮5暨\行中的docker容器并正確的終止其中的程序,這就成為一個非常值得討論的話題了。
事實上,在我們?nèi)粘5捻椖慨斨?,這是我們經(jīng)常需要面對和處理的問題:
場景A:假如我們打包在容器中的程序,提供HTTP方式的服務(wù),負責處理各種HTTP requests并返回結(jié)果,我們必然希望在容器被停掉的時候,能夠讓程序有時間把已經(jīng)在處理中的請求繼續(xù)處理完畢,并返回結(jié)果給客戶端。
場景B:又比如我們打包在容器中的程序,負責寫入數(shù)據(jù)到某個數(shù)據(jù)文件中,我們希望程序能夠在容器被停掉的時候,有時間把內(nèi)存中緩存的數(shù)據(jù)持久化到存儲設(shè)備中,以防數(shù)據(jù)丟失。
場景C:再比如現(xiàn)在流行的微服務(wù)架構(gòu)中,一般會有服務(wù)發(fā)現(xiàn)的機制,也即每一個微服務(wù)在啟動之后,都會主動把自己的地址信息注冊到服務(wù)發(fā)現(xiàn)模塊當中,讓其他的服務(wù)可以知道自己的存在。而在容器被停掉的時候,微服務(wù)需要即時從服務(wù)發(fā)現(xiàn)模塊中注銷自己,以防止從API Gateway而來的請求被錯誤的路由到了已經(jīng)被停止掉的微服務(wù)。
如上的各種場景中,都要求打包在容器中的應(yīng)用程序能夠被優(yōu)雅的終止(也即gracefully shutdown),這種gracefully shutdown的方式,允許程序在容器被停止的時候,有一定時間做一些后續(xù)處理操作,這也是我們需要進一步探討的話題。
docker stop 與 docker kill 的區(qū)別
Docker本身提供了兩種終止容器運行的方式,即docker stop
與docker kill
。
docker stop
先來說說docker stop
吧,當我們用docker stop
命令來停掉容器的時候,docker默認會允許容器中的應(yīng)用程序有10秒的時間用以終止運行。所以我們查看docker stop
命令幫助的時候,會有如下的提示:
→ docker stop --help Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] Stop one or more running containers Options: --help Print usage -t, --time int Seconds to wait for stop before killing it (default 10)
在docker stop
命令執(zhí)行的時候,會先向容器中PID為1的進程發(fā)送系統(tǒng)信號SIGTERM,然后等待容器中的應(yīng)用程序終止執(zhí)行,如果等待時間達到設(shè)定的超時時間,或者默認的10秒,會繼續(xù)發(fā)送SIGKILL的系統(tǒng)信號強行kill掉進程。在容器中的應(yīng)用程序,可以選擇忽略和不處理SIGTERM信號,不過一旦達到超時時間,程序就會被系統(tǒng)強行kill掉,因為SIGKILL信號是直接發(fā)往系統(tǒng)內(nèi)核的,應(yīng)用程序沒有機會去處理它。在使用docker stop
命令的時候,我們唯一能控制的是超時時間,比如設(shè)置為20秒超時:
docker stop --time=20 container_name
docker kill
接著我們來看看docker kill
命令,默認情況下,docker kill
命令不會給容器中的應(yīng)用程序有任何gracefully shutdown的機會。它會直接發(fā)出SIGKILL的系統(tǒng)信號,以強行終止容器中程序的運行。通過查看docker kill
命令的幫助,我們可以看到,除了默認發(fā)送SIGKILL信號外,還允許我們發(fā)送一些自定義的系統(tǒng)信號:
→ docker kill --help Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...] Kill one or more running containers Options: --help Print usage -s, --signal string Signal to send to the container (default "KILL")
比如,如果我們想向docker中的程序發(fā)送SIGINT信號,我們可以這樣來實現(xiàn):
docker kill --signal=SIGINT container_name
與docker stop命令不一樣的地方在于,docker kill
沒有任何的超時時間設(shè)置,它會直接發(fā)送SIGKILL信號,以及用戶通過signal參數(shù)指定的其他信號。
其實不難看出,docker stop命令,更類似于Linux系統(tǒng)中的kill命令,二者都是發(fā)送系統(tǒng)信號SIGTERM。而docker kill
命令,更像是Linux系統(tǒng)中的kill -9或者是kill -SIGKILL命令,用來發(fā)送SIGKILL信號,強行終止進程。
在程序中接收并處理信號
了解了docker stop
與docker kill
的區(qū)別,我們能夠知道,docker kill
適合用來強行終止程序并實現(xiàn)快速停止容器。而如果希望程序能夠gracefully shutdown的話,docker stop
才是不二之選。這樣,我們可以讓程序在接收到SIGTERM信號后,有一定的時間處理、保存程序執(zhí)行現(xiàn)場,優(yōu)雅的退出程序。
接下來我們可以寫一個簡單的Go程序來實現(xiàn)信號的接收與處理,程序在啟動過后,會一直阻塞并監(jiān)聽系統(tǒng)信號,直到監(jiān)測到對應(yīng)的系統(tǒng)信號后,輸出控制臺并退出執(zhí)行。
// main.go package main import ( "fmt" "os" "os/signal" "syscall" ) func main() { fmt.Println("Program started...") ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGTERM) s := <-ch if s == syscall.SIGTERM { fmt.Println("SIGTERM received!") //Do something... } fmt.Println("Exiting...") }
接下來使用交叉編譯的方式來編譯程序,讓程序可以在Linux下運行:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o graceful
編譯好之后,我們還需要打包程序到容器中運行。于是,我們還得有個Dockerfile。在這里,我們選擇使用體積小又輕盈的alpine鏡像作為基礎(chǔ)鏡像,打包這個Go程序:
from alpine:latest MAINTAINER Timothy ADD graceful /graceful CMD ["/graceful"]
這里需要避開的一個坑,是Dockerfile中CMD命令的用法。
CMD命令有兩種方式:
CMD /graceful
使用 CMD command param1 param2
這種方式,其實是以shell的方式運行程序。最終程序被執(zhí)行時,類似于/bin/sh -c的方式運行了我們的程序,這樣會導致/bin/sh以PID為1的進程運行,而我們的程序只不過是它fork/execs出來的子進程而已。前面我們提到過docker stop的SIGTERM信號只是發(fā)送給容器中PID為1的進程,而這樣,我們的程序就沒法接收和處理到信號了。
CMD [“/graceful”]
使用 CMD [“executable”,”param1”,”param2”]
這種方式啟動程序,才是我們想要的,這種方式執(zhí)行和啟動時,我們的程序會被直接啟動執(zhí)行,而不是以shell的方式,這樣我們的程序就能以PID=1
的方式開始執(zhí)行了。
話題轉(zhuǎn)回來,我們開始執(zhí)行容器構(gòu)建操作,打包程序:
docker build -t registry.xiaozhou.net/graceful:latest .
打包過后的鏡像,才6MB左右:
λ Timothy [workspace/src/graceful] → docker images REPOSITORY TAG IMAGE ID CREATED SIZE registry.xiaozhou.net/graceful latest b2210a85ca55 20 hours ago 6.484 MB
啟動并運行容器:
λ Timothy [workspace/src/graceful] → docker run -d --name graceful b2210a85
查看容器運行狀態(tài):
λ Timothy [workspace/src/graceful] → docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES fd18eedafd16 b221 "/graceful" 3 seconds ago Up 2 seconds graceful
查看容器輸出,能看到程序已經(jīng)正常啟動:
λ Timothy [workspace/src/graceful] → docker logs graceful Started...
接著我們要使用docker stop
大法,看程序能否響應(yīng)SIGTERM信號:
λ Timothy [workspace/src/graceful] → docker stop graceful graceful
最后,查看容器的日志,檢驗輸出:
λ Timothy [workspace/src/graceful] → docker logs graceful Started... SIGTERM received! Exiting...
總結(jié)
以上就是這篇文章的全部內(nèi)容了,用docker kill命令,可以簡單粗暴的終止docker容器中運行的程序,但是想要優(yōu)雅的終止掉的話,我們需要使用docker stop命令,并且在程序中多花一些功夫來處理系統(tǒng)信號,這樣能保證程序不被粗暴的終止掉,從而實現(xiàn)gracefully shutdown。希望本文的內(nèi)容對大家的學習或者工作能有所幫助,如果有疑問大家可以留言交流。
相關(guān)文章
詳解MAC OSX Docker開發(fā)環(huán)境搭建
本篇文章主要介紹了詳解MAC OSX Docker開發(fā)環(huán)境搭建,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03docker清理大殺器/docker的overlay文件占用磁盤太大的解決
這篇文章主要介紹了docker清理大殺器/docker的overlay文件占用磁盤太大的解決操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11使用Docker安裝Nginx并配置端口轉(zhuǎn)發(fā)問題及解決方法
這篇文章主要介紹了使用Docker安裝Nginx并配置端口轉(zhuǎn)發(fā),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-01-01pipework docker無法使用ip netns命令解決辦法
這篇文章主要介紹了pipework docker無法使用ip netns命令解決辦法的相關(guān)資料,需要的朋友可以參考下2016-10-10在宿主機上執(zhí)行docker容器內(nèi)部的shell或程序方式
這篇文章主要介紹了在宿主機上執(zhí)行docker容器內(nèi)部的shell或程序方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11