中的stop_谈谈stop容器
docker stop
對于docker來說,一般來說通過docker stop命令來實現(xiàn)停止容器,而不是docker kill。
具體命令如下:
docker stop [OPTIONS] CONTAINER [CONTAINER...]容器內(nèi)的主進程(PID為1的進程)將收到SIGTERM,并在寬限期之后收到SIGKILL。在容器中的應用程序,可以選擇忽略和不處理SIGTERM信號,不過一旦達到超時時間,程序就會被系統(tǒng)強行kill掉,因為SIGKILL信號是直接發(fā)往系統(tǒng)內(nèi)核的,應用程序沒有機會去處理它。
至于這個寬限期默認是10s,當然可以通過參數(shù)來制定具體時間。
docker stop --helpUsage: docker stop [OPTIONS] CONTAINER [CONTAINER...]Stop one or more running containersOptions:--help Print usage-t, --time int Seconds to wait for stop before killing it (default 10)而對于k8s來說,pod的寬限期默認是30s。通過terminationGracePeriodSeconds參數(shù)設置。
為什么需要優(yōu)雅stop docker ?
你的程序需要一些退出工作,比如保存checkpoint,回收一些資源對象等。如果你的服務是一個http server,那么你需要完成已經(jīng)處理的請求。如果是長鏈接,你還需要主動關(guān)閉keepalive。
如果你是在k8s中運行容器,那么k8s整個機制是一種基于watch的并行機制,我們不能保證操作的串行執(zhí)行。比如在刪除一個Pod的時候,需要更改iptables規(guī)則,LB的upstream 摘除等。
你的應用程序為什么接收不到SIGTERM停機信號?
- 你的業(yè)務進程不是1號進程
Dockerfile中支持兩種格式定義入口點:shell格式和exec 格式。
exec格式 如下:
ENTRYPOINT ["/app/bin/your-app", "arg1", "arg2"]該格式能保證你的主進程接受到停機信號。
示例:
程序代碼如下:
package mainimport ("fmt""os""os/signal""syscall""time" )func main() {c := make(chan os.Signal)// 監(jiān)聽信號signal.Notify(c, syscall.SIGTERM)go func() {for s := range c {switch s {case syscall.SIGTERM:fmt.Println("退出:", s)ExitFunc()default:fmt.Println("其他信號:", s)}}}()fmt.Println("啟動了程序")sum := 0for {sum++fmt.Println("休眠了:", sum, "秒")time.Sleep(1 * time.Second)} }func ExitFunc() {fmt.Println("開始退出...")fmt.Println("執(zhí)行清理...")fmt.Println("結(jié)束退出...")os.Exit(0) }Dockerfiler如下,我們采用多階段構(gòu)建:
FROM golang:latest as builderWORKDIR /go/src COPY main.go .RUN CGO_ENABLED=0 go build -o stop ./main.goFrom alpine:latestWORKDIR /root/ COPY --from=builder /go/src/stop . RUN chmod +x /root/stopENTRYPOINT ["/root/stop"]構(gòu)建鏡像:
docker build -t stop . Sending build context to Docker daemon 3.584kB Step 1/9 : FROM golang:latest as builder latest: Pulling from library/golang 376057ac6fa1: Pull complete 5a63a0a859d8: Pull complete 496548a8c952: Pull complete 2adae3950d4d: Pull complete 039b991354af: Pull complete 0cca3cbecb14: Pull complete 59c34b3f33f3: Pull complete Digest: sha256:1e36f8e9ac49d5ee6d72e969382a698614551a59f4533d5d61590e3deeb543a7 Status: Downloaded newer image for golang:latest---> 7e5e8028e8ec Step 2/9 : WORKDIR /go/src---> Running in efb1e4b1c200 Removing intermediate container efb1e4b1c200---> 312e98c07647 Step 3/9 : COPY main.go .---> 2dc4088e6548 Step 4/9 : RUN CGO_ENABLED=0 go build -o stop ./main.go---> Running in 6d18a1ef07ff Removing intermediate container 6d18a1ef07ff---> a207b2ecdd67 Step 5/9 : From alpine:latest latest: Pulling from library/alpine Digest: sha256:9a839e63dad54c3a6d1834e29692c8492d93f90c59c978c1ed79109ea4fb9a54 Status: Downloaded newer image for alpine:latest---> f70734b6a266 Step 6/9 : WORKDIR /root/---> Running in a308fc079da2 Removing intermediate container a308fc079da2---> a14716065730 Step 7/9 : COPY --from=builder /go/src/stop .---> 3573b92b9ab3 Step 8/9 : RUN chmod +x /root/stop---> Running in f620b3287636 Removing intermediate container f620b3287636---> 3cbc57300792 Step 9/9 : ENTRYPOINT ["/root/stop"]---> Running in 86f23ea9306f Removing intermediate container 86f23ea9306f---> 283788e6ad37 Successfully built 283788e6ad37 Successfully tagged stop:latest在一個終端中運行該鏡像:
docker run stop在另外一個終端stop該容器:
docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 91eeef705489 stop "/root/stop" 12 seconds ago Up 11 seconds clever_leavittdocker stop 91eeef705489 91eeef705489最終有如下輸出:
啟動了程序 休眠了: 1 秒 休眠了: 2 秒 休眠了: 3 秒 休眠了: 4 秒 休眠了: 5 秒 休眠了: 6 秒 休眠了: 7 秒 休眠了: 8 秒 休眠了: 9 秒 休眠了: 10 秒 休眠了: 11 秒 休眠了: 12 秒 休眠了: 13 秒 休眠了: 14 秒 休眠了: 15 秒 休眠了: 16 秒 休眠了: 17 秒 休眠了: 18 秒 休眠了: 19 秒 休眠了: 20 秒 休眠了: 21 秒 休眠了: 22 秒 退出: terminated 開始退出... 執(zhí)行清理... 結(jié)束退出...通過標準輸出,我們的程序接受到了SIGTERM信號,并執(zhí)行了一些退出工作。
shell格式 如下:
ENTRYPOINT "/app/bin/your-app arg1 arg2"Shell格式將您的入口點作為 /bin/sh -c 的子命令來運行。
示例:
代碼不變,Dockerfile更改為:
FROM golang:latest as builderWORKDIR /go/src COPY main.go .RUN CGO_ENABLED=0 go build -o stop ./main.goFrom alpine:latestWORKDIR /root/ COPY --from=builder /go/src/stop . RUN chmod +x /root/stopENTRYPOINT "/root/stop"構(gòu)建新的鏡像:
$ docker build -t stop-shell -f Dockerfile-shell .Sending build context to Docker daemon 4.608kB Step 1/9 : FROM golang:latest as builder---> 7e5e8028e8ec Step 2/9 : WORKDIR /go/src---> Using cache---> 312e98c07647 Step 3/9 : COPY main.go .---> Using cache---> 2dc4088e6548 Step 4/9 : RUN CGO_ENABLED=0 go build -o stop ./main.go---> Using cache---> a207b2ecdd67 Step 5/9 : From alpine:latest---> f70734b6a266 Step 6/9 : WORKDIR /root/---> Using cache---> a14716065730 Step 7/9 : COPY --from=builder /go/src/stop .---> Using cache---> 3573b92b9ab3 Step 8/9 : RUN chmod +x /root/stop---> Using cache---> 3cbc57300792 Step 9/9 : ENTRYPOINT "/root/stop"---> Running in 199ca0277b08 Removing intermediate container 199ca0277b08---> e0fe6a86ee1e Successfully built e0fe6a86ee1e Successfully tagged stop-shell:latest重復上面的步驟,最終觀察到的結(jié)果如下:
動了程序 休眠了: 1 秒 休眠了: 2 秒 休眠了: 3 秒 休眠了: 4 秒 休眠了: 5 秒 休眠了: 6 秒 休眠了: 7 秒 休眠了: 8 秒 休眠了: 9 秒 休眠了: 10 秒 休眠了: 11 秒 休眠了: 12 秒 休眠了: 13 秒 休眠了: 14 秒 休眠了: 15 秒 休眠了: 16 秒 休眠了: 17 秒 休眠了: 18 秒 休眠了: 19 秒 休眠了: 20 秒 休眠了: 21 秒 休眠了: 22 秒 休眠了: 23 秒 休眠了: 24 秒 退出: terminated 開始退出... 執(zhí)行清理... 結(jié)束退出...shell格式,我們的主程序也接受到了停機信號,并做了退出工作。
為了驗證,我們docker exec 到運行的docker-shell容器中,執(zhí)行ps:
docker exec -it 0299308034e7 sh ~ # ps PID USER TIME COMMAND1 root 0:00 /root/stop12 root 0:00 sh17 root 0:00 ps我們的應用進程是1號進程,所以我們依舊可以接收到SIGTERM信號。
當我們的應用程序直接是啟動的入口,那么在接受停機信號方面,兩種格式并沒有什么區(qū)別。如果我們的啟動腳本是一個類似于run.sh 的shell腳本,又會怎么樣那?
當我們以一個shell腳本啟動我們的應用程序,那么我們的應用程序不再是1號進程,此時,shell進程并不會通知我們的應用進程退出,我們需要在shell腳本中做一些特殊的處理,才能實現(xiàn)同樣的效果。
需要做的就是告訴你的Shell用你的應用程序替換自身。為此,shell具有 exec 命令(與前面講到的 exec 格式相似)。詳情見exec syscall。
在run.sh 中替換
/app/bin/your-app為:
exec /app/bin/your-app示例:
我們的run.sh 腳本如下:
#!/bin/shexec /root/stop然后我們的Dockerfile 變更為:
FROM golang:latest as builderWORKDIR /go/src COPY main.go .RUN CGO_ENABLED=0 go build -o stop ./main.goFrom alpine:latestWORKDIR /root/ COPY --from=builder /go/src/stop . COPY run.sh . RUN chmod +x /root/stopENTRYPOINT ["/root/run.sh"]構(gòu)建新的鏡像之后,運行該鏡像:
docker run stop-shell-runsh啟動了程序 休眠了: 1 秒 休眠了: 2 秒 休眠了: 3 秒然后進入到容器中執(zhí)行ps:
docker exec -it 97adce7dd7e4 sh ~ # ps PID USER TIME COMMAND1 root 0:00 /root/stop14 root 0:00 sh19 root 0:00 ps可以看到雖然我們的啟動腳本是run.sh,但是經(jīng)過exec 之后,應用程序成為了1號進程。
停止運行容器查看停機狀況:
docker stop 97adce7dd7e4然后可以看到容器有如下輸出:
休眠了: 104 秒 休眠了: 105 秒 休眠了: 106 秒 休眠了: 107 秒 休眠了: 108 秒 休眠了: 109 秒 休眠了: 110 秒 休眠了: 111 秒 休眠了: 112 秒 休眠了: 113 秒 休眠了: 114 秒 休眠了: 115 秒 休眠了: 116 秒 休眠了: 117 秒 退出: terminated 開始退出... 執(zhí)行清理... 結(jié)束退出...- 監(jiān)聽了錯誤的信號
并不是所有的代碼框架都支持SIGTERM,比如Python的生態(tài)中,經(jīng)常是SIGINT。
例如:
try:do_work() except KeyboardInterrupt:cleanup()所以默認是發(fā)送SIGTERM信號,我們依舊可以設置成其他的信號。
最簡單的解決方法是在Dockerfile中添加一行:
STOPSIGNAL SIGINT雖然我們將應用程序作為1號進程,可以接收到信號,但是也帶來其他的問題,比如僵尸進程。該問題在docker使用過程中很普遍存在。大家可以參考我另外一篇文章--避免在Docker鏡像下將NodeJS作為PID 1運行。最佳實踐
使用 init 系統(tǒng)。這里我們推薦使用 tini。
Tini是你可能想到的最簡單的 init。 Tini所做的全部工作就是span出子進程,并等待它退出,同時收獲僵尸進程并執(zhí)行信號轉(zhuǎn)發(fā)。
使用 tini 有以下好處:
- 它可以保護您免受意外創(chuàng)建僵尸進程的軟件的侵害,因為僵尸進程可能(隨著時間的推移!)使整個系統(tǒng)缺乏PID(并使其無法使用)。
- 它可確保默認信號處理程序適用于您在Docker鏡像中運行的軟件。例如,對于Tini,即使您沒有顯式安裝信號處理程序,SIGTERM也會正確終止您的進程。
- 它完全透明地執(zhí)行!沒有Tini的Docker鏡像將與Tini一起使用,而無需進行任何更改。
示例:
新的Dockerfile如下:
FROM golang:latest as builderWORKDIR /go/src COPY main.go .RUN CGO_ENABLED=0 go build -o stop ./main.goFrom alpine:latestRUN apk add --no-cache tini WORKDIR /root/ COPY --from=builder /go/src/stop . RUN chmod +x /root/stopENTRYPOINT ["/sbin/tini", "--", "/root/stop"]構(gòu)建鏡像:
docker build -t stop-tini -f Dockerfile-tini .運行tini鏡像:
$ docker run stop-tini啟動了程序 休眠了: 1 秒 休眠了: 2 秒 休眠了: 3 秒 休眠了: 4 秒 休眠了: 5 秒 休眠了: 6 秒 休眠了: 7 秒...此時在另外一個終端執(zhí)行 docker exec 進入到容器中,并執(zhí)行 ps:
docker exec -it a727bd6617f4 sh ~ # ps PID USER TIME COMMAND1 root 0:00 /sbin/tini -- /root/stop7 root 0:00 /root/stop14 root 0:00 sh20 root 0:00 ps此時可以看到,tini是1號進程,我們的應用程序是1號進程的子進程(7號)。
停止該容器:
docker stop a727bd6617f4最終我們的運行容器有以下輸出:
休眠了: 82 秒 休眠了: 83 秒 休眠了: 84 秒 休眠了: 85 秒 休眠了: 86 秒 退出: terminated 開始退出... 執(zhí)行清理... 結(jié)束退出...可以看到我們業(yè)務進程雖然不是1號進程,但是也接受到了停機信號。
當然這一切都歸功于tini,tini將信號轉(zhuǎn)發(fā)到了我們的應用程序。
總結(jié)
以上是生活随笔為你收集整理的中的stop_谈谈stop容器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: antd动态隐藏表格中的一列_有很多ex
- 下一篇: 个人手机银行怎么登录