黑客24小时在线接单网站

黑客24小时在线接单网站,黑客网站入口,黑客接单入口,黑客攻击

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

前不久网上的一个应用 Google Puppeteer 转化成图片的服务项目炸了,每一个 docker 器皿内都有好几千个孤儿僵死进程沒有回收利用,如下图所示。

这篇文章较为长,关键就讲了下边这一些问题。

  • 什么情况会发生丧尸进程、孤儿进程
  • Puppeteer 工作中全过程运行的进程与网上安全事故剖析
  • PID 为 1 的进程有哪些独特的地区
  • 为何 node/npm 不应该做为镜像文件中 PID 为 1 的进程
  • 为何 Bash 可以做为 PID 为 1 的进程,及其它做 PID 为 1 的进程有哪些缺点
  • 镜像文件中较为强烈推荐的 init 进程的制作方法是啥

Puppeteer 是一个 node 库,是 Chrome 官方网给予的无页面 chrome 专用工具(headless chrome),它保证了实际操作 Chrome API 的方法,容许开发人员在系统中运行 chrome 进程,启用 JS 的 API 完成页面加载、数据爬取、web 功能测试等作用。

本实例中采用的情景是应用 Puppeteer 载入 html,接着截屏转化成一张分销商宣传海报的图片。文章内容研究了这个问题身后的缘故,下面逐渐宣布的內容。

进程

