中的stop_谈谈stop容器
docker stop
對(duì)于docker來(lái)說(shuō),一般來(lái)說(shuō)通過(guò)docker stop命令來(lái)實(shí)現(xiàn)停止容器,而不是docker kill。
具體命令如下:
docker stop [OPTIONS] CONTAINER [CONTAINER...]容器內(nèi)的主進(jìn)程(PID為1的進(jìn)程)將收到SIGTERM,并在寬限期之后收到SIGKILL。在容器中的應(yīng)用程序,可以選擇忽略和不處理SIGTERM信號(hào),不過(guò)一旦達(dá)到超時(shí)時(shí)間,程序就會(huì)被系統(tǒng)強(qiáng)行kill掉,因?yàn)镾IGKILL信號(hào)是直接發(fā)往系統(tǒng)內(nèi)核的,應(yīng)用程序沒(méi)有機(jī)會(huì)去處理它。
至于這個(gè)寬限期默認(rèn)是10s,當(dāng)然可以通過(guò)參數(shù)來(lái)制定具體時(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)而對(duì)于k8s來(lái)說(shuō),pod的寬限期默認(rèn)是30s。通過(guò)terminationGracePeriodSeconds參數(shù)設(shè)置。
為什么需要優(yōu)雅stop docker ?
你的程序需要一些退出工作,比如保存checkpoint,回收一些資源對(duì)象等。如果你的服務(wù)是一個(gè)http server,那么你需要完成已經(jīng)處理的請(qǐng)求。如果是長(zhǎng)鏈接,你還需要主動(dòng)關(guān)閉keepalive。
如果你是在k8s中運(yùn)行容器,那么k8s整個(gè)機(jī)制是一種基于watch的并行機(jī)制,我們不能保證操作的串行執(zhí)行。比如在刪除一個(gè)Pod的時(shí)候,需要更改iptables規(guī)則,LB的upstream 摘除等。
你的應(yīng)用程序?yàn)槭裁唇邮詹坏絊IGTERM停機(jī)信號(hào)?
- 你的業(yè)務(wù)進(jìn)程不是1號(hào)進(jìn)程
Dockerfile中支持兩種格式定義入口點(diǎn):shell格式和exec 格式。
exec格式 如下:
ENTRYPOINT ["/app/bin/your-app", "arg1", "arg2"]該格式能保證你的主進(jìn)程接受到停機(jī)信號(hào)。
示例:
程序代碼如下:
package mainimport ("fmt""os""os/signal""syscall""time" )func main() {c := make(chan os.Signal)// 監(jiān)聽(tīng)信號(hào)signal.Notify(c, syscall.SIGTERM)go func() {for s := range c {switch s {case syscall.SIGTERM:fmt.Println("退出:", s)ExitFunc()default:fmt.Println("其他信號(hào):", s)}}}()fmt.Println("啟動(dòng)了程序")sum := 0for {sum++fmt.Println("休眠了:", sum, "秒")time.Sleep(1 * time.Second)} }func ExitFunc() {fmt.Println("開(kāi)始退出...")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在一個(gè)終端中運(yùn)行該鏡像:
docker run stop在另外一個(gè)終端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最終有如下輸出:
啟動(dòng)了程序 休眠了: 1 秒 休眠了: 2 秒 休眠了: 3 秒 休眠了: 4 秒 休眠了: 5 秒 休眠了: 6 秒 休眠了: 7 秒 休眠了: 8 秒 休眠了: 9 秒 休眠了: 10 秒 休眠了: 11 秒 休眠了: 12 秒 休眠了: 13 秒 休眠了: 14 秒 休眠了: 15 秒 休眠了: 16 秒 休眠了: 17 秒 休眠了: 18 秒 休眠了: 19 秒 休眠了: 20 秒 休眠了: 21 秒 休眠了: 22 秒 退出: terminated 開(kāi)始退出... 執(zhí)行清理... 結(jié)束退出...通過(guò)標(biāo)準(zhǔn)輸出,我們的程序接受到了SIGTERM信號(hào),并執(zhí)行了一些退出工作。
shell格式 如下:
ENTRYPOINT "/app/bin/your-app arg1 arg2"Shell格式將您的入口點(diǎn)作為 /bin/sh -c 的子命令來(lái)運(yùn)行。
示例:
代碼不變,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重復(fù)上面的步驟,最終觀察到的結(jié)果如下:
動(dòng)了程序 休眠了: 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 開(kāi)始退出... 執(zhí)行清理... 結(jié)束退出...shell格式,我們的主程序也接受到了停機(jī)信號(hào),并做了退出工作。
為了驗(yàn)證,我們docker exec 到運(yùn)行的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我們的應(yīng)用進(jìn)程是1號(hào)進(jìn)程,所以我們依舊可以接收到SIGTERM信號(hào)。
當(dāng)我們的應(yīng)用程序直接是啟動(dòng)的入口,那么在接受停機(jī)信號(hào)方面,兩種格式并沒(méi)有什么區(qū)別。如果我們的啟動(dòng)腳本是一個(gè)類似于run.sh 的shell腳本,又會(huì)怎么樣那?
當(dāng)我們以一個(gè)shell腳本啟動(dòng)我們的應(yīng)用程序,那么我們的應(yīng)用程序不再是1號(hào)進(jìn)程,此時(shí),shell進(jìn)程并不會(huì)通知我們的應(yīng)用進(jìn)程退出,我們需要在shell腳本中做一些特殊的處理,才能實(shí)現(xiàn)同樣的效果。
需要做的就是告訴你的Shell用你的應(yīng)用程序替換自身。為此,shell具有 exec 命令(與前面講到的 exec 格式相似)。詳情見(jiàn)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)建新的鏡像之后,運(yùn)行該鏡像:
docker run stop-shell-runsh啟動(dòng)了程序 休眠了: 1 秒 休眠了: 2 秒 休眠了: 3 秒然后進(jìn)入到容器中執(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可以看到雖然我們的啟動(dòng)腳本是run.sh,但是經(jīng)過(guò)exec 之后,應(yīng)用程序成為了1號(hào)進(jìn)程。
停止運(yùn)行容器查看停機(jī)狀況:
docker stop 97adce7dd7e4然后可以看到容器有如下輸出:
休眠了: 104 秒 休眠了: 105 秒 休眠了: 106 秒 休眠了: 107 秒 休眠了: 108 秒 休眠了: 109 秒 休眠了: 110 秒 休眠了: 111 秒 休眠了: 112 秒 休眠了: 113 秒 休眠了: 114 秒 休眠了: 115 秒 休眠了: 116 秒 休眠了: 117 秒 退出: terminated 開(kāi)始退出... 執(zhí)行清理... 結(jié)束退出...- 監(jiān)聽(tīng)了錯(cuò)誤的信號(hào)
并不是所有的代碼框架都支持SIGTERM,比如Python的生態(tài)中,經(jīng)常是SIGINT。
例如:
try:do_work() except KeyboardInterrupt:cleanup()所以默認(rèn)是發(fā)送SIGTERM信號(hào),我們依舊可以設(shè)置成其他的信號(hào)。
最簡(jiǎn)單的解決方法是在Dockerfile中添加一行:
STOPSIGNAL SIGINT雖然我們將應(yīng)用程序作為1號(hào)進(jìn)程,可以接收到信號(hào),但是也帶來(lái)其他的問(wèn)題,比如僵尸進(jìn)程。該問(wèn)題在docker使用過(guò)程中很普遍存在。大家可以參考我另外一篇文章--避免在Docker鏡像下將NodeJS作為PID 1運(yùn)行。最佳實(shí)踐
使用 init 系統(tǒng)。這里我們推薦使用 tini。
Tini是你可能想到的最簡(jiǎn)單的 init。 Tini所做的全部工作就是span出子進(jìn)程,并等待它退出,同時(shí)收獲僵尸進(jìn)程并執(zhí)行信號(hào)轉(zhuǎn)發(fā)。
使用 tini 有以下好處:
- 它可以保護(hù)您免受意外創(chuàng)建僵尸進(jìn)程的軟件的侵害,因?yàn)榻┦M(jìn)程可能(隨著時(shí)間的推移!)使整個(gè)系統(tǒng)缺乏PID(并使其無(wú)法使用)。
- 它可確保默認(rèn)信號(hào)處理程序適用于您在Docker鏡像中運(yùn)行的軟件。例如,對(duì)于Tini,即使您沒(méi)有顯式安裝信號(hào)處理程序,SIGTERM也會(huì)正確終止您的進(jìn)程。
- 它完全透明地執(zhí)行!沒(méi)有Tini的Docker鏡像將與Tini一起使用,而無(wú)需進(jìn)行任何更改。
示例:
新的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 .運(yùn)行tini鏡像:
$ docker run stop-tini啟動(dòng)了程序 休眠了: 1 秒 休眠了: 2 秒 休眠了: 3 秒 休眠了: 4 秒 休眠了: 5 秒 休眠了: 6 秒 休眠了: 7 秒...此時(shí)在另外一個(gè)終端執(zhí)行 docker exec 進(jìn)入到容器中,并執(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此時(shí)可以看到,tini是1號(hào)進(jìn)程,我們的應(yīng)用程序是1號(hào)進(jìn)程的子進(jìn)程(7號(hào))。
停止該容器:
docker stop a727bd6617f4最終我們的運(yùn)行容器有以下輸出:
休眠了: 82 秒 休眠了: 83 秒 休眠了: 84 秒 休眠了: 85 秒 休眠了: 86 秒 退出: terminated 開(kāi)始退出... 執(zhí)行清理... 結(jié)束退出...可以看到我們業(yè)務(wù)進(jìn)程雖然不是1號(hào)進(jìn)程,但是也接受到了停機(jī)信號(hào)。
當(dāng)然這一切都?xì)w功于tini,tini將信號(hào)轉(zhuǎn)發(fā)到了我們的應(yīng)用程序。
總結(jié)
以上是生活随笔為你收集整理的中的stop_谈谈stop容器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: antd动态隐藏表格中的一列_有很多ex
- 下一篇: 个人手机银行怎么登录