首页 > 编程笔记 > Go语言笔记

使用Docker Compose部署应用(详解版)

下面为大家介绍一下如何使用 Docker Compose 部署多容器的应用。

Docker Compose 与 Docker Stack 非常类似。它能够在 Docker 节点上,以单引擎模式(Single-Engine Mode)进行多容器应用的部署和管理。

多数的现代应用通过多个更小的服务互相协同来组成一个完整可用的应用。比如一个简单的示例应用可能由如下 4 个服务组成。

将以上服务组织在一起,就是一个可用的应用。

部署和管理繁多的服务是困难的。而这正是 Docker Compose 要解决的问题。

Docker Compose 并不是通过脚本和各种冗长的 docker 命令来将应用组件组织起来,而是通过一个声明式的配置文件描述整个应用,从而使用一条命令完成部署。

应用部署成功后,还可以通过一系列简单的命令实现对其完整声明周期的管理。甚至,配置文件还可以置于版本控制系统中进行存储和管理。

Docker Compose 的背景

Docker Compose 的前身是 Fig。Fig 是一个由 Orchard 公司开发的强有力的工具,在当时是进行多容器管理的最佳方案。

Fig 是一个基于 Docker 的 Python 工具,允许用户基于一个 YAML 文件定义多容器应用,从而可以使用 fig 命令行工具进行应用的部署。

Fig 还可以对应用的全生命周期进行管理。

内部实现上,Fig 会解析 YAML 文件,并通过 Docker API 进行应用的部署和管理。

在 2014 年,Docker 公司收购了 Orchard 公司,并将 Fig 更名为 Docker Compose。

命令行工具也从 fig 更名为 docker-compose,并自此成为绑定在 Docker 引擎之上的外部工具。

虽然它从未完全集成到 Docker 引擎中,但是仍然受到广泛关注并得到普遍使用。

直至今日,Docker Compose 仍然是一个需要在 Docker 主机上进行安装的外部 Python 工具。

使用它时,首先编写定义多容器(多服务)应用的 YAML 文件,然后将其交由 docker-compose 命令处理,Docker Compose 就会基于 Docker 引擎 API 完成应用的部署。

安装 Docker Compose

Docker Compose 可用于多种平台。下面将分别介绍在 Windows、Mac 以及 Linux 上的几种安装方法。

1) 在 Windows 10 上安装 Docker Compose

在 Windows 10 上运行 Docker 的推荐工具是 Windows 版 Docker(Docker for Windows, DfW)。

Docker Compose 会包含在标准 DfW 安装包中。所以,安装 DfW 之后就已经有 Docker Compose 工具了。

在 PowerShell 或 CMD 终端中使用如下命令可以检查 Docker Compose 是否安装成功。

> docker-compose --version
docker-compose version 1.18.0, build 8dd22a96

关于在 Windows 10 上安装 Windows 版 Docker 的详细步骤请参考《Windows Docker安装》一节。

2) 在 Mac 上安装 Docker Compose

与 Windows 10 一样,Docker Compose 也作为 Mac 版 Docker(Docker for Mac, DfM)的一部分进行安装,所以一旦安装了 DfM,也就安装了 Docker Compose。

在终端中运行如下命令检查 Docker Compose 是否安装。

$ docker-compose --version
docker-compose version 1.18.0, build 8dd22a96

关于安装 Mac 版Docker的详细内容请参考《Mac Docker安装》一节。

3) 在 Windows Server 上安装 Docker Compose

Docker Compose 在Windows Server 上是作为一个单独的二进制文件安装的。

因此,使用它的前提是确保在 Windows Server 上已经正确安装了 Docker。

在 PowerShell 终端中输入如下命令来安装 Docker Compose。

