利用 trap 在 docker 容器優雅關閉前執行環境清理的方案

當一個運行中的容器被終止時,如何能夠執行一些預定義的操作,比如在容器徹底退出之前清理環境。這是一種類似於 pre stop 的鉤子體驗。但 docker 本身無法提供這種能力,本文結合 Linux 內置命令 trap ,實現在容器優雅關閉之前,可以執行自定義的操作。

當一個運行中的容器被終止時,如何能夠執行一些預定義的操作,比如在容器徹底退出之前清理環境。這是一種類似於 pre stop 的鉤子體驗。但 docker 本身無法提供這種能力,本文結合 Linux 內置命令 trap ,實現在容器優雅關閉之前,可以執行自定義的操作。

如何關閉容器

我瞭解有三種方式可以關閉一個正在運行中的容器,三者都是由 docker 命令行發起的。

  • 第一種是較為優雅的方式 docker stop ContainerID
  • 第二種看起來就比較武斷 docker rm -f ContainerID
  • 第三種用的人會少很多 docker kill --signal=KILL ContainerID

docker 的設計者自然不會平白無故的設計三種命令組合來做關閉容器這件事,三種方式都應該在什麼場景下被使用呢?

這三種終止容器的方式之間是略有不同的,在講解這些不同之前,需要提及一些看似和容器不相關的知識點——SIGNAL 。

進程與信號

用戶是可以通過發送信號,來和進程通信的。

基本上每一個運維工程師都執行過如下命令來殺死一個進程:

kill -9 PID

這個命令看起來恰如其分,我 “殺死” 瞭一個進程,但是,為什麼是 “-9” ?

9 是信號 SIGKILL 的代號,上述命令實際上是向對應的進程發送瞭一個信號,一個可以殺死進程的信號。

kill 命令的真正意義,是向進程發送指定的信號,除瞭SIGKILL(9) 之外,還可以發送其他多種信號:

root@ubuntuserver:~# kill –help

kill: kill [-s sigspec | -n signum | -sigspec] pid | jobspec … or kill -l [sigspec]

    Send a signal to a job.

root@ubuntuserver:~# kill -l

 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP

 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1

11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM

16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP

21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ

26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR

31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3

38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8

43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13

48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12

53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7

58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2

63) SIGRTMAX-1 64) SIGRTMAX

我無意去詳解每一個信號的意義,我的功力還差得遠,在這裡隻揀取和我們主題相關的知識來進行闡述。

有兩個信號和我們的主題相關, SIGTERM. SIGKILL

信號名稱 代號 可否被捕獲或忽略
SIGTERM 15 可以
SIGKILL 9 不可以

SIGTERMkill 命令默認發送的信號。當用戶請求終止進程時,會產生SIGTERM信號。SIGTERM信號可以被捕獲或無視。這允許該進程在結束前釋放掉所占用的資源並保存其狀態。

SIGKILL 發送SIGKILL信號到一個進程可以使其立即終止(KILL)。與SIGTERM不同的是,這個信號不能被捕獲或忽略,接收過程在接收到這個信號時不能執行任何清理。但有時候 kill -9 並非一定可以殺死進程,釋放資源。還是有一些特殊情況:

  • 僵屍進程不能被殺死,因為它們已經死瞭,正在等待它們的父進程來收獲它們。
  • 處於阻塞狀態的進程不會死亡,直到它們再次醒來init 進程是特殊的:
  • init不接收任何它不打算處理的信號,因此它會忽略SIGKILL。這條規則有一個例外,Linux 上的 init 如果被 ptrace 瞭,那麼它是可以接收 SIGKILL 並被殺死的。
  • 處於不可中斷的睡眠的進程即使發送瞭SIGKILL,也有可能不會終止(並釋放其資源)。這是少數 Unix 系統必須重新啟動才能解決臨時軟件問題的幾種情況之一。

容器與信號

容器的本質,是一組被封裝起來的進程。所以通過開頭講到的三種命令行方式關閉一個運行中的容器,其本質也是在通過發送信號的方式與容器中的進程進行交互,使之被 “殺死” 的過程。

  • docker stop

執行 docker stop ContainerID ,會向容器中的主進程先發送一個 SIGTERM 信號,在一段時間的寬限期後,發送 SIGKILL 信號徹底殺死容器。

Docker 手冊原文如下:

The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL

  • docker rm -f

執行 docker rm -f ContainerID ,會向容器中的主進程直接發送SIGKILL 信號,在容器殺死之後,也會把容器刪除掉。從刪除容器這個操作看來,這個命令是用來刪除一個已停止的容器,而非用於停止運行中的容器。

  • docker kill

執行 docker kill --signal=KILL ContainerID ,是專門向容器主進程發送各種自定義信號的方式。換言之,它就是面向容器的 kill 命令。當前命令是在向容器主進程發送一個 SIGKILL 信號。

通過比對,docker rm -f ContainerID 這種方式是不應該用於停止運行中容器的。而剩餘兩種方式之間, docker stop ContainerID 也明顯要優雅一些,它既可以保證容器會被最終殺死,也會提供 SIGTERM 供用戶後續捕獲處理。

接下來終於要進入正題瞭。

捕獲信號並處理

信號 SIGTERM 是一種可以被捕獲的信號。當容器主進程捕獲到這個信號之後,可以觸發事先設計好的邏輯,在徹底退出之前完成預定的任務。比如可以執行環境的清理、數據的保存、關閉其他不受主進程控制的進程等等。在某些場景下,這種需求非常突出。

Linux 提供內置的 trap 命令,負責捕獲信號,並確保在進程徹底退出前,執行某些任務。

root@ubuntuserver:~# trap –help

trap: trap [-lp] [[arg] signal_spec …]

    Trap signals and other events.

其基本的使用方式如下:

trap do_some_things SIGSPEC

思路已經清晰瞭,我們需要在容器的啟動腳本中,加入 trap 指令,來完成容器在退出前需要做的所有事情。

以下是一個腳本示例,這個腳本被作為容器的入口(ENTRYPOINT)執行。

#!/bin/bash

function clean_up_term {
  rm -rf /data/tmp
  echo "clean_up_term in execution"
}

trap clean_up_term SIGTERM

for ((i=1;i<=1000;i++))
  do
    echo "Wait for $i"
    sleep 1
  done 

容器啟動後,從其他終端執行瞭 docker stop ContainerID 命令,可以觀察到以下結果。

guox@MacBook-Pro-For-Guox: /Users/guox/GitHub/test-plugin git:(master) ✗ 

➜   docker run -ti –name=clean -v $(pwd)/data:/data clean 

Wait for 1

Wait for 2

Wait for 3

Wait for 4

Wait for 5

Wait for 6

Wait for 7

Wait for 8

Wait for 9

Wait for 10

Wait for 11

Wait for 12

Wait for 13

clean_up_term in execution

Wait for 14

Wait for 15

Wait for 16

Wait for 17

Wait for 18

Wait for 19

Wait for 20

Wait for 21

Wait for 22

Wait for 23

guox@MacBook-Pro-For-Guox: /Users/guox/GitHub/test-plugin git:(master) ✗ 

信號 SIGTERM 的確被容器捕獲,並進行瞭相關的清理操作。 docker stop ContainerID 提供瞭一段寬限期,所以在執行瞭清理操作後,容器主進程還是繼續執行瞭一會才退出。

到此這篇關於利用 trap 在 docker 容器優雅關閉前執行環境清理的文章就介紹到這瞭,更多相關docker 容器執行環境清理內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: