docker-compose
现代应用程序由多个应用组成,比如 Web 应用里可能有 Mysql 用来当数据库、Redis 用来做数据缓存。
当开启一个应用容器时,需要先将 Mysql 和 Redis 启动起来,再将应用容器运行起来。
同时,我们还需要在有互相通信的容器之间创建网络,以便他们进行数据交换。
如果我们对容器进行了各种配置,我们还需要将容器创建和配置的命令保存下来,以便下次可以直接使用。
为了应对上述那么多复杂的场景,Docker 给出了 docker-compose 方案。
如果说 Dockerfile 能够对容器内运行的环境的搭建固化下来,那么 Docker Compose 就可以理解成将多个容器运行的方式和配置固化到一个 yml 文件中,再通过这个 yml 文件启动多个容器。
nest 项目 docker-compose 示例
Docker-compose 的步骤分成三步:
- 编写容器所需的镜像的 Dockerfile。(非必须)
- 编写用于配置容器的 docker-compose.yml 描述文件。
- docker-compose 命令启动应用。
最核心的是第二部,下面是部署 nestjs app 的示例:
yaml1services: 2 server: 3 build: 4 context: . 5 restart: on-failure:2 6 environment: 7 - NODE_ENV=production 8 - DATABASE_URL=postgresql://postgres:root@db:5432/postgres?schema=public 9 ports: 10 - 8888:3000 11 depends_on: 12 db: 13 condition: service_healthy 14 15 db: 16 image: postgres:14 17 restart: always 18 user: postgres 19 secrets: 20 - db-password 21 volumes: 22 - db-data:/var/lib/postgresql/data 23 environment: 24 - POSTGRES_DB=postgres 25 - POSTGRES_PASSWORD_FILE=/run/secrets/db-password 26 expose: 27 - 5432 28 ports: 29 - 5432:5432 30 healthcheck: 31 test: ['CMD', 'pg_isready'] 32 interval: 10s 33 timeout: 5s 34 retries: 5 35 36 redis: 37 image: redis:7.0 38 restart: unless-stopped 39 volumes: 40 - redis-data:/data 41 expose: 42 - 6379 43 ports: 44 - 6379:6379 45 46volumes: 47 db-data: 48 redis-data: 49secrets: 50 db-password: 51 file: db/password.txt
-
定义服务
Docker Compose 中一个服务对应一个容器,redis、server、db 就是服务的名称。对服务的定义跟之前的创建和启动容器非常相似,Docker Compose 会从配置文件中读取出这些内容,帮助我们创建和管理这些容器。
-
指定镜像
容器的基础就是镜像,所以每个服务需要指定镜像。
我们可以通过两种方式指定镜像,一种是通过 image 配置,这个非常简单,只需要能给出在
Docker Hub中找到的镜像的名称即可。另外一种是直接采用 Dockerfile 来构建镜像,通过
build这个配置我们能够定义构建的环境目录,相当于执行docker build。如果我们用这个方式指定镜像,那么 Docker-Compose 会先执行镜像的构建,再通过这个镜像启动容器。下面是一个常用的
build选项的配置yaml1webapp: 2 build: 3 context: ./webapp 4 dockerfile: webapp-dockerfile 5 args: 6 - JAVA_VERSION=1.6context表示路径,dockerfile表示 Dockerfile 的文件名,args表示构建的参数。command配置能够指定基于该镜像启动容器时执行的命令,相当于替代 Dockerfile 中的 CMDenvironment配置则是环境变量,例如例子中给 POSTGRES 设置了初始密码 -
依赖声明
如果我们的服务间有非常强的依赖关系、先后顺序关系,就必须告知 Docker Compose 容器的启动顺序。只有当被依赖的容器完全启动后,Docker Compose 才能创建和启动这个容器。
定义依赖关系是用
depends_on这个配置项。 -
文件挂载和数据卷
文件挂载和数据卷都用
volumes字段指定,区别在于一个是使用文件的路径,一个是使用卷的名字。在开发时,推荐将代码挂载到容器里,这样在代码修改后,容器内也可以马上做出反应。
Docker Compose 也能够自动完成对数据卷的创建,在上面的例子中,独立于 services 的 volumes 配置是用来声明数据卷的。使用数据卷则是在
services的volumes里用<Volume Name>:<Container dataStorage>这种形式定义。 -
端口映射
在 Docker Compose 的每个服务配置里,我们还看到了
ports这个配置项,它是用来定义端口映射的。在
Docker CLI中相当于-p选项,用来指定宿主机和容器中的端口映射。
通过 docker compose up命令,即可创建包含多个服务的 docker-compose。
重启策略
node 服务往往用 pm2 来守护进程,在服务器上跑 node 时,一般都会配置 pm2。
pm2 在进程崩溃时也有重启进程的功能。
docker 支持自动重启,所以我们用 docker 方案就不需要 pm2 了。
有 4 种重启策略:
- no: 容器退出不自动重启(默认值)
- always:容器退出总是自动重启,除非 docker stop。
- on-failure:容器非正常退出才自动重启,还可以指定重启次数,如 on-failure:5
- unless-stopped:容器退出总是自动重启,除非 docker stop
以上面的示例产生的 docker-compose 为例,
docker-compose 的 server 是这么配的
yml1restart: on-failure:2
db 是这么配的
yml1restart: always
redis 是这么配的
yml1restart: unless-stopped
我们在项目源代码的 main.js 中特意写一个 error:
jade1async function bootstrap() { 2 const app = await NestFactory.create(AppModule); 3 throw new Error('This is an error'); 4 await app.listen(3000); 5}
然后docker compose up看一下生成的 compose 容器。

点开状态为Exited的容器,会发现内部报错了,并且重启了一次。

这就是on-failure的作用,当遇到非正常情况时,会重启并且可以设置重启次数。
always 和 unless-stopped有什么差别呢?
差别在于 Docker Deamon 退出的时候。
现在我们关闭这两个容器

然后重启 docker。
当 docker 重启时,docker Engine 也会启动,docker Engine 是 Docker Deamon 的后台进程。

当 Docker Deamon 启动完成后,原来策略为 always的容器会自动打开,而策略为unless-stopped的容器还是关闭状态。

这就是两者之间的差别。
总结
docker compose 是记录容器的各种配置的 yaml 文件,通过它可以描述容器的配置、启动、挂载、网络、依赖关系等。
Docker 也是支持自动重启的,可以在 docker run 的时候通过 --restart 指定重启策略,或者 Docker Compose 配置文件里配置 restart。
有 4 种重启策略:
- no: 容器退出不自动重启(默认值)
- always:容器退出总是自动重启,除非 docker stop。
- on-failure:容器非正常退出才自动重启,还可以指定重启次数,如 on-failure:5
- unless-stopped:容器退出总是自动重启,除非 docker stop
always 和 unless-stopped 的差别在于 always策略的容器会在 Docker Deamon 启动时启动,而unless-stopped不会。
绝大多数情况下,直接用 Docker 跑 node 脚本就行,不需要 PM2。