为了便于阅读,下面的命令使用反引号(`)来对换行进行转义,从而将多行命令合并。

下面的命令安装的是 1.18.0 版本的 Docker Compose,可以参考 GitHub 自行选择版本号(https://github.com/docker/compose/releases)。

只需要将 URL 中的 1.18.0 替换为你希望安装的版本即可。

> Invoke-WebRequest ` "https://github.com/docker/compose/releases/download/1\
.18.0/docker-compose-Windows-x86_64.exe" `
-UseBasicParsing `
-OutFile $Env:ProgramFiles\docker\docker-compose.exe

Writing web request
Writing request stream... (Number of bytes written: 5260755)

使用 docker-compose --version 命令查看安装情况。

> docker-compose --version
docker-compose version 1.18.0, build 8dd22a96

Docker Compose 安装好了,只要 Windows Server 上安装有 Docker 引擎即可使用。

4) 在 Linux 上安装 Docker Compose

在 Linux 上安装 Docker Compose分为两步。

首先使用 curl 命令下载二进制文件,然后使用 chmod 命令将其置为可运行。

Docker Compose 在 Linux 上的使用,同样需要先安装有 Docker 引擎。

如下命令会下载 1.18.0 版本的 Docker Compose 到 /usr/bin/local。请在 GitHub 上查找想安装的版本(https://github.com/docker/compose/releases),并替换 URL 中的 1.18.0。

下面的示例是一条写成多行的命令,如果要将其合并为一行,请删掉反斜杠(\)。

https% Total % Received Time Time Time Current

下载 docker-compose 二进制文件后,使用如下命令使其可执行。

$ chmod +x /usr/local/bin/docker-compose

检查安装情况以及版本。

$ docker-compose --version
docker-compose version 1.18.0, build 8dd22a9

现在就可以在 Linux 上使用 Docker Compose 了。

此外,也可以使用 pip 来安装 Docker Compose 的 Python 包。

Compose 文件

Docker Compose 使用 YAML 文件来定义多服务的应用。YAML 是 JSON 的一个子集,因此也可以使用 JSON。

Docker Compose 默认使用文件名 docker-compose.yml。当然,也可以使用 -f 参数指定具体文件。

如下是一个简单的 Compose 文件的示例,它定义了一个包含两个服务(web-fe 和 redis)的小型 Flask 应用。

这是一个能够对访问者进行计数并将其保存到 Redis 的简单的 Web 服务。

version: "3.5"
services:
web-fe:
build: .
command: python app.py
ports:
- target: 5000
published: 5000
networks:
- counter-net
volumes:
- type: volume
source: counter-vol
target: /code
redis:
image: "redis:alpine"
networks:
counter-net:

networks:
counter-net:

volumes:
counter-vol:

在深入研究之前粗略观察文件的基本结构,首先可以注意到,它包含 4 个一级 key:version、services、networks、volumes。

version 是必须指定的,而且总是位于文件的第一行。它定义了 Compose 文件格式(主要是 API)的版本。

注意,version 并非定义 Docker Compose 或 Docker 引擎的版本号。

示例中 Compose 文件将使用版本 3 及以上的版本。

services 用于定义不同的应用服务。上边的例子定义了两个服务:一个名为 web-fe 的 Web 前端服务以及一个名为 redis 的内存数据库服务。

Docker Compose 会将每个服务部署在各自的容器中。

networks 用于指引 Docker 创建新的网络。默认情况下,Docker Compose 会创建 bridge 网络。

这是一种单主机网络,只能够实现同一主机上容器的连接。当然,也可以使用 driver 属性来指定不同的网络类型。

下面的代码可以用来创建一个名为 over-net 的 Overlay 网络,允许独立的容器(standalone container)连接(attachable)到该网络上。

networks:
over-net:
driver: overlay
attachable: true

volumes 用于指引 Docker 来创建新的卷。

上面例子中的 Compose 文件使用的是 v3.5 版本的格式,定义了两个服务,一个名为 counter-net 的网络和一个名为 counter-vol 的卷。

更多的信息在 services 中,下面仔细分析一下。

Compose 文件中的 services 部分定义了两个二级 key:web-fe 和 redis。

它们各自定义了一个应用程序服务。需要明确的是,Docker Compose 会将每个服务部署为一个容器,并且会使用 key 作为容器名字的一部分。

本例中定义了两个 key:web-fe 和 redis。因此 Docker Compose 会部署两个容器,一个容器的名字中会包含 web-fe,而另一个会包含 redis。

web-fe 的服务定义中,包含如下指令。

1) build

