# 前台运行

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

**在容器中，如果`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
