多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] 我们在打镜像时经常会遇到这样的场景,打好的镜像跑起来之后马上就退出了。 **在容器中,如果****`pid=1`****的进程退出了,那么容器就会退出。所以要保证容器持久运行,就要保证****`pid=1`****的进程能够持久运行** 接下来,我们举几个例子,来看一下如何保证`pid=1`的进程不退出。我们以docker官网的镜像`nginx:1.11.5`作为基础镜像,该镜像中`nginx -g 'daemon off;'`命令是一个持久任务,不会运行一下该程序就结束了;如果在shell下执行该命令,该命令不会退出,会一直占据终端。(像 `echo "hello world"`这样的命令就是一个瞬时任务,执行一下就结束了) ## **情景一** 我们把`nginx -g 'daemon off;'`直接作为镜像的CMD,并且为executable模式 ``` FROM nginx:1.11.5 CMD ["nginx", "-g", "daemon off;"] ``` 打成镜像并启动容器,去到容器里查看进程信息如下: ``` UID PID PPID C STIME TTY TIME CMD root 1 0 0 01:29 ? 00:00:00 nginx: master process nginx -g daemon off; nginx 5 1 0 01:29 ? 00:00:00 nginx: worker process ``` 可以看出,`pid=1`的进程就是nginx,而nginx是一个持久任务,所以该容器可以持久运行 ## **情景二** 我们把`nginx -g 'daemon off;'`直接作为镜像的CMD,但为shell模式 ``` FROM nginx:1.11.5 CMD nginx -g 'damone off;' ``` 打成镜像并启动容器,去到容器里查看进程信息如下: ``` UID PID PPID C STIME TTY TIME CMD root 1 0 0 01:34 ? 00:00:00 /bin/sh -c nginx -g 'daemon off;' root 5 1 0 01:34 ? 00:00:00 nginx: master process nginx -g daemon off; nginx 6 5 0 01:34 ? 00:00:00 nginx: worker process ``` 该容器中`pid=1`的进程为shell,该进程的启动命令为`/bin/sh -c nginx -g 'daemon off'`。该容器也能持久运行,因为`pid=1`的shell进程不会退出。 为什么该shell进程不会退出?因为shell(`pid=1`)进程在启动nginx(`pid=5`)这个子进程后会阻塞,直到nginx子进程退出返回,shell父进程才会继续往前走。而nginx是一个循环任务,它不会返回,所以shell进程会一直阻塞,不会结束。既然`pid=1`的进程一直都在,那么容器也不会退出。 ## **情景三** 我们把nginx的启动命令放到一个shell脚本里,容器的CMD设置为该脚本 ``` FROM nginx:1.11.5 COPY entrypoint.sh / CMD ["/entrypoint.sh"] ``` `entrypoint.sh`的内容如下: ``` #!/bin/bash echo "hello world 1" >> /hello.log nginx -g 'daemon off;' echo "hello world 2" >> /hello.log ``` 打成镜像并启动容器,去到容器里查看进程信息如下: ``` UID PID PPID C STIME TTY TIME CMD root 1 0 0 02:01 ? 00:00:00 /bin/bash /entrypoint.sh root 5 1 0 02:01 ? 00:00:00 nginx: master process nginx -g daemon off; nginx 6 5 0 02:01 ? 00:00:00 nginx: worker process ``` 可以看出,`pid=1`的进程为bash进程,启动命令为`/bin/bash /entrypoint.sh`。该容器也能持久运行,原因与情景二是一样的。 细心的你可能会发现,在`entrypoint.sh`中有两个echo语句,如果我们去查看`/hello.log`文件的内容,会发现,第二个echo语句并没有执行。这是为什么呢?因为第二个echo语句会在上一条语句`nginx -g 'daemon off;'`执行结束后才会执行,而nginx进程永远不会返回结束,所以后面的echo就不会执行到。 ## **情景四** 在情景二与情景三中,我们的容器都可以持久运行,但是我们的目标进程nginx的pid却不为1,那么有没有办法使nginx进程的pid变成1呢?答案是有,只需要在shell中在nginx的命令前加上`exec`关键子即可 ``` FROM nginx:1.11.5 COPY entrypoint.sh / CMD ["/entrypoint.sh"] ``` `entrypoint.sh`的内容如下: ``` #!/bin/bash echo "hello world 1" >> /hello.log exec nginx -g 'daemon off;' echo "hello world 2" >> /hello.log ``` 打成镜像并启动容器,去到容器里查看进程信息如下: ``` UID PID PPID C STIME TTY TIME CMD root 1 0 0 02:17 ? 00:00:00 nginx: master process nginx -g daemon off; nginx 5 1 0 02:17 ? 00:00:00 nginx: worker process ``` 可以看出,此时`pid=1`的进程变成了nginx,而不是shell。`entrypoint.sh`中的第二个echo语句还是同样不会执行。 在这里我们可能会问,既然在情景二与三中容器都能持久运行了,nginx进程的pid是否为1重要么?答案是重要。我们知道,我们可以为容器设置资源限制,如果容器中的nginx进程超出限制而被杀掉,而此时nginx进程的pid不为1且pid为1的进程因为其他的原因而没有退出,那么该容器虽然在跑,但已经不能提供nginx服务了,造成容器在,服务不在的问题。请阅读情景五的例子 ## **情景五** 接下来,我们来做个实验:nginx的pid不为1,nginx进程挂掉后,容器还能跑 Dockerfile如下: ``` FROM nginx:1.11.5 COPY entrypoint.sh / CMD ["/entrypoint.sh"] ``` entrypoint.sh的内容如下: ``` #!/bin/bash echo "hello world 1" >> /hello.log nginx -g 'daemon off;' tail -f /dev/null ``` 打成镜像并启动容器,去到容器里查看进程信息如下: ``` UID PID PPID C STIME TTY TIME CMD root 1 0 4 03:14 ? 00:00:00 /bin/bash /entrypoint.sh root 5 1 0 03:14 ? 00:00:00 nginx: master process nginx -g daemon off; nginx 6 5 0 03:14 ? 00:00:00 nginx: worker process ``` 我们发现,bash进程的pid为1,nginx进程的pid为5,而`tail -f /dev/null`还没有执行到,所以看不到该进程的信息 接下来,我们在该容器中执行命令 `kill 5` 来杀掉pid为5的nginx进程,然后再查看容器内的进程信息,如下: ``` UID PID PPID C STIME TTY TIME CMD root 1 0 0 03:14 ? 00:00:00 /bin/bash /entrypoint.sh root 16 1 0 03:17 ? 00:00:00 tail -f /dev/null ``` 可以看到,nginx进程杀掉后,bash进程(`pid=1`)收到了nginx进程(`pid=5`)的返回结果,bash进程继续往后执行`tail -f /dev/null`命令,该命令起的进程(`pid=16`)也是一个持久任务,于是再一次让bash进程(pid=1)发生阻塞,所以容器并不会退出。 接下来,我们在该容器中执行命令 `kill 16` 来杀掉pid为16的tail进程,当执行完这条命令后,实验证明容器就退出了。这是因为bash进程收到pid为16的tail进程的返回结果后,继续往前走,由于所有的命令已执行完,bash进程退出,pid为1的bash进程退出后,容器也就退出了。 ## **情景六** 在物理机或虚拟机中,我们启动nginx进程一般会加上`&`符号使进程在后台运行。接下来,我们看一下在entrypoint.sh中为nginx加上后台运行符号会是什么效果。 ``` FROM nginx:1.11.5 COPY entrypoint.sh / CMD ["/entrypoint.sh"] ``` `entrypoint.sh`的内容如下: ``` #!/bin/bash echo "hello world 1" >> /hello.log nginx -g 'daemon off;' & ``` 实验证明,容器很快就退出了。为什么会这样呢,这是因为加上后台运行符号`&`后,bash进程不需要等待nginx进程的返回就可以继续往下走,所以bash进程很快就运行结束,然后就退出了。 ## **最佳实践** * 一个容器尽量只跑一个业务进程 * 让业务进程的pid保持为1:如果业务进程可以在CMD中启动,则使用executable模式;如果必须放在shell脚本中启动,则在业务进程的启动命令前加上`exec`关键字使其pid保持为1