指定 Docker 基于当前目录(.)下 Dockerfile 中定义的指令来构建一个新镜像。该镜像会被用于启动该服务的容器。

2) command

python app.py 指定 Docker 在容器中执行名为 app.py 的 Python 脚本作为主程序。

因此镜像中必须包含 app.py 文件以及 Python,这一点在 Dockerfile 中可以得到满足。

3) ports

指定 Docker 将容器内(-target)的 5000 端口映射到主机(published)的 5000 端口。

这意味着发送到 Docker 主机 5000 端口的流量会被转发到容器的 5000 端口。容器中的应用监听端口 5000。

4) networks

使得 Docker 可以将服务连接到指定的网络上。这个网络应该是已经存在的,或者是在 networks 一级 key 中定义的网络。

对于 Overlay 网络来说,它还需要定义一个 attachable 标志,这样独立的容器才可以连接上它(这时 Docker Compose 会部署独立的容器而不是 Docker 服务)。

5) volumes

指定 Docker 将 counter-vol 卷(source:)挂载到容器内的 /code(target:)。

counter-vol 卷应该是已存在的,或者是在文件下方的 volumes 一级 key 中定义的。

综上,Docker Compose 会调用 Docker 来为 web-fe 服务部署一个独立的容器。该容器基于与 Compose 文件位于同一目录下的 Dockerfile 构建的镜像。

基于该镜像启动的容器会运行 app.py 作为其主程序,将 5000 端口暴露给宿主机,连接到 counter-net 网络上,并挂载一个卷到/code。

从技术上讲,本例并不需要配置 command: python app.py。因为镜像的 Dockerfile 已经将 python app.py 定义为了默认的启动程序。

但是,本例主要是为了展示其如何执行,因此也可用于覆盖 Dockerfile 中配置的 CMD 指令。

redis 服务的定义相对比较简单。

6) image

redis:alpine 使得 Docker 可以基于 redis:alpine 镜像启动一个独立的名为 redis 的容器。

这个镜像会被从 Docker Hub 上拉取下来。

7) networks

配置 redis 容器连接到 counter-net 网络。

由于两个服务都连接到 counter-net 网络,因此它们可以通过名称解析到对方的地址。了解这一点很重要,本例中上层应用被配置为通过名称与 Redis 服务通信。

使用 Docker Compose 部署应用

