精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

一次 Docker 容器內(nèi)大量僵尸進(jìn)程排查分析

安全
前段時(shí)間線上的一個(gè)使用 Google Puppeteer 生成圖片的服務(wù)炸了,每個(gè) docker 容器內(nèi)都有幾千個(gè)孤兒僵死進(jìn)程沒(méi)有回收。

前段時(shí)間線上的一個(gè)使用 Google Puppeteer 生成圖片的服務(wù)炸了,每個(gè) docker 容器內(nèi)都有幾千個(gè)孤兒僵死進(jìn)程沒(méi)有回收,如下圖所示。

這篇文章比較長(zhǎng),主要就講了下面這幾個(gè)問(wèn)題。

  • 什么情況下會(huì)出現(xiàn)僵尸進(jìn)程、孤兒進(jìn)程
  • Puppeteer 工作過(guò)程啟動(dòng)的進(jìn)程與線上事故分析
  • PID 為 1 的進(jìn)程有什么特殊的地方
  • 為什么 node/npm 不應(yīng)該作為鏡像中 PID 為 1 的進(jìn)程
  • 為什么 Bash 可以作為 PID 為 1 的進(jìn)程,以及它做 PID 為 1 的進(jìn)程有什么缺陷
  • 鏡像中比較推薦的 init 進(jìn)程的做法是什么

Puppeteer 是一個(gè) node 庫(kù),是 Chrome 官方提供的無(wú)界面 chrome 工具(headless chrome),它提供了操作 Chrome API 的方式,允許開(kāi)發(fā)者在程序中啟動(dòng) chrome 進(jìn)程,調(diào)用 JS 的 API 實(shí)現(xiàn)頁(yè)面加載、數(shù)據(jù)爬取、web 自動(dòng)化測(cè)試等功能。

本案例中使用的場(chǎng)景是使用 Puppeteer 加載 html,隨后截圖生成一張分銷(xiāo)海報(bào)的圖片。文章分析了這個(gè)問(wèn)題背后的原因,接下來(lái)開(kāi)始正式的內(nèi)容。

進(jìn)程

每個(gè)進(jìn)程都有一個(gè)唯一的標(biāo)識(shí),稱(chēng)為 pid,pid 是一個(gè)非負(fù)的整數(shù)值,使用 ps 命令可以查看,在我的 Mac 電腦上執(zhí)行 ps -ef 可以看到當(dāng)前運(yùn)行的所有進(jìn)程,如下所示。

 

  1. UID   PID  PPID   C STIME   TTY           TIME CMD 
  2.   0     1     0   0 六04下午 ??        23:09.18 /sbin/launchd 
  3.   0    39     1   0 六04下午 ??         0:49.66 /usr/sbin/syslogd 
  4.   0    40     1   0 六04下午 ??         0:13.00 /usr/libexec/UserEventAgent (System) 

其中 PID 是表示進(jìn)程號(hào)。

系統(tǒng)中每個(gè)進(jìn)程都有對(duì)應(yīng)的父進(jìn)程,上面 ps 輸出中的 PPID 就表示進(jìn)程的父進(jìn)程號(hào)。最頂層的進(jìn)程的 PID 為 1,PPID 為 0。

打開(kāi) iTerm,在終端中執(zhí)行一個(gè)命令,比如 "ls",實(shí)際上系統(tǒng)會(huì)創(chuàng)建新的 iTerm 子進(jìn)程,這個(gè) iTerm 進(jìn)程又創(chuàng)建了 zsh 子進(jìn)程。在 zsh 中輸入的 ls 命令,則是 zsh 進(jìn)程又啟動(dòng)了一個(gè) ls 子進(jìn)程。在 iTerm 中輸入 ls 命令過(guò)程的進(jìn)程關(guān)系如下所示。

 

  1. UID   PID  PPID   C STIME   TTY           TIME CMD 
  2.  501   321     1   0 六04下午 ??        61:01.45 /Applications/iTerm.app/Contents/MacOS/iTerm2 -psn_0_81940 
  3.  501 97920   321   0  8:02上午 ttys039    0:00.07 /Applications/iTerm.app/Contents/MacOS/iTerm2 --server login -fp arthur 
  4.    0 97921 97920   0  8:02上午 ttys039    0:00.03 login -fp arthur 
  5.  501 97922 97921   0  8:02上午 ttys039    0:00.29 -zsh 
  6.  501 98369 97922   0  8:14上午 ttys039    0:00.00 ./a.out 

進(jìn)程與 fork

