在 Docker Compose 中让容器保持运行
1、概览
一个 Docker 容器会运行一个进程、应用程序,有时仅是一个脚本或命令,以执行其设计任务。
每个容器一旦内部没有任何进程或脚本运行,就会停止并退出。有些容器默认会持续运行,直到用户选择停止它们,例如 MySQL 数据库容器、Spring Boot Web 应用容器或 SMTP 邮件服务器容器。但有时,我们需要让容器在其主要任务完成后仍然保持运行,比如 Ubuntu 容器。
本文将带你了解如何使用 Docker Compose 实现这一需求。
2、Docker Compose 设置
Docker Compose 是我们用来定义和运行多容器服务的工具。唯一的前提条件是在受支持的操作系统平台上安装 Docker(包括 Docker Server、Docker Client 和 Docker Compose)。
本教程中我们使用 Linux Ubuntu。
3、运行 Ubuntu 容器
我们使用一个名为 docker-compose.yml
的示例 Docker Compose 配置文件来定义服务:
services:
demo:
image: ubuntu
3.1、启动 Docker Compose 服务
使用 -d
选项在后台运行服务的容器:
$ docker-compose up -d
输出如下,显示它创建了一个容器:
Creating ubuntu_demo_1 ... done
查看 Docker Compose 服务创建的容器是否正在运行。
使用 docker ps -a
命令列出容器:
$ docker ps -a
结果显示了一个包含运行中和已退出容器的列表:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a66cec15e72c ubuntu "/bin/bash" 10 seconds ago Exited (0) 9 seconds ago ubuntu_demo_1
由 Docker Compose 服务启动的 ubuntu_demo_1 容器在创建后不久便会退出。
设置完成后,来看看保持容器运行的几种方法:
- 分配 TTY
- 使用
tail
命令 - 使用
sleep
命令 - 使用死循环脚本
3.2、测试间的清理工作
在逐一尝试这些示例时,最好在每个演示的策略之间使用 docker-compose down
命令来清理容器。
$ docker-compose down
Stopping ubuntu_demo_1 ... done
Removing ubuntu_demo_1 ... done
Removing network ubuntu_default
该命令还会删除为 Docker Compose 服务创建的所有网络(Network)和卷(Volume)。
4、分配 TTY
可以为示例 Ubuntu 容器分配一个伪终端(pseudo-TTY)来保持其运行。之所以称为 “伪” 终端,是因为没有使用物理设备驱动程序;伪终端驱动程序使容器看起来像终端连接会话。
通过向示例配置文件中添加键值对 tty: true
来分配 TTY:
services:
demo:
image: ubuntu
tty: true
现在,使用相同的命令创建服务容器:
$ docker-compose up -d
可以看到它仍在运行:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dcc44f90a9eb ubuntu "/bin/bash" 16 seconds ago Up 13 seconds ubuntu_demo_1
由 Docker Compose 服务启动的 ubuntu_demo_1 容器正在运行。当我们附加 TTY 时,Docker 守护进程会保持主进程运行,从而使容器持续运行。
5、使用 tail 命令
另一种保持容器运行的方法是使用 tail
命令:
services:
demo:
image: ubuntu
command: "tail -f /dev/null"
我们在这里使用 tail
命令及其 -f
选项来持续从 TTY 上的 stdin
读取数据。通过指定 /dev/null
,tail
会永久 “读取” 该设备,从而使容器保持运行状态。
启动 Docker Compose 服务,随后列出所有运行中的容器:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5b293dbf6cc9 ubuntu "tail -f /dev/null" 18 seconds ago Up 17 seconds ubuntu_demo_1
可以看到,ubuntu_demo_1 容器仍在运行。
6、使用 sleep 命令
还可以通过 sleep
命令保持容器运行:
services:
demo:
image: ubuntu
command: "sleep infinity"
这种方法之所以有效,是因为它在容器的 bash shell 中创建了一个 TTY。使用 infinity
参数意味着该命令永远不会完成,从而使容器永久保持运行状态。
再次创建服务容器,随后列出所有运行中的容器:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c55a8b1f3839 ubuntu "sleep infinity" 2 minutes ago Up 2 minutes ubuntu_demo_1
同样,容器正在运行。
7、使用死循环脚本
此外,我们还可以在 Docker Compose 启动的容器中运行一个由 while
和 do
命令组合的死循环脚本,使容器保持运行。
如下:
services:
demo:
image: ubuntu
command: "sh -c 'while true; do sleep 1; done'"
运行修改后的 Docker Compose 服务,随后列出所有运行中的容器:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1f690849f774 ubuntu "sh -c 'while true; …" 14 seconds ago Up 12 seconds ubuntu_demo_1
Docker 容器持续运行是因为 while true
条件始终为 true
。
与单纯使用 sleep infinity
命令相比,无限 while
循环脚本的优势在于:用户可以在循环内执行其他命令来完成诸如输出日志信息或检查进程状态等任务。
8、为什么这对 hello-world 无效?
注意,并非所有类型的容器都能通过这些方法保持运行。我们上述所有示例的前提是:能在容器内启动 bash shell 来执行 Linux 命令。这对于使用 ubuntu 镜像或其他基于 Linux 的镜像创建的 Docker Compose 服务有效。
但以 hello-world
镜像为基础的容器就无法保持运行。要理解其原因,来看下 hello-world
镜像的 Dockerfile:
FROM scratch
COPY hello /
CMD ["/hello"]
scratch
镜像通常用于构建基础镜像,其本身并不提供 Linux 环境。没有 Linux 环境,就无法在 shell 中运行命令。
9、总结
本文介绍了通过 Docker Compose 保持 Docker 容器运行的不同方法。需要注意的是,并非所有 Docker 容器都需要显式配置来保持运行。因为部分 Docker 容器会默认启动内部应用程序或进程作为常规功能,这些进程会自然维持容器运行。
此外,并非所有类型的容器都能被保持运行,因为有些容器本身不支持运行 Bash shell 或其他 Linux 命令。
Ref:https://www.baeldung.com/docker-compose-keep-container-running