4-DockerCompose
4-DockerCompose
简介
Compose
是Docker官方的开源项目,负责实现对Docker容器集群的快速编排。
Compose
的定位是定义和运行多个Docker容器。
通过Dockerfile
的介绍,可以知道,使用Dockerfile
可以方便的定义一个单独使用的容器镜像,然后手动运行该容器。
但是,在实际使用中,经常需要多个容器相互配合来完成某项任务。例如实现Web项目, 除了Web服务容器外,还需要后端的数据库服务容器,还可能包括缓存数据库、负载均衡容器等。
Compose
满足了这样的需求。它允许用户通过一个配置文件docker-compse.yml
来定义一组相关联的应用容器为一个项目(project)。配置文件使用YAML
格式。
官方介绍
Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration. To learn more about all the features of Compose, see the list of features.
Compose works in all environments: production, staging, development, testing, as well as CI workflows. You can learn more about each case in Common Use Cases.
Using Compose is basically a three-step process:
- Define your app’s environment with a
Dockerfile
so it can be reproduced anywhere. - Define the services that make up your app in
docker-compose.yml
so they can be run together in an isolated environment. - Run
docker compose up
and the Docker compose command starts and runs your entire app. You can alternatively rundocker-compose up
using Compose standalone(docker-compose
binary).
两个重要概念
- 服务(service):一个应用容器,实际上可以包括若干运行相同镜像的容器实例。
- 项目(project):由一组关联的应用容器组成的完整业务单元,在
docker-compse.yml
文件中定义。
Compose
的默认管理对象是项目,通过子命令对项目中的容器进行便捷地生命周期管理。
Compose
使用
根据上述官方介绍,使用Compose
分为三步。
- 使用
Dockerfile
文件定义应用环境,也就是构建镜像。 - 使用
docker-compose.yml
配置文件配置多个应用容器。 - 使用docker子命令
docker compose up
或docker compose
的单独命令docker-compse up
运行整个项目。
compose
初体验
跟随Docker官网的Get started with Docker Compose | Docker Documentation
体验如何使用compose
。创建了一个统计网页访问次数的Flask项目,访问次数存储在redis中。
因此本项目有两个服务:Python flask和redis。
创建文件夹
composetest
,作为项目文件夹,并cd
进入该文件夹创建Python文件
app.py
,并复制代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import 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)导入了flask和redis包,连接redis服务时,直接使用了redis服务的容器名,并使用默认的redis访问端口6379。
定义了
get_hit_count
函数来统计访问次数,每访问一次,该函数就会执行一次,将统计次数加1并再次存入缓存redis中。当redis无法连接时,except
后代码会执行。会尝试重复连接五次,如果无法连接,休息0.5秒后再次尝试连接,五次过后还是不行,就会报错。创建
requirements.txt
用来说明需要下载的Python库。本项目需要使用flask和redis1
2flask
redis创建
Dockerfile
文件,来构建flask
镜像。1
2
3
4
5
6
7
8
9
10
11
12# syntax=docker/dockerfile:1 # 使用版本1语法的最新版本,
# 在构建之前检查语法更新,必须位于文件顶部
FROM python:3.7-alpine # 以python3.7为基础镜像
WORKDIR /code # 工作目录为/code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0 # 设置环境变量
RUN apk add --no-cache gcc musl-dev linux-headers # 安装gcc等依赖库,便 # 便于python包编译加速
COPY requirements.txt requirements.txt # 拷贝requirements.txt
RUN pip install -r requirements.txt # 安装需要的Python库
EXPOSE 5000 # 设置容器端口为5000
COPY . . #将当前目录拷贝到容器内目录
CMD ["flask", "run"] #设置容器启动默认命令 flask run创建
docker-compose.yml
配置文件。1
2
3
4
5
6
7
8version: "3.9"
services:
web:
build: .
ports:
- "8000:5000"
redis:
image: "redis:alpine"该配置定义了两个服务:
web
和redis
。其中
web
服务的镜像由当前目录的Dockerfile
构建而来,将本地主机的端口8000映射到容器开放的端口5000。redis
服务使用公开库拉取的镜像。运行
docker-compose up
命令,构建镜像并运行容器。执行流程:
使用默认网络模式,创建自定义网络
composetest_default
根据Dockerfile文件构建Python的flask镜像。
根据Dockerfile文件一行一行执行。
其中在执行
RUN apk add --no-cache gcc musl-dev linux-headers
时,默认镜像地址下载非常慢,所以可以在该行前面配置阿里云镜像:RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
构建镜像完成后,从公共库拉取redis镜像。
创建两个服务容器:
composetest_web_1
和composetest_redis_1
进入两个容器,运行容器
此时访问IP:8080
即可看到输出信息。
查看镜像。
可以看到,存在三个新构建和下载的镜像:composetest_web
、python
和redis
。
查看正在运行容器。
两个正在运行的容器:composetest_web
和redis
。
修改compose
配置文件
可以修改docker-compose.yml
配置文件,添加数据卷挂载到本地目录。
1 |
|
volumes
:挂载数据卷,容器内目录/code
故障挂载到本地当前工作目录。这样在修改
app.py
时,不需要重新构建镜像,可以直接同步。environment
:设置FLASK_ENV
环境变量,即设置flask
项目运行的环境为开发环境,这样在代码修改后,项目就可以自动地重新加载代码。仅可在开发模式环境下使用。
修改后,重新构建并运行容器。
可以看到,flask
项目的Debug mode
为on
,即开启了调试模式。代码修改后会自动重新加载。
使用curl http://localhost:8000
测试。
修改app.py
的返回值为
return 'Hello from Docker! I have been seen {} times.\n'.format(count)
然后再次测试。
停止compose
项目可以使用docker-compose stop
或在运行终端使用快捷键Ctrl + C
停止容器。
也可使用docker-compose down
来停止项目。
注意:docker-compose stop
和docker-compose down
有以下区别。
stop
命令只会停止项目中所有容器运行,相当于使用多个docker stop
。down
命令不仅会停止项目中所有容器运行,而且会删除所有容器并删除自定义网络。
Compose模板文件
模板文件也就是配置文件docker-compose.yml
。使用的是YAML
格式。
关于YAML
格式,可以参考 YAML 入门教程 | 菜鸟教程 (runoob.com)
模板文件是使用Compose
的核心,涉及到较多的指令关键词,不过大部分指令和docker run
的相关参数含义类似。
docker-compose.yml
配置文件大致分为三个部分:
- 版本号(version):
docker-compose.yml
第一行为Compose
版本 ,格式为version "版本号"
。 - 服务(services):配置服务容器。
- 其他配置:例如网络
networks
,挂载卷volumes
等。
下面首先介绍services
指令下的子指令。
services
配置服务容器
注意:Compose
项目中的每个服务都必须通过image
指令指定镜像或build
指令(需要Dockerfile)来自动构建镜像。
如果使用build
指令,那么在Dockerfile
中设置的选项(如CMD
、VOLUME
、EXPOSE
)等会自动被获取,无需在docker-compose.yml
中重复设置。
设置服务的格式为:
1 |
|
服务名称也代表该容器的域名,可以使用这个名称访问容器。
在定义的服务名称下,有以下指令。
build
指定Dockerfile
所在文件夹的路径(可以是绝对路径,或相对docker-compose.yml
的路径),Compose
会利用它构建镜像并使用。
例如:
1 |
|
也可使用context
指令指定Dockerfile
所在文件夹的路径,然后使用dockerfile
指令指定Dockerfile
文件名,使用arg
指令指定构建镜像时的变量。
1 |
|
image
指定为镜像名称或镜像ID,如果本地不存在此镜像,Compose
会尝试拉取镜像。
1 |
|
ports
设置宿主机与容器端口的映射。格式为:HOST:CONTAINER
。或仅指定容器的端口(宿主机将会随机选择端口)。
1 |
|
注意:当使用HOST:CONTAINER
格式来映射端口时,如果使用的容器端口小于60并且没放到引号中,可能会得到错误结果。因为YAML
会自动解析xx:yy
这种数字格式60进制。因此为避免这种问题,建议数字串都使用引号。
command
覆盖容器启动后默认执行的命令。
1 |
|
entrypoint
覆盖Dockerfile中的ENTRYPOINT
命令。
1 |
|
注意:该命令不仅可以覆盖Dockerfile文件中的ENTRYPOINT
命令。并且如果Dockerfile中有CMD
命令,那么CMD
命令也会被忽略。
container_name
指定容器名称。如不指定,默认使用 项目名称_服务名称_序号
格式。项目名称一般为工作目录名。
container_name: docker-web-container
注意:指定容器名称后,该服务无法进行拓展,因为Docker不允许多个容器具有相同名称。
depends_on
解决容器的依赖、启动先后问题。例如:
1 |
|
先启动redis
、db
,然后启动web
服务。
注意:web
服务不会等待redis
、db
完全启动后才启动。
environment
设置环境变量。可适用数组或字典两种格式。
只给定名称的变量会自动获取运行Compose
宿主机上对应变量的值,用来防止泄漏不必要的数据。
1 |
|
注:如果变量名称或值中用到true|false
或yes|no
等表达布尔的词汇,最好放到引号中,避免YAML
自动解析为某些内容对应的布尔语义。
env_file
从文件中获取环境变量,可以为单独的文件路径或文件路径列表。
如果通过docker-compose -f FILE
方式来指定Compose
模板文件,则env_file
中变量的路径会基于模板文件路径。
如果有变量名称和environment
指令冲突,那么按照惯例,以后者为准。
1 |
|
环境变量文件中每一行必须符合格式,支持#
开头的注释行。
1 |
|
expose
暴露端口,但不映射到宿主机,只可以被连接的服务容器访问。
即:该项目的其他容器可以使用这个端口访问本容器,但宿主机只能使用本容器端口映射的本机端口才能连接,无法使用本端口。
1 |
|
volumes
设置容器挂载路径。可以设置为宿主机路径(HOST:CONTAINER
)或数据卷名称(VOLUME:CONTAINER
),并且可以设置访问模式(HOST:CONTAINER:ro/rw
)。
该指令路径支持相对路径。
1 |
|
如果路径为数据卷名称,那么需要在文件中使用顶级指令(不是services
指令下的子指令,顶行写)volumes
配置数据卷。
1 |
|
networks
配置容器连接的网络。
设置的网络名应该是顶级指令networks
中配置的一个或多个网络。
即需要先使用顶级指令(即不是services
指令下的子指令,顶行写)networks
配置自定义网络,然后才可在服务中设置配置好的网络。
1 |
|
在该指令下,还可以有子指令aliases
,来指定该服务在本网络下的别名。本网络下的其他服务可以使用服务名或别名来连接本服务。
一个服务在不同的网络下可以有不同的别名。
1 |
|
上述代码中,some-service
配置了两个网络some-network
和other-network
。
在some-network
中的别名为alias1
和alias3
,那么同样在网络some-network
下的其他服务可以使用some-service
或alias1
或alias3
连接本服务。
在other-network
中又配置了alias2
别名。
labels
为容器添加Docker元数据(metadata
)信息。例如为容器添加辅助说明信息。
1 |
|
networks
顶级指令,不是services
指令的子指令。
可以指定要创建的自定义网络。
有以下几个选项:
driver
:默认为bridge
,可选为host
或none
。internal
:默认情况,Docker创建桥接网络后,可以连接外部网络。如果想要创建一个与外部隔离的网络,可以将该选项设为true
。external
:若将该选项设为true
,那么会指定一个外部网络,即不在Compose
内部。Compose
不会创建,因此该网络应该已经创建好了,若不存在,那么会出错。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16version: "3.9"
services:
proxy:
build: ./proxy
networks:
- outside
- default
app:
build: ./app
networks:
- default
networks:
outside:
external: true在上面例子中,
Compose
会寻找一个已存在的名为outside
的外部网络并将proxy
应用容器连接到该网络。也可以使用
name
选项来指定外部网络的名称。在下面例子中,
outside
是配置文件内部使用的名称,使用name
选项指定的名称是外部网络的实际名称。1
2
3
4
5version: "3.9"
networks:
outside:
external:
name: actual-name-of-networkname
:为网络设置自定义名称。该名称可以包含特殊字符。在下面例子中,网络的实际名称为
my-app-net
。1
2
3
4version: "3.9"
networks:
network1:
name: my-app-net还可以与
external
字段一起使用。1
2
3
4
5version: "3.9"
networks:
network1:
external: true
name: my-app-net