前面提到的父進(jìn)程“創(chuàng)建”子進(jìn)程,更嚴(yán)謹(jǐn)?shù)拿枋鍪?fork(孵化、衍生)。下面來(lái)看一個(gè)實(shí)際的例子,新建一個(gè) fork_demo.c 文件。

 

  1. #include <unistd.h> 
  2. #include <stdio.h> 
  3.  
  4. int main() { 
  5.   int ret = fork(); 
  6.   if (ret) { 
  7.     printf("enter if block\n"); 
  8.   } else { 
  9.     printf("enter else block\n"); 
  10.   } 
  11.   return 0; 

執(zhí)行上的代碼,會(huì)輸出如下的語(yǔ)句。

 

  1. enter if block 
  2. enter else block 

可以看到 if、else 語(yǔ)句都被執(zhí)行了。

fork 調(diào)用

fork 是一個(gè)系統(tǒng)調(diào)用,它的方法聲明如下所示。

  1. pid_t fork(void); 

fork 調(diào)用完成后會(huì)生成一個(gè)新的子進(jìn)程,且父子進(jìn)程都從 fork 返回處繼續(xù)執(zhí)行。這里需要特別注意的是 fork 的返回值的含義,在父進(jìn)程和新的子進(jìn)程中,它們的含義不一樣。

  • 在父進(jìn)程中 fork 的返回值是新創(chuàng)建的子進(jìn)程 id
  • 在創(chuàng)建的子進(jìn)程中 fork 的返回值始終等于 0

因此可以通過(guò) fork 的返回值區(qū)分父子進(jìn)程,在運(yùn)行過(guò)程中可以使用 getpid 方法獲取當(dāng)前的進(jìn)程 id。fork 典型的使用方式如下所示。

 

  1. #include <unistd.h> 
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4.  
  5. int main() { 
  6.   printf("before fork, pid=%d\n", getpid()); 
  7.   pid_t childPid; 
  8.   switch (childPid = fork()) { 
  9.     case -1: { 
  10.       // fork 失敗 
  11.       printf("fork error, %d\n", getpid()); 
  12.       exit(1); 
  13.     } 
  14.     case 0: { 
  15.       // 子進(jìn)程代碼進(jìn)入到這里 
  16.       printf("in child process, pid=%d\n", getpid()); 
  17.       break; 
  18.     } 
  19.     default: { 
  20.       // 父進(jìn)程代碼進(jìn)入到這里 
  21.       printf("in parent process, pid=%d, child pid=%d\n", getpid(), childPid); 
  22.       break; 
  23.     } 
  24.   } 
  25.   return 0; 

執(zhí)行上面的代碼,輸出結(jié)果如下所示。

 

  1. before fork, pid=26070 
  2. in parent process, pid=26070, child pid=26071 
  3. in child process, pid=26071 

子進(jìn)程是父進(jìn)程的副本,子進(jìn)程擁有父進(jìn)程數(shù)據(jù)空間、堆、棧的復(fù)制副本 ,fork 采用了 copy-on-write 技術(shù),fork 操作幾乎瞬間可以完成。只有在子進(jìn)程修改了相應(yīng)的區(qū)域才會(huì)進(jìn)行真正的拷貝。

孤兒進(jìn)程:不能同年同月同日生,也不會(huì)同年同月同日死

接下來(lái)問(wèn)一個(gè)問(wèn)題,父進(jìn)程掛掉時(shí),子進(jìn)程會(huì)掛掉嗎?

想象現(xiàn)實(shí)中的場(chǎng)景,父親不在了,兒子還可以活嗎?答案是肯定的。對(duì)應(yīng)于進(jìn)程,父進(jìn)程退出時(shí),子進(jìn)程會(huì)繼續(xù)運(yùn)行,不會(huì)一起共赴黃泉。

一個(gè)父進(jìn)程已經(jīng)終止的進(jìn)程被稱(chēng)為孤兒進(jìn)程(orphan process)。操作系統(tǒng)這個(gè)大家長(zhǎng)是比較人性化的,沒(méi)有人管的孤兒進(jìn)程會(huì)被進(jìn)程 ID 為 1 的進(jìn)程接管。這個(gè) PID 為 1 的進(jìn)程后面還會(huì)再講到。

接下來(lái)對(duì)之前的代碼稍作修改,讓父進(jìn)程 fork 子進(jìn)程以后自殺退出,生成孤兒進(jìn)程。代碼如下所示。

 

  1. #include <unistd.h> 
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4.  
  5. int main() { 
  6.   printf("before fork, pid=%d\n", getpid()); 
  7.   pid_t childPid; 
  8.   switch (childPid = fork()) { 
  9.     case -1: { 
  10.       printf("fork error, %d\n", getpid()); 
  11.       exit(1); 
  12.     } 
  13.     case 0: { 
  14.       printf("in child process, pid=%d\n", getpid()); 
  15.       sleep(100000); // 子進(jìn)程 sleep 不退出 
  16.       break; 
  17.     } 
  18.     default: { 
  19.       printf("in parent process, pid=%d, child pid=%d\n", getpid(), childPid); 
  20.       exit(0); // 父進(jìn)程退出 
  21.     } 
  22.   } 
  23.   return 0; 

編譯運(yùn)行上面的代碼

  1. gcc fork_demo.c -o fork_demo; ./fork_demo 

輸出結(jié)果如下。

 

  1. before fork, pid=21629 
  2. in parent process, pid=21629, child pid=21630 
  3. in child process, pid=21630 

可以看到父進(jìn)程 id 為 21629, 生成的子進(jìn)程 id 為 21630。

使用 ps 查看當(dāng)前進(jìn)程信息,結(jié)果如下所示。

 

  1. UID        PID  PPID  C STIME TTY          TIME CMD 
  2. root         1     0  0 12月12 ?      00:00:53 /usr/lib/systemd/systemd --system --deserialize 21 
  3. ya       21630     1  0 19:26 pts/8    00:00:00 ./fork_demo 

可以看到此時(shí)孤兒子進(jìn)程 21630 的父 ID 已經(jīng)變?yōu)榱隧攲拥?ID 為 1 的進(jìn)程。

僵尸進(jìn)程

父進(jìn)程負(fù)責(zé)生,如果不負(fù)責(zé)養(yǎng),那就不是一個(gè)好父親。子進(jìn)程掛了,如果父進(jìn)程不給子進(jìn)程“收尸”(調(diào)用 wait/waitpid),那這個(gè)子進(jìn)程小可憐就變成了僵尸進(jìn)程。

新建一個(gè) make_zombie.c 文件,內(nèi)容如下。

 

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <unistd.h> 
  4.  
  5. int main() { 
  6.  
  7.   printf("pid %d\n", getpid()); 
  8.   int child_pid = fork(); 
  9.   if (child_pid == 0) { 
  10.     printf("-----in child process:  %d\n", getpid()); 
  11.     exit(0); 
  12.   } else { 
  13.     sleep(1000000); 
  14.   } 
  15.   return 0; 

編譯運(yùn)行上面的代碼,就可以生成一個(gè)進(jìn)程號(hào)為 22538 的僵尸進(jìn)程,如下所示。

 

  1. UID        PID  PPID  C STIME TTY          TIME CMD 
  2. ya       22537 20759  0 19:57 pts/8    00:00:00 ./make_zombie 
  3. ya       22538 22537  0 19:57 pts/8    00:00:00 [make_zombie] <defunct> 

CMD 名中的 defunct 表示這是一個(gè)僵尸進(jìn)程。

也使用 ps 命令查看進(jìn)程的狀態(tài),顯示為 "Z" 或者 "Z+" 表示這是一個(gè)僵尸進(jìn)程,如下所示。

 

  1. ps -ho pid,state -p 22538 
  2. 22538 Z 

子進(jìn)程退出后絕大部分資源已經(jīng)被釋放可供其他進(jìn)使用,但是內(nèi)核的進(jìn)程表中的槽位沒(méi)有釋放。

僵尸進(jìn)程有一個(gè)很神奇的特性,使用 kill -9 必殺信號(hào)都沒(méi)有辦法殺掉僵尸進(jìn)程,這樣的設(shè)計(jì)利弊參半,好的地方是父進(jìn)程可以總是有機(jī)會(huì)執(zhí)行 wait/waitpid 等命令收割子進(jìn)程,壞的地方是無(wú)法強(qiáng)制回收這種僵尸進(jìn)程。

PID 為 1 的進(jìn)程

Linux 中內(nèi)核初始化以后會(huì)啟動(dòng)系統(tǒng)的第一個(gè)進(jìn)程,PID 為 1,也可以稱(chēng)之為 init 進(jìn)程或者根(ROOT)進(jìn)程。在我的 Centos 機(jī)器上,這個(gè) init 進(jìn)程是 systemd,如下所示。

 

  1. UID        PID  PPID  C STIME TTY          TIME CMD 
  2. root         1     0  0 12月12 ?      00:00:54 /usr/lib/systemd/systemd --system --deserialize 21 

在我的 Mac 電腦上,這個(gè)進(jìn)程為 launchd,如下所示。

 

  1. UID   PID  PPID   C STIME   TTY           TIME CMD 
  2.   0     1     0   0 六04下午 ??        28:40.65 /sbin/launchd 

init 進(jìn)程有下面這幾個(gè)功能

  • 如果一個(gè)進(jìn)程的父進(jìn)程退出了,那么這個(gè) init 進(jìn)程便會(huì)接管這個(gè)孤兒進(jìn)程。
  • 如果一個(gè)進(jìn)程的父進(jìn)程未執(zhí)行 wait/waitpid 就退出了,init 進(jìn)程會(huì)接管子進(jìn)程并自動(dòng)調(diào)用 wait 方法,從而保證系統(tǒng)中的僵尸進(jìn)程可以被移除。
  • 傳遞信號(hào)給子進(jìn)程,這點(diǎn)后面會(huì)介紹。

為什么 Node.js 不適合做 Docker 鏡像中 PID 為 1 的進(jìn)程

在 Node.js 的官方最佳實(shí)踐里有寫(xiě)到 "Node.js was not designed to run as PID 1 which leads to unexpected behaviour when running inside of Docker."。下圖來(lái)自 github.com/nodejs/dock… 。

接下來(lái)會(huì)做兩個(gè)實(shí)驗(yàn):第一個(gè)實(shí)驗(yàn)是在 Centos 機(jī)器上,第二個(gè)實(shí)驗(yàn)是在 Docker 鏡像中

實(shí)驗(yàn)一:在 Centos 上,systemd 作為 PID 為 1 的進(jìn)程

下面來(lái)做一些測(cè)試,修改上面的代碼,將父進(jìn)程 sleep 的時(shí)間改短為 15s,新建一個(gè) make_zombie.c 文件,如下所示。

 

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <unistd.h> 
  4.  
  5. int main() { 
  6.   printf("pid %d\n", getpid()); 
  7.   int child_pid = fork(); 
  8.   if (child_pid == 0) { 
  9.     printf("-----in child process:  %d\n", getpid()); 
  10.     exit(0); 
  11.   } else { 
  12.     sleep(15); 
  13.     exit(0); 
  14.   } 

編譯生成可執(zhí)行文件 make_zombie。

  1. gcc make_zombie.c -o make_zombie 

然后新建一個(gè) run.js 代碼,內(nèi)部啟動(dòng)一個(gè)進(jìn)程運(yùn)行 make_zombie,如下所示。

 

  1. const { spawn } = require('child_process'); 
  2. const cmd = spawn('./make_zombie'); 
  3. cmd.stdout.on('data', (data) => { 
  4.     console.log(`stdout: ${data}`); 
  5. }); 
  6.  
  7. cmd.stderr.on('data', (data) => { 
  8.     console.error(`stderr: ${data}`); 
  9. }); 
  10.  
  11. cmd.on('close', (code) => { 
  12.     console.log(`child process exited with code ${code}`); 
  13. }); 
  14. setTimeout(function () { 
  15.     console.log("..."); 
  16. }, 1000000); 

執(zhí)行 node run.js 運(yùn)行這段 js 代碼,使用 ps -ef 查看進(jìn)程關(guān)系如下。

 

  1. UID        PID  PPID  C STIME TTY          TIME CMD 
  2. ya       19234 19231  0 12月20 ?       00:00:00 sshd: ya@pts/6 
  3. ya       19235 19234  0 12月20 pts/6   00:00:01 -zsh 
  4. ya       29513 19235  3 15:28 pts/6    00:00:00 node run.js 
  5. ya       29519 29513  0 15:28 pts/6    00:00:00 ./make_zombie 
  6. ya       29520 29519  0 15:28 pts/6    00:00:00 [make_zombie] <defunct> 

過(guò) 15s 以后,再次執(zhí)行 ps -ef 查詢當(dāng)前運(yùn)行的進(jìn)程,可以看到 make_zombie 相關(guān)進(jìn)程都不見(jiàn)了。

 

  1. UID        PID  PPID  C STIME TTY          TIME CMD 
  2. ya       19234 19231  0 12月20 ?       00:00:00 sshd: ya@pts/6 
  3. ya       19235 19234  0 12月20 pts/6   00:00:01 -zsh 
  4. ya       29513 19235  3 15:28 pts/6    00:00:00 node run.js 

這是因?yàn)?PID 為 29519 的 make_zombie 父進(jìn)程在 15s 以后退出,僵尸子進(jìn)程被托管到 init 進(jìn)程,這個(gè)進(jìn)程會(huì)調(diào)用 wait/waitfor 為這個(gè)僵尸收尸。

實(shí)驗(yàn)二:在 Docker 上,node 作為 PID 為 1 的進(jìn)程

將 make_zombie 可執(zhí)行文件和 run.js 打包為 .tar.gz 包,隨后新建一個(gè) Dockerfile,內(nèi)容如下。

 

  1. #指定基礎(chǔ)鏡像 
  2. FROM  registry.gz.cctv.cn/library/your_node_image:your_tag 
  3.  
  4. WORKDIR / 
  5.  
  6. #復(fù)制包文件到工作目錄,. 代表當(dāng)前目錄,也就是工作目錄 
  7. ADD test.tar.gz . 
  8.  
  9. #指定啟動(dòng)命令 
  10. CMD ["node""run.js"

執(zhí)行 docker build 命令構(gòu)建一個(gè)鏡像,在我的電腦上 Image ID 為 ab71925b5154, 執(zhí)行 docker run ab71925b5154,啟動(dòng) docker 鏡像,使用 docker ps 找到鏡像 CONTAINER ID,這里為 e37f7e3c2e39。隨即使用 docker exec 進(jìn)入到鏡像終端

  1. docker exec -it e37f7e3c2e39 /bin/bash 

執(zhí)行 ps 命令查看當(dāng)前的進(jìn)程狀況,如下所示。

 

  1. UID        PID  PPID  C STIME TTY          TIME CMD 
  2. root         1     0  1 07:52 ?        00:00:00 node run.js 
  3. root        12     1  0 07:52 ?        00:00:00 ./make_zombie 
  4. root        13    12  0 07:52 ?        00:00:00 [make_zombie] <defunct> 

等一段時(shí)間(15s),再次執(zhí)行 ps 查看當(dāng)前進(jìn)程,如下所示。

 

  1. UID        PID  PPID  C STIME TTY          TIME CMD 
  2. root         1     0  0 07:52 ?        00:00:00 node run.js 
  3. root        13     1  0 07:52 ?        00:00:00 [make_zombie] <defunct> 

可以看到 PID 為 13 的僵尸進(jìn)程已經(jīng)托管到 PID 為 1 的 node 進(jìn)程,但是沒(méi)有被回收。

這是 node 不適合做 init 進(jìn)程的最主要原因:無(wú)法回收僵尸進(jìn)程。

說(shuō)到 node,這里提一下 npm,npm 實(shí)際上是使用 npm 進(jìn)程啟動(dòng)了一個(gè)子進(jìn)程啟動(dòng)了 package.json 中 scripts 里寫(xiě)的啟動(dòng)腳本,示例 package.json 腳本如下所示。

 

  1.   "name""test-demo"
  2.   "version""1.0.0"
  3.   "description"""
  4.   "main""index.js"
  5.   "scripts": { 
  6.     "test""echo \"Error: no test specified\" && exit 1"
  7.     "start""node run.js" 
  8.   }, 
  9.   "keywords": [], 
  10.   "author"""
  11.   "license""ISC"
  12.   "dependencies": { 
  13.   } 

使用 npm run start 啟動(dòng),得到的進(jìn)程如下所示。

 

  1. ya       19235 19234  0 12月20 pts/6  00:00:01 -zsh 
  2. ya       32252 19235  0 16:32 pts/6    00:00:00 npm 
  3. ya       32262 32252  0 16:32 pts/6    00:00:00 node run.js 

與 node 一樣,npm 也不會(huì)處理僵尸子進(jìn)程回收。

線上問(wèn)題分析

我們線上出問(wèn)題的情況下使用 npm start 來(lái)啟動(dòng)一個(gè) Puppeteer 項(xiàng)目,每生成一次圖片便會(huì)創(chuàng)建 4 個(gè) chrome 相關(guān)的進(jìn)程,如下所示。

 

  1. └── chrome(1) 
  2.     ├── gpu-process(2) 
  3.     └── zygote(3) 
  4.         └── renderer(4) 

在圖片生成完成時(shí),chrome 主進(jìn)程退出,剩下的三個(gè)孤兒僵尸進(jìn)程被托管到頂層 npm 進(jìn)程下,但是 npm 進(jìn)程無(wú)力回收,所有每生成一次圖片便會(huì)新增三個(gè)僵尸進(jìn)程。在成千上萬(wàn)次圖片生成以后,系統(tǒng)中就充滿了僵尸進(jìn)程。

解決辦法

為了解決這個(gè)問(wèn)題,不能讓 node/npm 成為 init 進(jìn)程,讓有能力接管僵尸進(jìn)程的服務(wù)成為 init 進(jìn)程即可,有兩個(gè)解決辦法。

  • 使用 bash 啟動(dòng) node 或者 npm
  • 增加專(zhuān)門(mén)的 init 進(jìn)程,比如 tini

解決方式一:使用 bash 啟動(dòng) node

讓 bash 成為頂層進(jìn)程是比較快的一種方式,bash 進(jìn)程會(huì)負(fù)責(zé)回收僵尸進(jìn)程,修改 Dockerfile,如下所示。

 

  1. ADD test.tar.gz . 
  2. # CMD ["npm""run""start"
  3. CMD ["/bin/bash""-c""set -e && npm run start"

使用這種方式是比較簡(jiǎn)單,而且之前線上沒(méi)有出問(wèn)題正是因?yàn)橐婚_(kāi)始是使用這種 bash 方式啟動(dòng) node,后面有一個(gè)小兄弟為了統(tǒng)一啟動(dòng)命令將這個(gè)命令改為 npm run start,問(wèn)題才出現(xiàn)的。

但使用 bash 并非完美的方案,它有一個(gè)比較嚴(yán)重的問(wèn)題,bash 不會(huì)傳遞信號(hào)給它啟動(dòng)的進(jìn)程,優(yōu)雅停機(jī)等功能無(wú)法實(shí)現(xiàn)。

接下來(lái)做一個(gè)實(shí)驗(yàn),驗(yàn)證 bash 不會(huì)傳遞信號(hào)給子進(jìn)程的說(shuō)法,新建一個(gè) signal_test.c 文件,它處理 SIGQUIT、SIGTERM、SIGTERM 三個(gè)信號(hào),內(nèi)容如下。

 

  1. #include <signal.h> 
  2. #include <stdio.h> 
  3.  
  4. static void signal_handler(int signal_no) { 
  5.   if (signal_no == SIGQUIT) { 
  6.     printf("quit signal receive: %d\n", signal_no); 
  7.   } else if (signal_no == SIGTERM) { 
  8.     printf("term signal receive: %d\n", signal_no); 
  9.   } else if (signal_no == SIGTERM) { 
  10.     printf("interrupt signal receive: %d\n", signal_no); 
  11.   } 
  12.  
  13. int main() { 
  14.   printf("in main\n"); 
  15.  
  16.   signal(SIGQUIT, signal_handler); 
  17.   signal(SIGINT, signal_handler); 
  18.   signal(SIGTERM, signal_handler); 
  19.  
  20.   getchar(); 

在我 Centos 和 Mac 上運(yùn)行這個(gè) signal_test 程序時(shí),發(fā)送 kill -2、-3、-15 給這個(gè)程序,都會(huì)有對(duì)應(yīng)的打印輸出,表示收到了信號(hào)。如下所示。

 

  1. kill -15 47120 
  2. term signal receive: 15 
  3.  
  4. kill -3 47120 
  5. quit signal receive: 3 
  6.  
  7. kill -2 47120 
  8. interrupt signal receive: 2 

在 Docker 鏡像中使用 bash 啟動(dòng)這個(gè)程序時(shí),發(fā)送 kill 命令給 bash 以后,bash 并不會(huì)將信號(hào)傳遞給 signal_test 程序。在執(zhí)行 docker stop 以后,docker 會(huì)發(fā)送 SIGTERM(15) 信號(hào)給 bash,bash 并不會(huì)將這個(gè)信號(hào)傳遞給啟動(dòng)的應(yīng)用程序,只能等一段時(shí)間超時(shí),docker 會(huì)發(fā)送 kill -9 強(qiáng)制殺死這個(gè) docker 進(jìn)程,無(wú)法達(dá)到優(yōu)雅停機(jī)的功能。

于是有了下面的第二種解決方案。

解決方式二:使用專(zhuān)門(mén)的 init 進(jìn)程

Node.js 提供了兩種方案,第一種是使用 docker 官方的輕量級(jí) init 系統(tǒng),如下所示。

  1. docker run -it --init you_docker_image_id 

這種啟動(dòng)方式會(huì)以 /sbin/docker-init 為 PID 為 1 的 init 進(jìn)程,不會(huì)把 Dockerfile 中 CMD 作為第一個(gè)啟動(dòng)進(jìn)程。

以下面的 Dockerfile 內(nèi)容為例

 

  1. ... 
  2. CMD ["./signal_test"
  3. ... 

執(zhí)行 docker run -it --init image_id 啟動(dòng) docker 鏡像,此時(shí)鏡像內(nèi)的進(jìn)程如下所示。

 

  1. UID        PID  PPID  C STIME TTY          TIME CMD 
  2. root         1     0  0 15:30 pts/0    00:00:00 /sbin/docker-init -- /app/node-default 
  3. root         6     1  0 15:30 pts/0    00:00:00 ./signal_test 

可以看到 signal_test 程序作為 docker-init 的子進(jìn)程啟動(dòng)了。

在 docker stop 命令發(fā)送 SIGTERM 信號(hào)給鏡像以后,docker-init 進(jìn)程會(huì)將這個(gè)信號(hào)轉(zhuǎn)給 signal_test,這個(gè)應(yīng)用進(jìn)程就可以收到 SIGTERM 信號(hào)做自定義的處理,比如優(yōu)雅停機(jī)等。

除了 docker 的官方方案,Node.js 的最佳實(shí)踐還推薦了一個(gè) tini 這樣一個(gè) C 語(yǔ)言寫(xiě)的極小的 init 進(jìn)程,github.com/krallin/tin… 。它的代碼較短,很值得一讀,對(duì)理解信號(hào)傳遞、處理僵尸進(jìn)程非常有幫助。

小結(jié)

通過(guò)這篇文章,希望你可以搞懂僵尸進(jìn)程、孤兒進(jìn)程、PID 為 1 的進(jìn)程是什么,以及為什么 node/npm 不適合做 PID 為 1 的進(jìn)程,bash 作為 PID 為 1 的進(jìn)程有什么缺陷。

下面留一個(gè)作業(yè)題,考考你對(duì)進(jìn)程 fork 函數(shù)的理解。如下程序連續(xù)調(diào)用三次 fork() 調(diào)用后會(huì)產(chǎn)生多少新進(jìn)程?

 

  1. #include <stdio.h> 
  2. #include <unistd.h> 
  3.  
  4. int main() { 
  5.   printf("Hello, World!\n"); 
  6.  
  7.   fork(); 
  8.   fork(); 
  9.   fork(); 
  10.   sleep(100); 
  11.   return 0; 

 

責(zé)任編輯:未麗燕 來(lái)源: 今日頭條
相關(guān)推薦

2018-01-19 11:12:11

HTTP問(wèn)題排查

2022-02-08 17:17:27

內(nèi)存泄漏排查

2025-03-17 10:01:07

2023-04-06 07:53:56

Redis連接問(wèn)題K8s

2018-07-12 10:33:50

Docker容器內(nèi)存

2019-03-15 16:20:45

MySQL死鎖排查命令

2017-12-19 14:00:16

數(shù)據(jù)庫(kù)MySQL死鎖排查

2021-05-13 08:51:20

GC問(wèn)題排查

2019-09-10 10:31:10

JVM排查解決

2019-01-21 11:17:13

CPU優(yōu)化定位

2023-01-04 18:32:31

線上服務(wù)代碼

2021-03-29 12:35:04

Kubernetes環(huán)境TCP

2022-11-03 16:10:29

groovyfullGC

2011-04-07 11:20:21

SQLServer

2021-09-14 13:25:23

容器pod僵尸進(jìn)程

2018-12-06 16:01:01

Redis遷移容器數(shù)據(jù)庫(kù)

2020-11-02 09:48:35

C++泄漏代碼

2021-11-23 21:21:07

線上排查服務(wù)

2022-10-10 09:10:07

命令磁盤(pán)排查

2021-04-13 08:54:28

dubbo線程池事故排查
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

av黄色免费在线观看| 国产在线青青草| av综合在线观看| 亚洲夜间福利| 亚洲性xxxx| 肉色超薄丝袜脚交| 三级在线观看视频| 欧美激情一区二区在线| 91性高湖久久久久久久久_久久99| 免费在线视频观看| 蜜臀av免费一区二区三区| 欧美日精品一区视频| 9色porny| 瑟瑟视频在线| 99久久99久久免费精品蜜臀| 国产日韩在线免费| 亚洲第一在线播放| 五月天久久网站| 亚洲精品第一页| 国产免费中文字幕| 国产精品av一区二区三区| 中文字幕亚洲欧美在线不卡| 国产精品一区在线观看| 136福利视频导航| 国产农村妇女精品一区二区| 欧美成人午夜剧场免费观看| 国产aⅴ激情无码久久久无码| 精品精品视频| 欧美午夜不卡视频| 日本欧美黄色片| av大全在线| 国产精品女主播在线观看| 欧美成人一区二区在线| 亚洲精品久久久蜜桃动漫| 蜜臀a∨国产成人精品| 97avcom| 欧美黑人猛猛猛| 日韩在线第七页| 亚洲欧美日韩直播| 亚洲国产第一区| 国产无遮挡裸体免费久久| 欧美一区二区三区在线观看| 日本 片 成人 在线| 三上悠亚激情av一区二区三区| 亚洲影院免费观看| 视色,视色影院,视色影库,视色网| www.视频在线.com| 久久久精品天堂| 欧美不卡在线一区二区三区| 欧美自拍第一页| 成人三级伦理片| 国产精选在线观看91| 亚洲黄色片视频| 国产·精品毛片| 国产精品免费在线播放| 亚洲美女福利视频| 国产成人亚洲综合a∨猫咪| 91精品视频专区| 国产精品丝袜黑色高跟鞋| 久久精品国产亚洲a| 国产免费亚洲高清| 97超视频在线观看| 国产一区二区三区综合| 亚洲一区二区中文| www.日韩高清| 成av人片一区二区| 欧美精品一区二区三区四区五区| 青青草免费在线视频| 国产亚洲综合av| 亚洲一卡二卡| 中文字幕在线观看播放| 亚洲综合久久久| 欧美一级视频在线播放| 小早川怜子影音先锋在线观看| 色哟哟日韩精品| 最近中文字幕一区二区| 在线高清欧美| 精品国产123| 日韩乱码人妻无码中文字幕久久| 欧美日韩在线二区| 久久亚洲国产精品| 五月天婷婷网站| 久久天堂成人| 91久久久久久久久久| 亚洲精品97久久中文字幕无码| 成人激情小说乱人伦| 热re99久久精品国99热蜜月| 午夜视频成人| 亚洲图片自拍偷拍| 日韩精品一区二区三区不卡 | 亚洲最好看的视频| 亚洲精品自在久久| 色老板免费视频| 日韩一级网站| 国产精品入口夜色视频大尺度 | 日韩av一区在线观看| 丰满少妇高潮一区二区| 欧美电影三区| 97国产精品视频人人做人人爱| 国产又大又粗又爽| 国产麻豆一精品一av一免费| 久久99蜜桃综合影院免费观看| 中文字幕在线免费| 亚洲大片在线观看| 在线免费av播放| 成人福利一区| 色七七影院综合| 91午夜视频在线观看| 美女视频黄 久久| 国产专区一区二区| 免费人成在线观看播放视频| 欧美视频裸体精品| 日韩欧美色视频| 欧美丝袜一区| 91精品国产高清久久久久久91| 夜夜狠狠擅视频| 久久久久久久电影| 日韩视频免费播放| 五月天色综合| 中文欧美日本在线资源| 日韩毛片在线播放| 国产伦精一区二区三区| 日韩欧美精品一区二区| 国产精品xx| 日韩精品一区二区三区蜜臀| 色噜噜噜噜噜噜| 久久狠狠一本精品综合网| 超碰在线观看97| 成人在线观看亚洲| 欧美午夜精品久久久久久超碰| 中文字幕一区二区三区人妻不卡| 91精品国产乱码久久久久久| 国产精品美女av| 黄色在线视频观看网站| 精品国产1区2区| 亚洲天堂2024| 亚洲欧洲另类| 国产欧美丝袜| 国产ktv在线视频| 精品999在线播放| 免费人成年激情视频在线观看| 久久91精品国产91久久小草| 亚洲一区二区三区涩| 欧洲精品一区二区三区| 亚洲欧美福利视频| 探花视频在线观看| 26uuu久久综合| 国模杨依粉嫩蝴蝶150p| 偷拍视屏一区| 日本国产高清不卡| 青青草免费观看免费视频在线| 五月天欧美精品| 朝桐光av一区二区三区| 99在线观看免费视频精品观看| 国产一区二区久久久| 高清毛片在线观看| 日韩毛片在线看| 无码人妻av免费一区二区三区| 久久蜜桃av一区二区天堂| 国产又黄又大又粗视频| 亚洲另类春色校园小说| 国产福利视频一区二区| 福利在线视频导航| 8v天堂国产在线一区二区| 九九热最新地址| 丰满岳乱妇一区二区三区| 成人免费在线网| 性欧美xxxx免费岛国不卡电影| 国产精品96久久久久久| aaa在线观看| 91麻豆精品国产91久久久久| 久久午夜无码鲁丝片| 99精品欧美一区| 我要看一级黄色大片| 欧美99久久| 国偷自产av一区二区三区小尤奈| 成人福利av| 久久久国产一区二区三区| www.色日本| 欧美性猛交xxxx黑人| 大吊一区二区三区| 国产精品99久久久久久有的能看 | 精品亚洲欧美一区| 青青草视频国产| 亚洲欧洲av| 成人黄色在线观看| 2020国产在线| 曰本色欧美视频在线| www.午夜激情| 色先锋aa成人| www.99re7| 久久夜色精品国产欧美乱极品| 日本中文字幕二区| 亚洲精选久久| av电影一区二区三区| 欧美高清视频看片在线观看| 国产欧美精品一区二区三区-老狼| 少女频道在线观看高清| 国产一区二区久久精品| 超碰人人人人人人| 欧美色窝79yyyycom| 欧美日韩中文视频| 国产精品无圣光一区二区| 2018国产精品| 理论电影国产精品| 欧美a v在线播放| 亚洲澳门在线| 欧美中日韩免费视频| 一区二区三区高清在线观看| 国产精品黄页免费高清在线观看| 超碰97免费在线| 日韩小视频在线观看| 日本午夜在线| 精品免费视频.| 国产精品久久久久久久一区二区| 欧美性猛交99久久久久99按摩| 欧美精品99久久久| 国产精品欧美精品| 色婷婷av777| 99久久精品国产网站| 欧美视频亚洲图片| 青娱乐精品在线视频| 97国产精东麻豆人妻电影| 一区二区电影在线观看| 涩涩涩999| 一本色道久久综合狠狠躁的番外| 成人在线观看91| 欧美日本三级| 91免费视频网站| 在线高清欧美| 国产一区二中文字幕在线看| www.一区| 国产精品久久久91| 欧洲av不卡| 国产成人精品亚洲精品| 日韩伦理在线一区| 69影院欧美专区视频| 国产经典三级在线| 欧美精品久久久久久久久| av超碰免费在线| 欧美精品一本久久男人的天堂| 毛片激情在线观看| www亚洲精品| 黄网站免费在线播放| 日韩中文综合网| 欧美jizz18性欧美| 日韩在线观看免费| 日本在线免费看| 久久精品视频在线播放| 黄色网页在线观看| 另类天堂视频在线观看| a级网站在线播放| 欧美黑人极品猛少妇色xxxxx| av网站网址在线观看| 操人视频在线观看欧美| av大大超碰在线| 国外视频精品毛片| 日韩电影免费看| 国产精品7m视频| 看片一区二区| 成人动漫网站在线观看| 国语精品视频| 国产91社区| 希岛爱理av免费一区二区| 久久人人九九| 日韩欧美网址| av动漫在线播放| 在线综合亚洲| 国产一区二区在线免费播放| 久久97超碰色| 北京富婆泄欲对白| 久久理论电影网| 三级黄色片在线观看| 亚洲日本成人在线观看| 久视频在线观看| 色婷婷精品久久二区二区蜜臂av| 国产情侣呻吟对白高潮| 制服丝袜激情欧洲亚洲| 蜜桃久久一区二区三区| 亚洲欧美日韩中文在线| 日本免费在线观看| 欧美激情a∨在线视频播放| 小视频免费在线观看| 国产欧美精品在线| 911亚洲精品| 日本一区二区三区免费看| 天天射综合网视频| 老太脱裤让老头玩ⅹxxxx| 日本欧美韩国一区三区| 国产成人精品综合久久久久99| 91在线视频播放地址| www..com.cn蕾丝视频在线观看免费版 | 成人一区二区电影| 欧美精品国产白浆久久久久| 伊人色综合影院| 亚洲茄子视频| 一级做a免费视频| 91香蕉视频污| 亚洲国产精品久| 欧美在线|欧美| 手机看片1024国产| 精品国产一区久久久| 欧美久久天堂| 91观看网站| 成人精品电影| 国产成人黄色片| 国产成人精品一区二区三区四区| 国产高潮呻吟久久| 亚洲午夜久久久久久久久电影院 | 亚洲成人自拍| 99国产精品| 色诱av手机版| 亚洲欧洲成人av每日更新| 中文字幕在线看人| 亚洲大胆人体视频| 国产色在线观看| 国产精品久久久久久久久久久久久久| 果冻天美麻豆一区二区国产| 一区一区视频| 奇米四色…亚洲| 欧美特级黄色录像| 欧美性xxxxx| 天天操天天操天天操| 欧美日本精品在线| 欧美a视频在线| 四虎影院一区二区三区| 在线综合亚洲| 最近中文字幕无免费| 亚洲国产一区二区a毛片| 国产伦精品一区二区三区免.费| 国产一区二区动漫| 激情亚洲影院在线观看| 久久亚洲国产精品日日av夜夜| 一区在线播放| 亚洲啪av永久无码精品放毛片| 亚洲精品国产a久久久久久 | 国产一区91精品张津瑜| 天美传媒免费在线观看| 欧美三区在线观看| h视频在线观看免费| 欧洲亚洲免费视频| 日韩欧美美女在线观看| 人妻无码久久一区二区三区免费| 丁香婷婷深情五月亚洲| 麻豆视频在线观看| 日韩欧美中文字幕一区| 韩国av网站在线| 97se亚洲综合在线| 国产精品porn| av av在线| 欧美日韩激情视频8区| 亚洲区小说区图片区| 国产91精品久久久久| 香蕉人人精品| 天堂社区在线视频| 中文字幕av一区二区三区| 一区二区久久精品66国产精品| 日韩在线视频观看| 国产精品3区| 六月婷婷激情网| 成人免费毛片a| 国产免费av一区| 正在播放亚洲1区| 精品视频一区二区三区| 日本阿v视频在线观看| 床上的激情91.| 日本在线播放视频| 伊人久久综合97精品| 99精品视频在线免费播放 | 欧美黑人猛交的在线视频| 国产一级二级三级精品| 日韩精品高清不卡| 欧美激情精品久久久久久免费| 日韩欧美成人一区| 亚洲美女炮图| 一区二区在线观看网站| 懂色av一区二区在线播放| 五月婷婷色丁香| 少妇高潮 亚洲精品| 天堂av一区| 日本在线视频www| 成人免费在线播放视频| 亚洲精品视频网| 国产精品国产三级国产aⅴ浪潮| 久久精品青草| 你懂得在线视频| 欧美日韩精品欧美日韩精品一综合| 日本在线视频网址| 欧美日韩电影一区二区| 国产一区二区调教| 久久黄色精品视频| 久久人人爽亚洲精品天堂| 国产欧美一区二区三区米奇| 三上悠亚av一区二区三区| 亚洲午夜一区二区| 亚洲欧美视频一区二区| 国产精品jizz视频| 免费一级欧美片在线观看| 日韩精品乱码久久久久久| 日韩三级影视基地|