你知道如何在docker容器中捕獲信號么

你知道如何在docker容器中捕獲信號么

想必大家一定使用過docker stop命令停止正在運(yùn)行中的容器,有時我們還可能使用docker kill命令強(qiáng)行關(guān)閉容器或者把某個信號傳遞給容器中的進(jìn)程。

實際上我們進(jìn)行的這些操作,本質(zhì)上都是通過從主機(jī)向容器發(fā)送信號實現(xiàn)主機(jī)與容器中程序的交互。舉個例子來說,比如我們向容器中的應(yīng)用發(fā)送一個重新加載信號,那么容器中的應(yīng)用程序在接到信號后就會執(zhí)行相應(yīng)的處理程序完成重新加載配置文件的任務(wù)。

信號(linux)

信號是一種進(jìn)程間通信的形式。一個信號就是內(nèi)核發(fā)送給進(jìn)程的一個消息,告訴進(jìn)程發(fā)生了某種事件。當(dāng)一個信號被發(fā)送給一個進(jìn)程后,進(jìn)程會立即中斷當(dāng)前的執(zhí)行流并開始執(zhí)行信號的處理程序(這么說不太準(zhǔn)確,信號是在特定的時機(jī)被處理)。如果沒有為這個信號指定處理程序,就執(zhí)行默認(rèn)的處理程序。
進(jìn)程需要為自己感興趣的信號注冊處理程序,比如為了能讓程序優(yōu)雅的退出(接到退出的請求后能夠?qū)Y源進(jìn)行清理)一般程序都會處理 SIGTERM 信號。與 SIGTERM 信號不同,SIGKILL 信號會粗暴的結(jié)束一個進(jìn)程。因此我們的應(yīng)用應(yīng)該實現(xiàn)這樣的目錄:捕獲并處理 SIGTERM 信號,從而優(yōu)雅的退出程序。如果我們失敗了,用戶就只能通過 SIGKILL 信號這一終極手段了。除了 SIGTERM 和 SIGKILL ,還有像 SIGUSR1 這樣的專門支持用戶自定義行為的信號。下面的代碼簡單的說明在 nodejs 中如何為一個信號注冊處理程序:

process.on('SIGTERM',?function()?{ ??console.log('shutting?down...'); });

關(guān)于信號的更多信息,筆者在《linux kill 命令》一文中有所提及,這里不再贅述。

容器中的信號

Docker 的 stop 和 kill 命令都是用來向容器發(fā)送信號的。注意,只有容器中的 1 號進(jìn)程能夠收到信號,這一點非常關(guān)鍵!
stop 命令會首先發(fā)送 SIGTERM 信號,并等待應(yīng)用優(yōu)雅的結(jié)束。如果發(fā)現(xiàn)應(yīng)用沒有結(jié)束(用戶可以指定等待的時間),就再發(fā)送一個 SIGKILL 信號強(qiáng)行結(jié)束程序。
kill 命令默認(rèn)發(fā)送的是 SIGKILL 信號,當(dāng)然你可以通過 -s 選項指定任何信號。

下面我們通過一個 nodejs 應(yīng)用演示信號在容器中的工作過程。創(chuàng)建 app.js 文件,內(nèi)容如下:

'use?strict';  var?http?=?require('http');  var?server?=?http.createServer(function?(req,?res)?{ ??res.writeHead(200,?{'Content-Type':?'text/plain'}); ??res.end('Hello?Worldn'); }).listen(3000,?'0.0.0.0');  console.log('server?started');  var?signals?=?{ ??'SIGINT':?2, ??'SIGTERM':?15 };  function?shutdown(signal,?value)?{ ??server.close(function?()?{ ????console.log('server?stopped?by?'?+?signal); ????process.exit(128?+?value); ??}); }  Object.keys(signals).forEach(function?(signal)?{ ??process.on(signal,?function?()?{ ????shutdown(signal,?signals[signal]); ??}); });

這個應(yīng)用是一個 http 服務(wù)器,監(jiān)聽端口 3000,為 SIGINT 和 SIGTERM 信號注冊了處理程序。接下來我們將介紹以不同的方式在容器中運(yùn)行程序時信號的處理情況。

應(yīng)用程序作為容器中的 1 號進(jìn)程

創(chuàng)建 Dockerfile 文件,把上面的應(yīng)用打包到鏡像中:

FROM?iojs:onbuild COPY?./app.js?./app.js COPY?./package.json?./package.json EXPOSE?3000ENTRYPOINT?["node",?"app"]

請注意 ENTRYPOINT 指令的寫法,這種寫法會讓 node 在容器中以 1 號進(jìn)程的身份運(yùn)行。

接下來創(chuàng)建鏡像:

$?docker?build?--no-cache?-t?signal-app?-f?Dockerfile?.

然后啟動容器運(yùn)行應(yīng)用程序:

請注意?ENTRYPOINT?指令的寫法,這種寫法會讓?node?在容器中以?1?號進(jìn)程的身份運(yùn)行。 接下來創(chuàng)建鏡像: $?docker?build?--no-cache?-t?signal-app?-f?Dockerfile?. 然后啟動容器運(yùn)行應(yīng)用程序: $?docker?run?-it?--rm?-p?3000:3000?--name="my-app"?signal-app 此時?node?應(yīng)用在容器中的進(jìn)程號為?1:

此時 node 應(yīng)用在容器中的進(jìn)程號為 1:

你知道如何在docker容器中捕獲信號么

現(xiàn)在我們讓程序退出,執(zhí)行命令:

$?docker?container?kill?--signal="SIGTERM"?my-app

此時應(yīng)用會以我們期望的方式退出:

你知道如何在docker容器中捕獲信號么

應(yīng)用程序不是容器中的 1 號進(jìn)程

創(chuàng)建一個啟動應(yīng)用程序的腳本文件 app1.sh,內(nèi)容如下:

#!/usr/bin/env?bash node?app

然后創(chuàng)建 Dockerfile1 文件,內(nèi)容如下:

FROM?iojs:onbuild COPY?./app.js?./app.js COPY?./app1.sh?./app1.sh COPY?./package.json?./package.json RUN?chmod?+x?./app1.sh EXPOSE?3000 ENTRYPOINT?["./app1.sh"]

接下來創(chuàng)建鏡像:

$?docker?build?--no-cache?-t?signal-app1?-f?Dockerfile1?.

然后啟動容器運(yùn)行應(yīng)用程序:

$?docker?run?-it?--rm?-p?3000:3000?--name="my-app1"?signal-app1

此時 node 應(yīng)用在容器中的進(jìn)程號不再是 1:

你知道如何在docker容器中捕獲信號么

現(xiàn)在給 my-app1 發(fā)送 SIGTERM 信號試試,已經(jīng)無法退出程序了!在這個場景中,應(yīng)用程序由 bash 腳本啟動,bash 作為容器中的 1 號進(jìn)程收到了 SIGTERM? 信號,但是它沒有做出任何的響應(yīng)動作。
我們可以通過:

$?docker?container?stop?my-app1 #?or $?docker?container?kill?--signal="SIGKILL"?my-app1

退出應(yīng)用,它們最終都是向容器中的 1 號進(jìn)程發(fā)送了 SIGKILL 信號。很顯然這不是我們期望的,我們希望程序能夠收到 SIGTERM? 信號優(yōu)雅的退出。

在腳本中捕獲信號

創(chuàng)建另外一個啟動應(yīng)用程序的腳本文件 app2.sh,內(nèi)容如下:

#!/usr/bin/env?bash set?-x  pid=0  #?SIGUSR1-handler my_handler()?{ ??echo?"my_handler" }  #?SIGTERM-handler term_handler()?{ ??if?[?$pid?-ne?0?];?then ????kill?-SIGTERM?"$pid" ????wait?"$pid" ??fi ??exit?143;?#?128?+?15?--?SIGTERM } #?setup?handlers #?on?callback,?kill?the?last?background?process,?which?is?`tail?-f?/dev/null`?and?execute?the?specified?handler trap?'kill?${!};?my_handler'?SIGUSR1 trap?'kill?${!};?term_handler'?SIGTERM  #?run?application node?app?& pid="$!"  #?wait?forever while?true do ??tail?-f?/dev/null?&?wait?${!} done

這個腳本文件在啟動應(yīng)用程序的同時可以捕獲發(fā)送給它的 SIGTERM 和 SIGUSR1 信號,并為它們添加了處理程序。其中 SIGTERM 信號的處理程序就是向我們的 node 應(yīng)用程序發(fā)送 SIGTERM 信號。

然后創(chuàng)建 Dockerfile2 文件,內(nèi)容如下:

FROM?iojs:onbuild COPY?./app.js?./app.js COPY?./app2.sh?./app2.sh COPY?./package.json?./package.json RUN?chmod?+x?./app2.sh EXPOSE?3000 ENTRYPOINT?["./app2.sh"]

接下來創(chuàng)建鏡像:

$?docker?build?--no-cache?-t?signal-app2?-f?Dockerfile2?.

然后啟動容器運(yùn)行應(yīng)用程序:

$?docker?run?-it?--rm?-p?3000:3000?--name="my-app2"?signal-app2

此時 node 應(yīng)用在容器中的進(jìn)程號也不是 1,但是它卻可以接收到 SIGTERM 信號并優(yōu)雅的退出了:

你知道如何在docker容器中捕獲信號么

結(jié)論

容器中的 1 號進(jìn)程是非常重要的,如果它不能正確的處理相關(guān)的信號,那么應(yīng)用程序退出的方式幾乎總是被強(qiáng)制殺死而不是優(yōu)雅的退出。究竟誰是 1 號進(jìn)程則主要由 EntryPoint, CMD, RUN 等指令的寫法決定,所以這些指令的使用是很有講究的。

相關(guān)推薦:docker入門教程

以上就是你知道如何在

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊14 分享