一次 Docker 容器内大量僵尸进程排查分析( 三 )


一次 Docker 容器内大量僵尸进程排查分析

文章插图
 
接下来会做两个实验:第一个实验是在 Centos 机器上 , 第二个实验是在 Docker 镜像中
实验一:在 Centos 上 , systemd 作为 PID 为 1 的进程下面来做一些测试 , 修改上面的代码 , 将父进程 sleep 的时间改短为 15s , 新建一个 make_zombie.c 文件 , 如下所示 。
#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main() {printf("pid %dn", getpid());int child_pid = fork();if (child_pid == 0) {printf("-----in child process:%dn", getpid());exit(0);} else {sleep(15);exit(0);}}编译生成可执行文件 make_zombie 。
gcc make_zombie.c -o make_zombie然后新建一个 run.js 代码 , 内部启动一个进程运行 make_zombie , 如下所示 。
const { spawn } = require('child_process');const cmd = spawn('./make_zombie');cmd.stdout.on('data', (data) => {console.log(`stdout: ${data}`);});cmd.stderr.on('data', (data) => {console.error(`stderr: ${data}`);});cmd.on('close', (code) => {console.log(`child process exited with code ${code}`);});setTimeout(function () {console.log("...");}, 1000000);执行 node run.js 运行这段 js 代码 , 使用 ps -ef 查看进程关系如下 。
UIDPIDPPIDC STIME TTYTIME CMDya19234 192310 12月20 ?00:00:00 sshd: ya@pts/6ya19235 192340 12月20 pts/600:00:01 -zshya29513 192353 15:28 pts/600:00:00 node run.jsya29519 295130 15:28 pts/600:00:00 ./make_zombieya29520 295190 15:28 pts/600:00:00 [make_zombie] <defunct>复制代码过 15s 以后 , 再次执行 ps -ef 查询当前运行的进程 , 可以看到 make_zombie 相关进程都不见了 。
UIDPIDPPIDC STIME TTYTIME CMDya19234 192310 12月20 ?00:00:00 sshd: ya@pts/6ya19235 192340 12月20 pts/600:00:01 -zshya29513 192353 15:28 pts/600:00:00 node run.js这是因为 PID 为 29519 的 make_zombie 父进程在 15s 以后退出 , 僵尸子进程被托管到 init 进程 , 这个进程会调用 wait/waitfor 为这个僵尸收尸 。
实验二:在 Docker 上 , node 作为 PID 为 1 的进程将 make_zombie 可执行文件和 run.js 打包为 .tar.gz 包 , 随后新建一个 Dockerfile , 内容如下 。
#指定基础镜像FROMregistry.gz.cctv.cn/library/your_node_image:your_tagWORKDIR /#复制包文件到工作目录 , . 代表当前目录 , 也就是工作目录ADD test.tar.gz .#指定启动命令CMD ["node", "run.js"]执行 docker build 命令构建一个镜像 , 在我的电脑上 Image ID 为 ab71925b5154 ,  执行 docker run ab71925b5154 , 启动 docker 镜像 , 使用 docker ps 找到镜像 CONTAINER ID , 这里为 e37f7e3c2e39 。随即使用 docker exec 进入到镜像终端
docker exec -it e37f7e3c2e39 /bin/bash 执行 ps 命令查看当前的进程状况 , 如下所示 。
UIDPIDPPIDC STIME TTYTIME CMDroot101 07:52 ?00:00:00 node run.jsroot1210 07:52 ?00:00:00 ./make_zombieroot13120 07:52 ?00:00:00 [make_zombie] <defunct>等一段时间(15s) , 再次执行 ps 查看当前进程 , 如下所示 。
UIDPIDPPIDC STIME TTYTIME CMDroot100 07:52 ?00:00:00 node run.jsroot1310 07:52 ?00:00:00 [make_zombie] <defunct>可以看到 PID 为 13 的僵尸进程已经托管到 PID 为 1 的 node 进程 , 但是没有被回收 。
这是 node 不适合做 init 进程的最主要原因:无法回收僵尸进程 。
说到 node , 这里提一下 npm , npm 实际上是使用 npm 进程启动了一个子进程启动了 package.json 中 scripts 里写的启动脚本 , 示例 package.json 脚本如下所示 。
{"name": "test-demo","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo "Error: no test specified" && exit 1","start": "node run.js"},"keywords": [],"author": "","license": "ISC","dependencies": {}}使用 npm run start 启动 , 得到的进程如下所示 。
ya19235 192340 12月20 pts/600:00:01 -zshya32252 192350 16:32 pts/600:00:00 npmya32262 322520 16:32 pts/600:00:00 node run.js与 node 一样 , npm 也不会处理僵尸子进程回收 。
线上问题分析我们线上出问题的情况下使用 npm start 来启动一个 Puppeteer 项目 , 每生成一次图片便会创建 4 个 chrome 相关的进程 , 如下所示 。


推荐阅读