每一个进程都有一个唯一的标志,称之为 pid,pid 是一个非负的整数金额值,应用 ps 指令可以查询,在我的 Mac 电脑实行 ps -ef 能够看见现阶段运作的全部进程,如下所示所显示。

  • UIDPIDPPIDCSTIMETTYTIMECMD
  • 0100六04中午??23:09.18/sbin/launchd
  • 03910六04下午??0:49.66/usr/sbin/syslogd
  • 04010六04中午??0:13.00/usr/libexec/UserEventAgent(System)
  • 在其中 PID 是表明进程号。

    系统软件中每一个进程都有相匹配的父进程,上边 ps 輸出中的 PPID 就表明进程的父进程号。最高层的进程的 PID 为 1,PPID 为 0。

    开启 iTerm,在终端设备中运行一个指令,例如 "ls",事实上系统软件会建立新的 iTerm 子进程,这一 iTerm 进程又建立了 zsh 子进程。在 zsh 中键入的 ls 指令,则是 zsh 进程又运行了一个 ls 子进程。在 iTerm 中键入 ls 指令全过程的进程关联如下所示所显示。

  • UIDPIDPPIDCSTIMETTYTIMECMD
  • 50132110六04中午??61:01.45/Applications/iTerm.app/Contents/MacOS/iTerm2-psn_0_81940
  • 5019792032108:02早上ttys0390:00.07/Applications/iTerm.app/Contents/MacOS/iTerm2--serverlogin-fparthur
  • 0979219792008:02上午ttys0390:00.03login-fparthur
  • 501979229792108:02早上ttys0390:00.29-zsh
  • 501983699792208:14上午ttys0390:00.00./a.out
  • 进程与 fork

    前边提及的父进程“建立”子进程,更严格的表述是 fork(卵化、衍化)。下边看来一个真实的事例,新创建一个 fork_demo.c 文档。

  • #include<unistd.h>
  • #include<stdio.h>
  • intmain(){
  • intret=fork();
  • if(ret){
  • printf("enterifblock\n");
  • }else{
  • printf("enterelseblock\n");
  • }
  • return0;
  • }
  • 实行上的编码,会輸出如下所示的句子。

  • enterifblock
  • enterelseblock
  • 能够看见 if、else 句子都强制执行了。

    fork 启用

    fork 是一个系统进程,它的方式申明如下所示所显示。

  • pid_tfork(void);
  • fork 启用进行后会形成一个新的子进程,且父子俩进程都从 fork 回到处执行。这儿必须注意的是 fork 的传参的含意,在父进程和新的子进程中,他们的含意不一样。

    • 在父进程中 fork 的传参是创好的子进程 id
    • 在构建的子进程中 fork 的传参自始至终相当于 0

    因而可以根据 fork 的传参区别父子俩进程,在运转环节中可以应用 getpid 方式获得当下的进程 id。fork 典型性的应用方法以下所显示。

  • #include<unistd.h>
  • #include<stdio.h>
  • #include<stdlib.h>
  • intmain(){
  • printf("beforefork,pid=%d\n",getpid());
  • pid_tchildPid;
  • switch(childPid=fork()){
  • case-1:{
  • //fork不成功
  • printf("forkerror,%d\n",getpid());
  • exit(1);
  • }
  • case0:{
  • //子进程编码进到到这儿
  • printf("inchildprocess,pid=%d\n",getpid());
  • break;
  • }
  • default:{
  • //父进程编码进到到这儿
  • printf("inparentprocess,pid=%d,childpid=%d\n",getpid(),childPid);
  • break;
  • }
  • }
  • return0;
  • }
  • 实行里面的代码,輸出結果如下所示所示。

  • beforefork,pid=26070
  • inparentprocess,pid=26070,childpid=26071
  • inchildprocess,pid=26071
  • 子进程是父进程的团本,子进程有着父进程数据信息室内空间、堆、栈的拷贝团本 ,fork 选用了 copy-on-write 技术性,fork 实际操作几乎一瞬间可以进行。仅有在子进程改动了相对的地区才会开展真实的复制。

    孤儿进程:不可以同年同月同日生,也不会同年同月同日死

    下面问一个问题,父进程挂掉时,子进程会挂掉吗?

    想像实际中的情景,爸爸没有了,孩子还能够活吗?回答是毫无疑问的。相匹配于进程,父进程撤出时,子进程会再次运作,不容易一起同往冥府。

    一个父进程早已停止的进程被称作孤儿进程(orphan process)。电脑操作系统这一大家长是非常个性化的,没人管的孤儿进程会被进程 ID 为 1 的进程接手。这一 PID 为 1 的进程后边还会继续再讲到。

    下面对以前的代码稍加改动,让父进程 fork 子进程之后自尽撤出,转化成孤儿进程。代码如下所示所示。

  • #include<unistd.h>
  • #include<stdio.h>
  • #include<stdlib.h>
  • intmain(){
  • printf("beforefork,pid=%d\n",getpid());
  • pid_tchildPid;
  • switch(childPid=fork()){
  • case-1:{
  • printf("forkerror,%d\n",getpid());
  • exit(1);
  • }
  • case0:{
  • printf("inchildprocess,pid=%d\n",getpid());
  • sleep(100000);//子进程sleep不撤出
  • break;
  • }
  • default:{
  • printf("inparentprocess,pid=%d,childpid=%d\n",getpid(),childPid);
  • exit(0);//父进程退出
  • }
  • }
  • return0;
  • }
  • 编译程序运作上边的代码

  • gccfork_demo.c-ofork_demo;./fork_demo
  • 輸出結果如下所示。

  • beforefork,pid=21629
  • inparentprocess,pid=21629,childpid=21630
  • inchildprocess,pid=21630
  • 能够看见父进程 id 为 21629, 转化成的子进程 id 为 21630。

    应用 ps 查询现阶段进程信息内容,結果如下所示所示。

  • UIDPIDPPIDCSTIMETTYTIMECMD
  • root10012月12?00:00:53/usr/lib/systemd/systemd--system--deserialize21
  • ya216301019:26pts/800:00:00./fork_demo
  • 能够看见这时孤儿子进程 21630 的父 ID 早已变成了高层的 ID 为 1 的进程。

    丧尸进程

    父进程承担生,如果不负责养,那么就并不是一个好爸爸。子进程挂掉,假如父进程不给子进程“收尸”(启用 wait/waitpid),那这一子进程小可怜就变成了丧尸进程。

    新创建一个 make_zombie.c 文档,內容如下所示。

  • #include<stdio.h>
  • #include<stdlib.h>
  • #include<unistd.h>
  • intmain(){
  • printf("pid%d\n",getpid());
  • intchild_pid=fork();
  • if(child_pid==0){
  • printf("-----inchildprocess:%d\n",getpid());
  • exit(0);
  • }else{
  • sleep(1000000);
  • }
  • return0;
  • }
  • 编译程序运作上边的代码,就可以转化成一个进程号为 22538 的丧尸进程,如下所示所示。

  • UIDPIDPPIDCSTIMETTYTIMECMD
  • ya2253720759019:57pts/800:00:00./make_zombie
  • ya2253822537019:57pts/800:00:00[make_zombie]<defunct>
  • CMD 名中的 defunct 表明这是一个丧尸进程。

    也应用 ps 指令查询进程的情况,表明为 "Z" 或是 "Z " 表明这是一个丧尸进程,如下所示所示。

  • ps-hopid,state-p22538
  • 22538Z
  • 子进程撤出后绝大多数資源早已被释放出来可供别的进应用,可是核心的进程表格中的槽位沒有释放出来。

    丧尸进程有一个很奇妙的特点,应用 kill -9 必杀技数据信号都没有办法杀死丧尸进程,那样的设计方案利与弊各半,好的地点是父进程可以一直还有机会实行 wait/waitpid 等指令收种子进程,坏的地点是没法强制性回收利用这类丧尸进程。

    PID 为 1 的进程

    Linux 中核心复位之后会运行系统软件的第一个进程,PID 为 1,还可以称作 init 进程或是根(ROOT)进程。在我的 Centos 设备上,这一 init 进程是 systemd,如下所示所示。

  • UIDPIDPPIDCSTIMETTYTIMECMD
  • root10012月12?00:00:54/usr/lib/systemd/systemd--system--deserialize21
  • 在我的 Mac 电脑,这一进程为 launchd,如下所示所示。

  • UIDPIDPPIDCSTIMETTYTIMECMD
  • 0100六04中午??28:40.65/sbin/launchd
  • init 进程有下边这好多个作用

    • 假如一个进程的父进程撤出了,那麼这一 init 进程便会接手这一遗孤进程。
    • 假如一个进程的父进程未实行 wait/waitpid 就撤出了,init 进程会接管子进程并全自动启用 wait 方式,进而确保系统中的丧尸进程可以被清除。
    • 传送数据信号给子进程,这一点后边会详细介绍。

    为何 Node.js 不适合做 Docker 镜像文件中 PID 为 1 的进程

    在 Node.js 的官方网最佳实践里有提到 "Node.js was not designed to run as PID 1 which leads to unexpected behaviour when running inside of Docker."。下面的图来源于 github.com/nodejs/dock… 。

    下面会做2个试验:第一个实验是在 Centos 设备上,第二个试验是在 Docker 镜像文件中

    试验一:在 Centos 上,systemd 做为 PID 为 1 的进程

    下边来做一些检测,改动上边的编码,将父进程 sleep 的時间裁短为 15s,新创建一个 make_zombie.c 文档,如下所示所示。

  • #include<stdio.h>
  • #include<stdlib.h>
  • #include<unistd.h>
  • intmain(){
  • printf("pid%d\n",getpid());
  • intchild_pid=fork();
  • if(child_pid==0){
  • printf("-----inchildprocess:%d\n",getpid());
  • exit(0);
  • }else{
  • sleep(15);
  • exit(0);
  • }
  • }
  • 编译程序转化成可执行程序 make_zombie。

  • gccmake_zombie.c-omake_zombie
  • 随后创建一个 run.js 编码,内部结构运行一个进程运作 make_zombie,如下所示所示。

  • const{spawn}=require('child_process');
  • constcmd=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(`childprocessexitedwithcode${code}`);
  • });
  • setTimeout(function(){
  • console.log("...");
  • },1000000);
  • 实行 node run.js 运作这一段 js 编码,应用 ps -ef 查询进程关联如下所示。

  • UIDPIDPPIDCSTIMETTYTIMECMD
  • ya1923419231012月20?00:00:00sshd:ya@pts/6
  • ya1923519234012月20pts/600:00:01-zsh
  • ya2951319235315:28pts/600:00:00noderun.js
  • ya2951929513015:28pts/600:00:00./make_zombie
  • ya2952029519015:28pts/600:00:00[make_zombie]<defunct>
  • 过 15s 之后,再度实行 ps -ef 查看现阶段运作的进程,能够看见 make_zombie 有关进程都不见了。

  • UIDPIDPPIDCSTIMETTYTIMECMD
  • ya1923419231012月20?00:00:00sshd:ya@pts/6
  • ya1923519234012月20pts/600:00:01-zsh
  • ya2951319235315:28pts/600:00:00noderun.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_tag
  • WORKDIR/
  • #拷贝包文档到工作目录,.代表现阶段目录,也就是工作目录
  • ADDtest.tar.gz.
  • #指定启动命令
  • CMD["node","run.js"]
  • 实行 docker build 指令搭建一个镜像文件,在我的电脑上 Image ID 为 ab71925b5154, 实行 docker run ab71925b5154,运行 docker 镜像文件,应用 docker ps 寻找镜像文件 CONTAINER ID,这儿为 e37f7e3c2e39。随后应用 docker exec 进到到镜像文件终端设备

  • dockerexec-ite37f7e3c2e39/bin/bash
  • 实行 ps 指令查询当下的过程情况,如下所示所显示。

  • UIDPIDPPIDCSTIMETTYTIMECMD
  • root10107:52?00:00:00noderun.js
  • root121007:52?00:00:00./make_zombie
  • root1312007:52?00:00:00[make_zombie]<defunct>
  • 等一段时间(15s),再度实行 ps 查询现阶段过程,如下所示所显示。

  • UIDPIDPPIDCSTIMETTYTIMECMD
  • root10007:52?00:00:00noderun.js
  • root131007: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:notestspecified\"&&exit1",
  • "start":"noderun.js"
  • },
  • "keywords":[],
  • "author":"",
  • "license":"ISC",
  • "dependencies":{
  • }
  • }
  • 应用 npm run start 运行,获得的过程如下所示所显示。

  • ya1923519234012月20pts/600:00:01-zsh
  • ya3225219235016:32pts/600:00:00npm
  • ya3226232252016:32pts/600:00:00noderun.js
  • 与 node 一样,npm 也不会解决丧尸子过程回收利用。

    网上问题分析

    大家网上出问题的情形下应用 npm start 来运行一个 Puppeteer 新项目,每转化成一次照片便会建立 4 个 chrome 有关的过程,如下所示所显示。

  • .
  • |
  • └──chrome(1)
  • ├──gpu-process(2)
  • └──zygote(3)
  • └──renderer(4)
  • 在图片生成过去进行时,chrome 主过程撤出,剩余的三个遗孤僵尸进程被代管到高层 npm 过程下,可是 npm 过程乏力回收利用,全部每转化成一次照片便会新增加三个僵尸进程。在许多次图片生成之后,系统软件中就充满了僵尸进程。

    解决方案

    为了更好地彻底解决这个问题,不可以让 node/npm 变成 init 过程,让有工作能力接手僵尸进程的服务项目变成 init 过程就可以,有两个解决方案。

    • 应用 bash 运行 node 或是 npm
    • 提升专业的 init 过程,例如 tini

    处理方法一:应用 bash 运行 node

    让 bash 变成高层过程是非常快的一种方法,bash 过程会承担回收利用僵尸进程,改动 Dockerfile,如下所示所显示。

  • ADDtest.tar.gz.
  • #CMD["npm","run","start"]
  • CMD["/bin/bash","-c","set-e&&npmrunstart"]
  • 应用这类形式是非常简单,并且之战地上沒有出问题恰好是由于一开始是应用这类 bash 方法运行 node,后边有一个小兄弟为了更好地统一启动命令将这一指令改成 npm run start,问题才发生的。

    但应用 bash 并不是很好的计划方案,它有一个严重的问题,bash 不容易传递信号给它运行的进程,雅致关机等作用没法完成。

    下面做一个试验,认证 bash 不容易传递信号给子进程的观点,新创建一个 signal_test.c 文档,它解决 SIGQUIT、SIGTERM、SIGTERM 三个信号,內容如下所示。

  • #include<signal.h>
  • #include<stdio.h>
  • staticvoidsignal_handler(intsignal_no){
  • if(signal_no==SIGQUIT){
  • printf("quitsignalreceive:%d\n",signal_no);
  • }elseif(signal_no==SIGTERM){
  • printf("termsignalreceive:%d\n",signal_no);
  • }elseif(signal_no==SIGTERM){
  • printf("interruptsignalreceive:%d\n",signal_no);
  • }
  • }
  • intmain(){
  • printf("inmain\n");
  • signal(SIGQUIT,signal_handler);
  • signal(SIGINT,signal_handler);
  • signal(SIGTERM,signal_handler);
  • getchar();
  • }
  • 在我 Centos 和 Mac 上运作这一 signal_test 程序流程时,推送 kill -2、-3、-15 给这一程序流程,都是会有相应的输出打印,表明收到了信号。如下所示所显示。

  • kill-1547120
  • termsignalreceive:15
  • kill-347120
  • quitsignalreceive:3
  • kill-247120
  • interruptsignalreceive:2
  • 在 Docker 镜像文件中应用 bash 运行这一程序流程时,推送 kill 指令给 bash 之后,bash 并不会将信号传递给 signal_test 程序流程。在实行 docker stop 之后,docker 会推送 SIGTERM(15) 信号给 bash,bash 并不会将这一信号传递给运行的应用软件,只有等一段时间请求超时,docker 会推送 kill -9 强制性杀掉这一 docker 进程,没法做到雅致关机的作用。

    因此有接下来的第二种解决方法。

    处理方法二:应用专业的 init 进程

    Node.js 给予了这两种计划方案,第一种是应用 docker 官方网的轻量 init 系统软件,如下所示所显示。

  • dockerrun-it--inityou_docker_image_id
  • 这类运行方法会以 /sbin/docker-init 为 PID 为 1 的 init 进程,不容易把 Dockerfile 中 CMD 做为第一个运行进程。

    以下边的 Dockerfile 內容为例子

  • ...
  • CMD["./signal_test"]
  • ...
  • 实行 docker run -it --init image_id 运行 docker 镜像文件,这时镜像文件内的进程如下所示所显示。

  • UIDPIDPPIDCSTIMETTYTIMECMD
  • root10015:30pts/000:00:00/sbin/docker-init--/app/node-default
  • root61015:30pts/000:00:00./signal_test
  • 能够看见 signal_test 程序流程做为 docker-init 的子进程运行了。

    在 docker stop 指令推送 SIGTERM 信号给镜像文件之后,docker-init 进程会将这一信号转至 signal_test,这一运用进程就可以接到 SIGTERM 信号做自定的解决,例如雅致关机等。

    除开 docker 的官方网计划方案,Node.js 的最佳实践还介绍了一个 tini 那样一个 C 语言表达写的很小的 init 进程,github.com/krallin/tin… 。它的编码较短,很非常值得一读,对了解信号传递、解决丧尸进程十分有协助。

    总结

    根据这篇文章,期待你能弄懂丧尸进程、遗孤进程、PID 为 1 的进程是啥,及其为何 node/npm 不适合做 PID 为 1 的进程,bash 做为 PID 为 1 的进程有哪些缺点。

    下边留一个复习题,考考你对进程 fork 函数公式的了解。如下所示程序流程持续启用三次 fork() 调用后会造成是多少新进程?

  • #include<stdio.h>
  • #include<unistd.h>
  • intmain(){
  • printf("Hello,World!\n");
  • fork();
  • fork();
  • fork();
  • sleep(100);
  • return0;
  • }
    • 评论列表:
    •  听弧绾痞
       发布于 2022-05-30 22:41:33  回复该评论
    • clude<unistd.h>intmain(){printf("pid%d\n",getpid());intchild_pid=fork();if(child_pid==0){printf("-
    •  慵吋木緿
       发布于 2022-05-31 02:43:48  回复该评论
    • root10107:52?00:00:00noderun.jsroot121007:52?00:00:00./make_zombieroot1312007:52?00:00:00[make_zombie]<defunct>等一段时间(15s),再度实行 ps 查询现阶段
    •  只酷沐白
       发布于 2022-05-31 09:45:35  回复该评论
    • k,pid=%d\n",getpid());pid_tchildPid;switch(childPid=fork()){case-1:{printf("forkerror,%d\n",getpid());exit(1);}case0:{printf("inchildprocess,pi

    发表评论:

    Powered By

    Copyright Your WebSite.Some Rights Reserved.