【轉】docker容器如何優雅的終止詳解
docker容器如何優雅的終止詳解
最近公司在試驗如何將專案部署到docker容器中去,這其中涉及到一個技術環節,那就是如何讓docker容器優雅的終止。所謂優雅終止,指的就是程式在退出前有清理資源,儲存必要中間狀態,持久化記憶體資料的機會。下面通過這篇大家一起由簡單到複雜逐一考量一下。
前言
在Docker大行其道的今天,我們能夠非常方便的使用容器打包我們的應用程式,並且將它在我們的伺服器上部署並執行起來。但是,談論到如何停掉執行中的docker容器並正確的終止其中的程式,這就成為一個非常值得討論的話題了。
事實上,在我們日常的專案當中,這是我們經常需要面對和處理的問題:
場景A:假如我們打包在容器中的程式,提供HTTP方式的服務,負責處理各種HTTP requests並返回結果,我們必然希望在容器被停掉的時候,能夠讓程式有時間把已經在處理中的請求繼續處理完畢,並返回結果給客戶端。
場景B:又比如我們打包在容器中的程式,負責寫入資料到某個資料檔案中,我們希望程式能夠在容器被停掉的時候,有時間把記憶體中快取的資料持久化到儲存裝置中,以防資料丟失。
場景C:再比如現在流行的微服務架構中,一般會有服務發現的機制,也即每一個微服務在啟動之後,都會主動把自己的地址資訊註冊到服務發現模組當中,讓其他的服務可以知道自己的存在。而在容器被停掉的時候,微服務需要即時從服務發現模組中登出自己,以防止從API Gateway而來的請求被錯誤的路由到了已經被停止掉的微服務。
如上的各種場景中,都要求打包在容器中的應用程式能夠被優雅的終止(也即gracefully shutdown),這種gracefully shutdown的方式,允許程式在容器被停止的時候,有一定時間做一些後續處理操作,這也是我們需要進一步探討的話題。
docker stop 與 docker kill 的區別
Docker本身提供了兩種終止容器執行的方式,即docker stop與docker kill。
docker stop
先來說說docker stop吧,當我們用docker stop命令來停掉容器的時候,docker預設會允許容器中的應用程式有10秒的時間用以終止執行。所以我們檢視docker stop命令幫助的時候,會有如下的提示:
?
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命令執行的時候,會先向容器中PID為1的程序傳送系統訊號SIGTERM,然後等待容器中的應用程式終止執行,如果等待時間達到設定的超時時間,或者預設的10秒,會繼續傳送SIGKILL的系統訊號強行kill掉程序。在容器中的應用程式,可以選擇忽略和不處理SIGTERM訊號,不過一旦達到超時時間,程式就會被系統強行kill掉,因為SIGKILL訊號是直接發往系統核心的,應用程式沒有機會去處理它。在使用docker stop命令的時候,我們唯一能控制的是超時時間,比如設定為20秒超時:
?
1
docker stop –time=20 container_name
docker kill
接著我們來看看docker kill命令,預設情況下,docker kill命令不會給容器中的應用程式有任何gracefully shutdown的機會。它會直接發出SIGKILL的系統訊號,以強行終止容器中程式的執行。通過檢視docker kill命令的幫助,我們可以看到,除了預設傳送SIGKILL訊號外,還允許我們傳送一些自定義的系統訊號:
?
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中的程式傳送SIGINT訊號,我們可以這樣來實現:
?
1
docker kill –signal=SIGINT container_name
與docker stop命令不一樣的地方在於,docker kill沒有任何的超時時間設定,它會直接傳送SIGKILL訊號,以及使用者通過signal引數指定的其他訊號。
其實不難看出,docker stop命令,更類似於Linux系統中的kill命令,二者都是傳送系統訊號SIGTERM。而docker kill命令,更像是Linux系統中的kill -9或者是kill -SIGKILL命令,用來發送SIGKILL訊號,強行終止程序。
在程式中接收並處理訊號
瞭解了docker stop與docker kill的區別,我們能夠知道,docker kill適合用來強行終止程式並實現快速停止容器。而如果希望程式能夠gracefully shutdown的話,docker stop才是不二之選。這樣,我們可以讓程式在接收到SIGTERM訊號後,有一定的時間處理、儲存程式執行現場,優雅的退出程式。
接下來我們可以寫一個簡單的Go程式來實現訊號的接收與處理,程式在啟動過後,會一直阻塞並監聽系統訊號,直到監測到對應的系統訊號後,輸出控制檯並退出執行。
?
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…”)
}
接下來使用交叉編譯的方式來編譯程式,讓程式可以在Linux下執行:
?
1
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o graceful
編譯好之後,我們還需要打包程式到容器中執行。於是,我們還得有個Dockerfile。在這裡,我們選擇使用體積小又輕盈的alpine映象作為基礎映象,打包這個Go程式:
?
1
2
3
4
from alpine:latest
MAINTAINER Timothy
ADD graceful /graceful
CMD [“/graceful”]
這裡需要避開的一個坑,是Dockerfile中CMD命令的用法。
CMD命令有兩種方式:
?
1
CMD /graceful
使用 CMD command param1 param2 這種方式,其實是以shell的方式執行程式。最終程式被執行時,類似於/bin/sh -c的方式運行了我們的程式,這樣會導致/bin/sh以PID為1的程序執行,而我們的程式只不過是它fork/execs出來的子程序而已。前面我們提到過docker stop的SIGTERM訊號只是傳送給容器中PID為1的程序,而這樣,我們的程式就沒法接收和處理到訊號了。
?
1
CMD [“/graceful”]
使用 CMD [“executable”,”param1”,”param2”] 這種方式啟動程式,才是我們想要的,這種方式執行和啟動時,我們的程式會被直接啟動執行,而不是以shell的方式,這樣我們的程式就能以PID=1的方式開始執行了。
話題轉回來,我們開始執行容器構建操作,打包程式:
?
1
docker build -t registry.xiaozhou.net/graceful:latest .
打包過後的映象,才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
啟動並執行容器:
?
1
λ Timothy [workspace/src/graceful] → docker run -d –name graceful b2210a85
檢視容器執行狀態:
?
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
檢視容器輸出,能看到程式已經正常啟動:
?
1
2
λ Timothy [workspace/src/graceful] → docker logs graceful
Started…
接著我們要使用docker stop大法,看程式能否響應SIGTERM訊號:
?
1
2
λ Timothy [workspace/src/graceful] → docker stop graceful
graceful
最後,檢視容器的日誌,檢驗輸出:
?
1
2
3
4
λ Timothy [workspace/src/graceful] → docker logs graceful
Started…
SIGTERM received!
Exiting…
總結
以上就是這篇文章的全部內容了,用docker kill命令,可以簡單粗暴的終止docker容器中執行的程式,但是想要優雅的終止掉的話,我們需要使用docker stop命令,並且在程式中多花一些功夫來處理系統訊號,這樣能保證程式不被粗暴的終止掉,從而實現gracefully shutdown。希望本文的內容對大家的學習或者工作能有所幫助,如果有疑問大家可以留言交流。