前台运行

我们在打镜像时经常会遇到这样的场景,打好的镜像跑起来之后马上就退出了。

在容器中,如果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

Last updated