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:

  1. Define your app’s environment with a Dockerfile so it can be reproduced anywhere.
  2. Define the services that make up your app in docker-compose.yml so they can be run together in an isolated environment.
  3. Run docker compose up and the Docker compose command starts and runs your entire app. You can alternatively run docker-compose up using Compose standalone(docker-compose binary).

两个重要概念

  • 服务(service):一个应用容器,实际上可以包括若干运行相同镜像的容器实例。
  • 项目(project):由一组关联的应用容器组成的完整业务单元,在docker-compse.yml文件中定义。

Compose的默认管理对象是项目,通过子命令对项目中的容器进行便捷地生命周期管理。

Compose使用

根据上述官方介绍,使用Compose分为三步。

  1. 使用Dockerfile文件定义应用环境,也就是构建镜像。
  2. 使用docker-compose.yml配置文件配置多个应用容器。
  3. 使用docker子命令docker compose updocker compose的单独命令docker-compse up运行整个项目。

compose初体验

跟随Docker官网的Get started with Docker Compose | Docker Documentation

体验如何使用compose。创建了一个统计网页访问次数的Flask项目,访问次数存储在redis中。

因此本项目有两个服务:Python flask和redis。

  1. 创建文件夹composetest,作为项目文件夹,并cd进入该文件夹

  2. 创建Python文件app.py,并复制代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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)

    导入了flask和redis包,连接redis服务时,直接使用了redis服务的容器名,并使用默认的redis访问端口6379。

    定义了get_hit_count函数来统计访问次数,每访问一次,该函数就会执行一次,将统计次数加1并再次存入缓存redis中。当redis无法连接时,except后代码会执行。会尝试重复连接五次,如果无法连接,休息0.5秒后再次尝试连接,五次过后还是不行,就会报错。

  3. 创建requirements.txt用来说明需要下载的Python库。本项目需要使用flask和redis

    1
    2
    flask
    redis
  4. 创建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
  5. 创建docker-compose.yml配置文件。

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

    该配置定义了两个服务:webredis

    其中web服务的镜像由当前目录的Dockerfile构建而来,将本地主机的端口8000映射到容器开放的端口5000。

    redis服务使用公开库拉取的镜像。

  6. 运行docker-compose up命令,构建镜像并运行容器。

    执行流程:

    • 使用默认网络模式,创建自定义网络composetest_default

      image-20220818132400961
    • 根据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镜像。

      image-20220818132835327
    • 创建两个服务容器:composetest_web_1composetest_redis_1

    • 进入两个容器,运行容器

      image-20220818133110825

此时访问IP:8080即可看到输出信息。

image-20220818133344892

查看镜像。

image-20220818133935669

可以看到,存在三个新构建和下载的镜像:composetest_webpythonredis

查看正在运行容器。

image-20220818134149491

两个正在运行的容器:composetest_webredis

修改compose配置文件

可以修改docker-compose.yml配置文件,添加数据卷挂载到本地目录。

1
2
3
4
5
6
7
8
9
10
11
12
version: "3.9"
services:
web:
build: .
ports:
- "8000:5000"
volumes:
- .:/code
environment:
FLASK_ENV: development
redis:
image: "redis:alpine"
  • volumes:挂载数据卷,容器内目录/code故障挂载到本地当前工作目录。

    这样在修改app.py时,不需要重新构建镜像,可以直接同步。

  • environment:设置FLASK_ENV环境变量,即设置flask项目运行的环境为开发环境,这样在代码修改后,项目就可以自动地重新加载代码。仅可在开发模式环境下使用。

修改后,重新构建并运行容器。

image-20220818135240816

可以看到,flask项目的Debug modeon,即开启了调试模式。代码修改后会自动重新加载。

使用curl http://localhost:8000测试。

image-20220818135213688

修改app.py的返回值为

return 'Hello from Docker! I have been seen {} times.\n'.format(count)

然后再次测试。

image-20220818135542409

停止compose项目可以使用docker-compose stop或在运行终端使用快捷键Ctrl + C停止容器。

也可使用docker-compose down来停止项目。

注意:docker-compose stopdocker-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中设置的选项(如CMDVOLUMEEXPOSE)等会自动被获取,无需在docker-compose.yml中重复设置。

设置服务的格式为:

1
2
3
4
5
6
7
8
9
10
version "3.8"

services :
服务1名称:
...

服务2名称:
...

...

服务名称也代表该容器的域名,可以使用这个名称访问容器。

在定义的服务名称下,有以下指令。

build

指定Dockerfile所在文件夹的路径(可以是绝对路径,或相对docker-compose.yml的路径),Compose会利用它构建镜像并使用。

例如:

1
2
3
4
version '3'
services:
web:
build: ./dir

也可使用context指令指定Dockerfile所在文件夹的路径,然后使用dockerfile指令指定Dockerfile文件名,使用arg指令指定构建镜像时的变量。

1
2
3
4
5
6
7
8
version '3'
services:
web:
build:
context: ./dir
dockerfile: Dockerfile-alternate
args:
buildno: 1

image

指定为镜像名称或镜像ID,如果本地不存在此镜像,Compose会尝试拉取镜像。

1
2
image: ubuntu
image: a4bc5fd

ports

设置宿主机与容器端口的映射。格式为:HOST:CONTAINER。或仅指定容器的端口(宿主机将会随机选择端口)。

1
2
3
4
ports: 
- "8000:5000"
- "3000"
- "127.0.0.1:8001:8001"

