Docker Compose 入门教程

Docker compose (以下简称为 compose)可用于定义和运行多容器 docker 应用程序。
通过 compose,我们可以使用 YAML 文件来配置应用程序的服务(services),然后只需要通过一个命令,就可以将配置的所有服务启动起来。

使用 compose 只需要三步:

  1. 使用 Dockerfile 定义应用的环境,以便于可以任何地方复制应用的环境
  2. 使用 docker-compose.yml 定义构成应用的服务,以便于它们可以在隔离的环境中一起运行
  3. 运行 docker-compose up 命令,这时 compose 会启动并运行整个应用程序

安装

对于 Mac 系统 和 Windows 系统,安装 docker 桌面端时, compose 已经捆绑安装了。对于 Linux 系统安装 compose 也不麻烦,可以参考官方安装文档 https://docs.docker.com/compose/install/

可以通过以下命令来测试 compose 安装是否成功:

1
docker-compose --version

输出:

docker-compose version 1.24.1, build 4667896b

实践:以 Flask 应用为例

我们以一个 Flask 应用程序为例,说明如何使用 compose 来配置相关的服务。

步骤一:编写源代码

  1. 创建源代码目录 composetest
1
2
mkdir composetest
cd composetest
  1. 创建 Flask 应用

创建 app.py 源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)


def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)


@app.route('/')
def hello():
count = get_hit_count()
return 'Hello World! I have been seen {} times.\n'.format(count)

上述的代码中,redis 是 redis 容器的主机名称,且 redis 的监听端口为 6379。对于 compose 来说,可以使用容器名称来作为主机名称来访问其他容器,具体可以参考 compose 网络
注意,如果使用 compose 运行 app.py,则 redis 的主机地址不能使用 127.0.0.1,否则会出现无法连接 redis 的报错。

  1. 创建 python 依赖

创建 requirements.txt 文件,里面包含应用程序依赖的 python 包。

1
2
flask
redis

步骤二:创建 Dockerfile

接下来,我们编写 Dockerfile 文件来构建 docker 镜像。这个镜像包含了 flask 应用程序的依赖环境。

composetest 目录创建 Dockerfile 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM python:3.7-alpine

WORKDIR /code

ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0

RUN apk add --no-cache gcc musl-dev linux-headers

COPY requirements.txt requirements.txt

RUN pip install -r requirements.txt

COPY . .
CMD ["flask", "run"]

这个 Dockerfile 文件作用包括:

  • 构建的基础镜像是 python:3.7-alpine
  • 工作目录为 /code
  • 设置 flask 应用程序的环境变量,有关 flask 环境变量的使用,可以参考 flask quickstart
  • 安装 gcc 等软件
  • 复制 requirements.txt 文件到工作目录,以便于安装有关 python 依赖
  • 复制当前目录文件到工作目录 /code
  • 设置容器启动命令,以便启动 flask 应用程序

有关 Dockerfile 的使用,请参考官方文档 Dockerfile 官方文档

步骤三:定义 compose 服务

接下来,我们编写 docker-compose.yml。在 composetest 目录创建 docker-compose.yml 文件:

1
2
3
4
5
6
7
8
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"

docker-compose.yml 文件定义了两个服务:webredis

  • web 服务:web 服务的镜像由 Dockerfile 来指定构建。另外,我们还指定的宿主机和容器间端口的映射关系 5000:5000
  • redis 服务:redis 服务的镜像从 docker 镜像仓库拉取

注意每个服务都必须通过 image 指定镜像或 build 指令(需要 Dockerfile)来自动构建生成镜像。

最后,composetest 目录内的文件如下图所示:

步骤四:运行

进入 composetest 目录,执行命令:

1
docker-compose up

接下来,compose 会构建 web 镜像,拉取 redis 镜像,并启动服务。

启动完毕,打开浏览器,输入地址 http://localhost:5000/,会看到以下输出:

Hello World! I have been seen 1 times.

刷新一下页面,会看到数据不断地增加 1。

使用命令 docker images 可以观察到当前的镜像,使用命令 docker ps -a 可以观察当前正在运行的容器,有需要的话,还可以使用命令 docker exec -it xxx /bin/sh 进入容器。

为了关闭并删除 compose 启动的容器,可以执行命令:

1
docker-compose down

步骤五:往 compose 文件添加 bind mount

修改 docker-compose.yml 文件,往 web 服务添加 bind mount:

1
2
3
4
5
6
7
8
9
10
11
12
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
environment:
FLASK_ENV: development
redis:
image: "redis:alpine"

通过 volumes 关键字我们将当前目录 composetest 挂载到容器内的 /code 目录,这样一来,我们就可以随时修改应用程序的源代码,而不需要重新构建镜像。
同时,我们增加了新的环境变量 FLASK_ENV(值为 development),这使得可以修改 flask 应用的代码随即生效,而不需要对 flask 应用进行重启。

有关 bind mount 的使用,可以参考文章 《Docker 数据持久化》

重新执行命令 docker-compose up,启动应用程序。打开浏览器,输入地址,看到输出

Hello World! I have been seen 1 times.

这时,修改本地 composetest 目录的 app.py 源代码,调整问候语,然后保存。

1
2
3
4
@app.route('/')
def hello():
count = get_hit_count()
return 'Hello from Docker! I have been seen {} times.\n'.format(count)

再刷新浏览器,可以看到输出的问候语已更新,说明由于 bind mount 的使用,通过修改本地源代码而不需要重新构建镜像,应用程序就已经得到了更新。

Hello from Docker! I have been seen 2 times.

参考资料