服務(wù)器維護(hù)docker容器如何優(yōu)雅的終止詳解
2020-05-27 17:34 作者:艾銻無(wú)限 瀏覽量:
如何做好服務(wù)器維護(hù)?北京艾銻無(wú)限科技與你談?wù)処T人員必須知道的服務(wù)器維護(hù)信息
在
服務(wù)器維護(hù)過(guò)程中,我們?nèi)粘5捻?xiàng)目當(dāng)中,這是我們經(jīng)常需要面對(duì)和處理的問(wèn)題:
服務(wù)器維護(hù)場(chǎng)景A:假如我們打包在容器中的程序,提供HTTP方式的服務(wù),負(fù)責(zé)處理各種HTTP requests并返回結(jié)果,我們必然希望在容器被停掉的時(shí)候,能夠讓程序有時(shí)間把已經(jīng)在處理中的請(qǐng)求繼續(xù)處理完畢,并返回結(jié)果給客戶(hù)端。
服務(wù)器維護(hù)場(chǎng)景B:又比如我們打包在容器中的程序,負(fù)責(zé)寫(xiě)入數(shù)據(jù)到某個(gè)數(shù)據(jù)文件中,我們希望程序能夠在容器被停掉的時(shí)候,有時(shí)間把內(nèi)存中緩存的數(shù)據(jù)持久化到存儲(chǔ)設(shè)備中,以防數(shù)據(jù)丟失。
服務(wù)器維護(hù)場(chǎng)景C:再比如現(xiàn)在流行的微服務(wù)架構(gòu)中,一般會(huì)有服務(wù)發(fā)現(xiàn)的機(jī)制,也即每一個(gè)微服務(wù)在啟動(dòng)之后,都會(huì)主動(dòng)把自己的地址信息注冊(cè)到服務(wù)發(fā)現(xiàn)模塊當(dāng)中,讓其他的服務(wù)可以知道自己的存在。而在容器被停掉的時(shí)候,微服務(wù)需要即時(shí)從服務(wù)發(fā)現(xiàn)模塊中注銷(xiāo)自己,以防止從API Gateway而來(lái)的請(qǐng)求被錯(cuò)
誤的路由到了已經(jīng)被停止掉的微服務(wù)。
如上的各種場(chǎng)景中,都要求打包在容器中的應(yīng)用程序能夠被優(yōu)雅的終止(也即gracefully shutdown),這種gracefully shutdown的方式,允許程序在容器被停止的時(shí)候,有一定時(shí)間做一些后續(xù)處理操作,這也是我們需要進(jìn)一步探討的話(huà)題。
服務(wù)器維護(hù)提示:docker stop 與 docker kill 的區(qū)別
Docker本身提供了兩種終止容器運(yùn)行的方式,即docker stop與docker kill。
服務(wù)器維護(hù)要點(diǎn):docker stop
先來(lái)說(shuō)說(shuō)docker stop吧,當(dāng)我們用docker stop命令來(lái)停掉容器的時(shí)候,docker默認(rèn)會(huì)允許容器中的應(yīng)用程序有10秒的時(shí)間用以終止運(yùn)行。所以我們查看docker stop命令幫助的時(shí)候,會(huì)有如下的提示:
1
2
3
4
5
6 |
→ 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í)行的時(shí)候,會(huì)先向容器中PID為1的進(jìn)程發(fā)送系統(tǒng)信號(hào)SIGTERM,然后等待容器中的應(yīng)用程序終止執(zhí)行,如果等待時(shí)間達(dá)到設(shè)定的超時(shí)時(shí)間,或者默認(rèn)的10秒,會(huì)繼續(xù)發(fā)送SIGKILL的系統(tǒng)信號(hào)強(qiáng)行kill掉進(jìn)程。在容器中的應(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ì)去處理它。在使用docker stop命令的時(shí)候,我們唯一能控制的是超時(shí)時(shí)間,比如設(shè)置為20秒超時(shí):
1 |
docker stop --time=20 container_name |
服務(wù)器維護(hù)要點(diǎn):docker kill
接著我們來(lái)看看docker kill命令,默認(rèn)情況下,docker kill命令不會(huì)給容器中的應(yīng)用程序有任何gracefully shutdown的機(jī)會(huì)。它會(huì)直接發(fā)出SIGKILL的系統(tǒng)信號(hào),以強(qiáng)行終止容器中程序的運(yùn)行。通過(guò)查看docker kill命令的幫助,我們可以看到,除了默認(rèn)發(fā)送SIGKILL信號(hào)外,還允許我們發(fā)送一些自定義的系統(tǒng)信號(hào):
1
2
3
4
5
6 |
→ 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信號(hào),我們可以這樣來(lái)實(shí)現(xiàn):
1 |
docker kill --signal=SIGINT container_name |
與docker stop命令不一樣的地方在于,docker kill沒(méi)有任何的超時(shí)時(shí)間設(shè)置,它會(huì)直接發(fā)送SIGKILL信號(hào),以及用戶(hù)通過(guò)signal參數(shù)指定的其他信號(hào)。
其實(shí)不難看出,docker stop命令,更類(lèi)似于Linux系統(tǒng)中的kill命令,二者都是發(fā)送系統(tǒng)信號(hào)SIGTERM。而docker kill命令,更像是Linux系統(tǒng)中的kill -9或者是kill -SIGKILL命令,用來(lái)發(fā)送SIGKILL信號(hào),強(qiáng)行終止進(jìn)程。
服務(wù)器維護(hù)提示:在程序中接收并處理信號(hào)
了解了docker stop與docker kill的區(qū)別,我們能夠知道,docker kill適合用來(lái)強(qiáng)行終止程序并實(shí)現(xiàn)快速停止容器。而如果希望程序能夠gracefully shutdown的話(huà),docker stop才是不二之選。這樣,我們可以讓程序在接收到SIGTERM信號(hào)后,有一定的時(shí)間處理、保存程序執(zhí)行現(xiàn)場(chǎng),優(yōu)雅的退出程序。
接下來(lái)我們可以寫(xiě)一個(gè)簡(jiǎn)單的Go程序來(lái)實(shí)現(xiàn)信號(hào)的接收與處理,程序在啟動(dòng)過(guò)后,會(huì)一直阻塞并監(jiān)聽(tīng)系統(tǒng)信號(hào),直到監(jiān)測(cè)到對(duì)應(yīng)的系統(tǒng)信號(hào)后,輸出控制臺(tái)并退出執(zhí)行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
// 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...")
} |
接下來(lái)
服務(wù)器維護(hù)要點(diǎn):使用交叉編譯的方式來(lái)編譯程序,讓程序可以在Linux下運(yùn)行:
1 |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o graceful
|
編譯好之后,我們還需要打包程序到容器中運(yùn)行。于是,我們還得有個(gè)Dockerfile。在這里,我們選擇使用體積小又輕盈的alpine鏡像作為基礎(chǔ)鏡像,打包這個(gè)Go程序:
1
2
3
4 |
from alpine:latest
MAINTAINER Timothy
ADD graceful /graceful
CMD ["/graceful"]
|
服務(wù)器維護(hù)要點(diǎn):這里需要避開(kāi)的一個(gè)坑,是Dockerfile中CMD命令的用法。
服務(wù)器維護(hù)要點(diǎn):CMD命令有兩種方式:
使用 CMD command param1 param2 這種方式,其實(shí)是以shell的方式運(yùn)行程序。最終程序被執(zhí)行時(shí),類(lèi)似于/bin/sh -c的方式運(yùn)行了我們的程序,這樣會(huì)導(dǎo)致/bin/sh以PID為1的進(jìn)程運(yùn)行,而我們的程序只不過(guò)是它fork/execs出來(lái)的子進(jìn)程而已。前面我們提到過(guò)docker stop的SIGTERM信號(hào)只是發(fā)送給容器中PID為1的進(jìn)程,而這樣,我們的程序就沒(méi)法接收和處理到信號(hào)了。
使用 CMD [“executable”,”param1”,”param2”] 這種方式啟動(dòng)程序,才是我們想要的,這種方式執(zhí)行和啟動(dòng)時(shí),我們的程序會(huì)被直接啟動(dòng)執(zhí)行,而不是以shell的方式,這樣我們的程序就能以PID=1的方式開(kāi)始執(zhí)行了。
話(huà)題轉(zhuǎn)回來(lái),我們開(kāi)始執(zhí)行容器構(gòu)建操作,打包程序:
1 |
docker build -t registry.xiaozhou.net/graceful:latest .
|
打包過(guò)后的鏡像,才6MB左右:
1
2
3 |
λ Timothy [workspace/src/graceful] → docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.xiaozhou.net/graceful latest b2210a85ca55 20 hours ago 6.484 MB
|
啟動(dòng)并運(yùn)行容器:
1 |
λ Timothy [workspace/src/graceful] → docker run -d --name graceful b2210a85 |
查看容器運(yùn)行狀態(tài):
1
2
3 |
λ 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)正常啟動(dòng):
1
2 |
λ Timothy [workspace/src/graceful] → docker logs graceful
Started... |
服務(wù)器維護(hù)要點(diǎn):接著我們要使用docker stop大法,看程序能否響應(yīng)SIGTERM信號(hào):
1
2 |
λ Timothy [workspace/src/graceful] → docker stop graceful
graceful |
最后,查看容器的日志,檢驗(yàn)輸出:
1
2
3
4 |
λ Timothy [workspace/src/graceful] → docker logs graceful
Started...
SIGTERM received!
Exiting... |
以上內(nèi)容為
北京艾銻無(wú)限科技發(fā)展有限公司IT外包服務(wù)公司為大家提供的
服務(wù)器維護(hù)小知識(shí),更多內(nèi)容請(qǐng)關(guān)注:
www.bjitwx.com。