# 前台运行

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

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://pshizhsysu.gitbook.io/docker/dockerfile/qian-tai-yun-xing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