注意:当使用HOST:CONTAINER格式来映射端口时,如果使用的容器端口小于60并且没放到引号中,可能会得到错误结果。因为YAML会自动解析xx:yy这种数字格式60进制。因此为避免这种问题,建议数字串都使用引号。

command

覆盖容器启动后默认执行的命令。

1
command: echo "hello world"

entrypoint

覆盖Dockerfile中的ENTRYPOINT命令。

1
entrypoint: /code/entrypoint.sh

注意:该命令不仅可以覆盖Dockerfile文件中的ENTRYPOINT命令。并且如果Dockerfile中有CMD命令,那么CMD命令也会被忽略。

container_name

指定容器名称。如不指定,默认使用 项目名称_服务名称_序号格式。项目名称一般为工作目录名。

container_name: docker-web-container

注意:指定容器名称后,该服务无法进行拓展,因为Docker不允许多个容器具有相同名称。

depends_on

解决容器的依赖、启动先后问题。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3'

services:
web:
build: .
depends_on:
- db
- redis

redis:
image: redis

db:
image: postgres

先启动redisdb,然后启动web服务。

注意:web服务不会等待redisdb完全启动后才启动。

environment

设置环境变量。可适用数组或字典两种格式。

只给定名称的变量会自动获取运行Compose宿主机上对应变量的值,用来防止泄漏不必要的数据。

1
2
3
4
5
6
7
8
environment: 
RACK_ENV: development
SESSION_SECRET:

# 也可写为
environment:
- RACK_ENV=development
- SESSION_SECRET

注:如果变量名称或值中用到true|falseyes|no等表达布尔的词汇,最好放到引号中,避免YAML自动解析为某些内容对应的布尔语义。

env_file

从文件中获取环境变量,可以为单独的文件路径或文件路径列表。

如果通过docker-compose -f FILE方式来指定Compose模板文件,则env_file中变量的路径会基于模板文件路径。

如果有变量名称和environment指令冲突,那么按照惯例,以后者为准。

1
2
3
4
5
env_file: .env
# 多个文件写成列表
env_file:
- ./common.env
- ./apps/web.env

环境变量文件中每一行必须符合格式,支持#开头的注释行。

1
2
# common.env: Set development environment
PROG_ENV=development

expose

暴露端口,但不映射到宿主机,只可以被连接的服务容器访问。

即:该项目的其他容器可以使用这个端口访问本容器,但宿主机只能使用本容器端口映射的本机端口才能连接,无法使用本端口。

1
2
3
expose: 
- "3000"
- "8000"

volumes

设置容器挂载路径。可以设置为宿主机路径(HOST:CONTAINER)或数据卷名称(VOLUME:CONTAINER),并且可以设置访问模式(HOST:CONTAINER:ro/rw)。

该指令路径支持相对路径。

1
2
3
4
5
6
7
8
9
version: '3'

services:
web:
build: .
volumes:
- /var/lib/mysql
- cache/:/tmp/cache
- ~/configs:/etc/configs/:ro

如果路径为数据卷名称,那么需要在文件中使用顶级指令(不是services指令下的子指令,顶行写)volumes配置数据卷。

1
2
3
4
5
6
7
8
9
10
version: '3'

services:
web:
build: .
volumes:
- mysql_data:/var/lib/mysql

volumes:
mysql_data:

networks

配置容器连接的网络。

设置的网络名应该是顶级指令networks中配置的一个或多个网络。

即需要先使用顶级指令(即不是services指令下的子指令,顶行写)networks配置自定义网络,然后才可在服务中设置配置好的网络。

1
2
3
4
5
6
7
8
9
10
11
12
version: '3'

services:
web:
build: .
networks:
- some_network
- other_network

networks:
some_network:
other_network:

在该指令下,还可以有子指令aliases,来指定该服务在本网络下的别名。本网络下的其他服务可以使用服务名或别名来连接本服务。

一个服务在不同的网络下可以有不同的别名。

1
2
3
4
5
6
7
8
9
10
services:
some-service:
networks:
some-network:
aliases:
- alias1
- alias3
other-network:
aliases:
- alias2

上述代码中,some-service配置了两个网络some-networkother-network

some-network中的别名为alias1alias3,那么同样在网络some-network下的其他服务可以使用some-servicealias1alias3连接本服务。

other-network中又配置了alias2别名。

labels

为容器添加Docker元数据(metadata)信息。例如为容器添加辅助说明信息。

1
2
3
4
labels:
com.startupteam.description: "webapp for a startup team"
com.startupteam.department: "devops department"
com.startupteam.release: "rc3 for v1.0"

networks

顶级指令,不是services指令的子指令。

可以指定要创建的自定义网络。

有以下几个选项:

  • driver:默认为bridge,可选为hostnone

  • internal:默认情况,Docker创建桥接网络后,可以连接外部网络。如果想要创建一个与外部隔离的网络,可以将该选项设为true

  • external:若将该选项设为true,那么会指定一个外部网络,即不在Compose内部。Compose不会创建,因此该网络应该已经创建好了,若不存在,那么会出错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    version: "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
    5
    version: "3.9"
    networks:
    outside:
    external:
    name: actual-name-of-network
  • name:为网络设置自定义名称。该名称可以包含特殊字符。

    在下面例子中,网络的实际名称为my-app-net

    1
    2
    3
    4
    version: "3.9"
    networks:
    network1:
    name: my-app-net

    还可以与external字段一起使用。

    1
    2
    3
    4
    5
    version: "3.9"
    networks:
    network1:
    external: true
    name: my-app-net

4-DockerCompose
https://zhaoquaner.github.io/2022/08/18/Docker/4-DockerCompose/
更新于
2022年8月18日
许可协议