下面将实际部署 Compose 文件中定义的应用。可以从我的百度网盘(https://pan.baidu.com/s/1Q0tcQnSqUQ06rfEdH_MBaw 提取码: a55v)下载所需的文件。

将代码下载到本地,并部署到新创建的名为 counter-app 的目录中。

该目录包含所需的所有文件,可以作为构建上下文。

Docker Compose 会使用目录名(counter-app)作为项目名称,这一点在后续的操作中会看到,Docker Compose 会将所有的资源名称中加上前缀 counter-app_。

进入 counter-app 目录中,检查文件是否存在。

$ cd counter-app
$ ls
app.py docker-compose.yml Dockerfile requirements.txt ...

简要介绍这几个文件。

app.py 显然是应用的核心文件,而 docker-compose.yml 文件将应用的所有组件组织起来。

下面使用 Docker Compose 将应用启动起来。以下所有的命令都是运行在刚才创建的 counter-app 目录下的。

$ docker-compose up &

[1] 1635
Creating network "counterapp_counter-net" with the default driver
Creating volume "counterapp_counter-vol" with default driver
Pulling redis (redis:alpine)...
alpine: Pulling from library/redis
1160f4abea84: Pull complete
a8c53d69ca3a: Pull complete
<Snip>
web-fe_1 | * Debugger PIN: 313-791-729

启动应用将花费几秒钟时间,其输出也非常详尽。下面我们来讲解一下 docker-compose 命令。

常用的启动一个 Compose 应用(通过 Compose 文件定义的多容器应用称为“Compose 应用”)的方式就是 docker-compose up 命令。

它会构建所需的镜像,创建网络和卷,并启动容器。

默认情况下,docker-compose up 会查找名为 docker-compose.yml 或 docker-compose.yaml 的 Compose 文件。

如果 Compose 文件是其他文件名,则需要通过 -f 参数来指定。

如下命令会基于名为 prod-equus-bass.yml 的 Compose 文件部署应用。

$ docker-compose -f prod-equus-bass.yml up

使用 -d 参数在后台启动应用也是常见的用法,代码如下。

docker-compose up -d

--OR--

docker-compose -f prod-equus-bass.yml up -d

前面的示例命令在前台启动应用(没有使用 -d 参数),但是使用了 & 将终端窗口返回。

这种用法不太正规,所有的日志还是会直接输出到我们后续可能会用的终端窗口上。

这样应用就构建并启动起来了,可以直接使用 docker 命令来查看 Docker Compose 创建的镜像、容器、网络和卷。

$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
counterapp_web-fe latest 96..6ff9e 3 minutes ago 95.9MB
python 3.4-alpine 01..17a02 2 weeks ago 85.5MB
redis alpine ed..c83de 5 weeks ago 26.9MB

可以看到有 3 个在部署过程中构建或拉取的镜像。

counterapp_web-fe:latest 镜像源自 docker-compose.yml 文件中的 build: . 指令。

该指令让 Docker 基于当前目录下的 Dockerfile 来构建一个新的镜像。

该镜像基于 python:3.4-alpine 构建,其中包含 Python Flask Web 应用的程序代码。更多信息可以通过查看 Dockerfile 的内容进行了解。

为了方便理解,每一行都添加了注释。部署时要删除掉。

请注意,Docker Compose 会将项目名称(counter-app)和 Compose 文件中定义的资源名称(web-fe)连起来,作为新构建的镜像的名称。

Docker Compose 部署的所有资源的名称都会遵循这一规范。

由于 Compose 文件的 .Services.redis 项中指定了 image: "redis:alpine",因此会从 Docker Hub 拉取 redis:alpine 镜像。

如下命令列出了两个容器。每个容器的名称都以项目名称(所在目录名称)为前缀。此外,它们还都以一个数字为后缀用于标识容器实例序号,因为 Docker Compose 允许扩缩容。

$ docker container ls
ID COMMAND STATUS PORTS NAMES
12.. "python app.py" Up 2 min 0.0.0.0:5000->5000/tcp counterapp_web-fe_1
57.. "docker-entry.." Up 2 min 6379/tcp counterapp_redis_1

counterapp_web-fe 容器中运行的是应用的 Web 前端。其中执行的是 app.py,并且被映射到了 Docker 主机的 5000 端口,稍后会进行连接。

如下的网络和卷列表显示了名为 counterapp_counter-net 的网络和名为 counterapp_counter-vol 的卷。

$ docker network ls
NETWORK ID NAME DRIVER SCOPE
1bd949995471 bridge bridge local
40df784e00fe counterapp_counter-net bridge local
f2199f3cf275 host host local
67c31a035a3c none null local

$ docker volume ls
DRIVER VOLUME NAME
<Snip>
local counterapp_counter-vol

应用部署成功后,读者可以用 Docker 主机的浏览器连接 5000 端口来查看应用的运行效果,如下图所示。

应用部署成功的运行效果

单击浏览器的刷新按钮,计数会增加。

如果使用 & 启动应用,那么可以在终端窗口中看到包含 HTTP 响应码 200 的日志。这表明请求收到了正确的响应,每次加载页面都会有日志打印出来。

web-fe_1 | 172.18.0.1 - - [09/Jan/2018 11:13:21] "GET / HTTP/1.1" 200 -
web-fe_1 | 172.18.0.1 - - [09/Jan/2018 11:13:33] "GET / HTTP/1.1" 200 -

到此,多容器的应用已经借助 Docker Compose 成功部署了。

使用 Docker Compose 管理应用

下面介绍如何使用 Docker Compose 启动、停止和删除应用,以及获取应用状态。还会演示如何使用挂载的卷来实现对 Web 前端的更新。

既然应用已经启动,下面看一下如何使其停止。为了实现这一点,将子命令 up 替换成 down 即可。

由于是使用 & 启动的应用,因此它运行在前台。这意味着在终端上会打印详细的输出,从而可以很好地了解其执行过程。下面介绍一下每一行都代表什么意思。

第 1、2 行开始尝试关闭两个服务,即 Compose 文件中定义的 web-fe 和 redis。

由第 3 行可知 stop 指令会发送 SIGTERM 信号。信号会被发送到每个容器中 PID 为 1 的进程。

第 4~6 行显示 Redis 容器接收到信号后优雅地自行关闭。

第 7、8 行表明已成功停止 Redis。

第 9 行表明 web-fe 服务也被成功停止。

由第 10 和 11 行可知已停止的服务被删除。

第 12 行显示 counter-net 网络被删除,第 13 行显示 docker-compose up 进程退出。

需要特别注意的是,counter-vol 卷并没有被删除,因为卷应该是用于数据的长期持久化存储的。

因此,卷的生命周期是与相应的容器完全解耦的。执行 docker volume ls 可见该卷依然存在于系统中。写到卷上的所有数据都会保存下来。

同样,执行 docker-compose up 过程中拉取或构建的镜像也会保留在系统中。因此,再次部署该应用将更加快捷。

下面继续介绍其他几个 docker-compose 子命令。使用如下命令再次启动应用,但是这次在后台启动它。

$ docker-compose up -d
Creating network "counterapp_counter-net" with the default driver
Creating counterapp_redis_1 ... done
Creating counterapp_web-fe_1 ... done

这时会发现这次启动要快很多——因为 counter-vol 卷已经存在,而且不需要去拉取和构建镜像。

使用 docker-compose up 命令来查看应用的状态。

$ docker-compose ps
Name Command State Ports
--------------------------------------------------------------------------
counterapp_redis_1 docker-entrypoint... Up 6379/tcp
counterapp_web-fe_1 python app.py Up 0.0.0.0:5000->5000/tcp

输出中会显示容器名称、其中运行的 Command、当前状态以及其监听的网络端口。

使用 docker-compose top 命令列出各个服务(容器)内运行的进程。

$ docker-compose top
counterapp_redis_1
PID USER TIME COMMAND
------------------------------------
843 dockrema 0:00 redis-server

counterapp_web-fe_1
PID USER TIME COMMAND
-------------------------------------------------
928 root 0:00 python app.py
1016 root 0:00 /usr/local/bin/python app.py

其中 PID 编号是在 Docker 主机上(而不是容器内)的进程 ID。

docker-compose stop 命令会停止应用,但并不会删除资源。然后再次运行 docker-compose ps 查看状态。

$ docker-compose stop
Stopping counterapp_web-fe_1 ... done
Stopping counterapp_redis_1 ... done

$ docker-compose ps
Name Command State
---------------------------------------------------------
counterapp_redis_1 docker-entrypoint.sh redis Exit 0
counterapp_web-fe_1 python app.py Exit 0

可以看到,停止 Compose 应用并不会在系统中删除对应用的定义,而仅将应用的容器停止。这一点可以使用 docker container ls -a 命令进行验证。

对于已停止的 Compose 应用,可以使用 docker-compose rm 命令来删除。这会删除应用相关的容器和网络,但是不会删除卷和镜像。

当然,也不会删除应用源码(项目目录下的 app.py、Dockerfile、requirements.txt 和 docker-compose.yml)。

执行 docker-compose restart 命令重启应用。

$ docker-compose restart
Restarting counterapp_web-fe_1 ... done
Restarting counterapp_redis_1 ... done

查看执行结果。

$ docker-compose ps
Name Command State Ports
--------------------------------------------------------------------------
counterapp_redis_1 docker-entrypoint... Up 6379/tcp
counterapp_web-fe_1 python app.py Up 0.0.0.0:5000->5000/tcp

使用 docker-compose down 这一个命令就可以停止和关闭应用。

$ docker-compose down
Stopping counterapp_web-fe_1 ... done
Stopping counterapp_redis_1 ... done
Removing counterapp_web-fe_1 ... done
Removing counterapp_redis_1 ... done
Removing network counterapp_counter-net

应用被删除,仅留下了镜像、卷和源码。下面最后一次部署应用,然后查看卷的情况。

$ docker compose up -d
Creating network "counterapp_counter-net" with the default driver
Creating counterapp_redis_1 ... done
Creating counterapp_web-fe_1 ... done

如果查看 Compose 文件会发现,其中定义了一个名为 counter-vol 的新卷,并将其挂载到 web-fe 服务的 /code 路径上。

services:
web-fe:
<Snip>
volumes:
- type: volume
source: counter-vol
target: /code
<Snip>
volumes:
counter-vol:

当第一次部署该应用的时候,Docker Compose 会检查是否有同名的卷存在。如果不存在,则会创建它。也可使用 docker volume ls 命令手动查看。

$ docker volume ls
RIVER VOLUME NAME
local counterapp_counter-vol

值得注意的是,Docker Compose 会在部署服务之前创建网络和卷。这很合理,因为它们是供服务(容器)使用的底层基础资源。

如下可见,Docker Compose 会首先创建网络和卷(甚至先于构建和拉取镜像)。

$ docker-compose up -d

Creating network "counterapp_counter-net" with the default driver
Creating volume "counterapp_counter-vol" with default driver
Pulling redis (redis:alpine)...
<Snip>

再次研读 Dockerfile 中关于 web-fe 服务的定义,会看到它将卷 counter-app 挂载到容器的 /code 目录。

还会发现,/code 正是应用安装和执行的目录。由此可知,应用的代码是位于 Docker 卷中的,如下图所示。

应用的代码位于Docker卷中

这意味着,我们在 Docker 主机对卷中文件的修改,会立刻反应到应用中。下面验证一下。

具体的验证过程包含这样几个步骤。

首先在项目目录下编辑 app.py 文件,从而应用在浏览器中的页面会显示不同的文本。

然后将更新的文件复制到位于 Docker 主机的卷中。

最后刷新应用的 Web 页面来查看更新的内容。

因为,所有对位于 Docker 主机上的卷中内容的修改都会立刻反映在容器内的卷里。

请使用顺手的文本编辑器修改位于项目目录下的 app.py 文件,这里我们使用的是 vim。

$ vim ~/counter-app/app.py

修改第 22 行位于双引号之间的文字。这一行以 "What's up..." 开始,可在双引号内随意输入文字并保存。

更新源码后,将其复制到 Docker 主机上相应的卷中,也就是复制到一个或多个容器的挂载点(Mount Point)中。

使用 docker volume inspect 命令可以查看卷位于 Docker 主机的什么位置。

$ docker volume inspect counterapp_counter-vol | grep Mount

"Mountpoint": "/var/lib/docker/volumes/counterapp_counter-vol/_data",

复制文件后,该文件就会出现在 web-fe 容器的 /code 中,覆盖掉容器中原有的 /code/app.py 文件。

$ cp ~/counterapp/app.py \
/var/lib/docker/volumes/counterapp_counter-vol/_data/app.py

现在更新的 app.py 文件已经位于容器中了。请在浏览器中通过 Docker 主机的 IP 和端口 5000 连接到应用来查看更新的内容。

更新后的情况如下图所示。

更新后的运行效果

显然在生产环境中不会这样做,但是在开发环境中这确实很节省时间。

所有教程

优秀文章