使用 Docker Compose 编排容器

Docker Compose

Docker Compose,原名为 Fig,是 Docker 官方开发的用于简化多容器部署和迁移过程的容器编排(Orchestration)插件。Compose 使用一个 YAML 格式的 docker-compose.yml 模板文件来定义一组相关联的应用容器,形成一个服务栈。

在 Compose 中有几个重要的概念:

  • 任务(task):一个容器被称为一个任务,拥有独一无二的 ID,在同一个服务中的多个任务序号依次递增。
  • 服务(service):某个相同应用镜像的容器副本集合,一个服务可以横向扩展为多个容器实例。
  • 服务栈(stack):由多个服务组成,互相配合完成特定任务,一般定义在一个 docker-compose.yml 文件中。

安装

V1 版本由 Python 编写,安装方式如下:

[root@server1 ~]$ curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64 -o /usr/local/bin/docker-compose
[root@server1 ~]$ curl -L https://raw.githubusercontent.com/docker/compose/1.29.2/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
[root@server1 ~]$ chmod +x /usr/local/bin/docker-compose

V2 版本使用 Golang 重构,安装方式如下:

[root@server4 ~]$ curl -L https://github.com/docker/compose/releases/download/v2.0.1/docker-compose-linux-x86_64 -o /usr/libexec/docker/cli-plugins/docker-compose
[root@server4 ~]$ chmod +x /usr/libexec/docker/cli-plugins/docker-compose
[root@server4 ~]$ docker compose version
Docker Compose version v2.0.1

不需要使用时,可以直接删除对应的二进制文件即可。

配置文件

一个简单的配置文件样本如下:

version: "3"
services:
  redis:
    image: redis:alpine
    ports:
      - "6379"
    networks:
      - frontend
  db:
    image: postgres:9.4
    environment:
      ROOT_PASSWORD: example
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - backend
  vote:
    image: dockersamples/examplevotingapp_vote:before
    ports:
      - "5000:80"
    networks:
      - frontend
    depends_on:
      - redis
  result:
    image: dockersamples/examplevotingapp_result:before
    ports:
      - "5001:80"
    networks:
      - backend
    depends_on:
      - db
  worker:
    image: dockersamples/examplevotingapp_worker
    networks:
      - frontend
      - backend
  visualizer:
    image: dockersamples/visualizer:stable
    ports:
      - "8080:8080"
    stop_grace_period: 1m30s
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      
networks:
  frontend:
  backend:

volumes:
  db-data:

从上面的示例模板文件中可以看出顶级的配置项有:

  • version:定义了版本信息。
  • services:定义了服务的配置信息,包含应用于为该服务启动的每个容器的配置。
  • networks:定义了网络信息,提供给 services 中的具体容器使用。
  • volumes:定义了卷信息,提供给 services 中的具体容器使用。

配置详解

一些常见配置命令说明如下:

alias

网络上此服务的别名(备用主机名)。同一网络上的其他容器可以使用服务名称或此别名连接到其中一个服务的容器。

由于 aliases 是网络范围的,因此相同的服务可以在不同的网络上具有不同的别名。

网络范围的别名可以由多个容器共享,甚至可以由多个服务共享。如果是这样的话,则无法保证名称解析为容器。

例如设置网络别名:

services:
  some-service:
    networks:
      some-network:
        aliases:
          - alias1
          - alias3
      other-network:
        aliases:
          - alias2

bulid

指定 Dockerfile 所在文件夹的路径,可以是绝对或相对路径,Compose 将会利用它自动构建并使用这个镜像。例如:

version: '3'
services:
  webapp:
    build: ./dir

也可以使用 context 指令指定 Dockerfile 所在文件夹的路径,同时使用 dockerfile 指令指定 Dockerfile 文件名:

version: '3'
services:
  webapp:
    build:
      context: /root/build
      dockerfile: Dockerfile-alternate

dockerfile 指令不能跟 image 同时使用,否则 Compose 不知道根据哪个指令来生成最终的服务镜像。

command

使用 command 可以覆盖容器启动后默认执行的命令,可以为字符串格式:

command: bundle exec thin -p 3000

也可以为 JSON 数组格式:

command: ["bundle", "exec", "thin", "-p", "3000"]

container_name

指定容器名称。不指定默认将会使用 项目名称_服务名称_序号 这样的格式:

container_name: docker-web-container

depends_on

解决容器的依赖和启动先后的问题。例如先启动 redis 和 db 再启动 web:

version: '3'
services:
  web:
    build: .
    depends_on:
      - db
      - redis
redis:
    image: redis
db:
    image: postgres

web 服务不会等待 redis 和 db 完全启动之后才启动。

dns

自定义 DNS 服务器,可以是单个也可以是一个列表:

dns: 8.8.8.8
dns:
  - 8.8.8.8
  - 9.9.9.9

entrypoint

覆盖容器中默认的入口命令:

entrypoint: python app.py

env file

从文件获取环境变量,可以为文件路径或列表。如果通过 docker-compose -f FILE 方式来指定 Compose 模板文件,则 env_file 中变量的路径会基于模板文件路径。如果有变量名称与 environment 指令冲突,以后者为准:

env file: .env

env file:
  - ./cornmon.enV
  - ./apps/web.env
  - /opt/secrets.env

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

# cornmon.env : Set development environment
PROG ENV=development

environment

设置环境变量。可以使用数组或字典两种格式。只给定名称的变量会自动获取运行 Compose 主机上对应变量的值,可以用来防止泄露不必要的数据:

environment:
  RACK_ENV: development
  SESSION_SECRET:
environment:
  - RACK_ENV=development
  - SESSION_SECRET

如果变量名称或者值中用到 true|falseyes|no 等表达布尔含义的词汇,最好放到引号里,避免 YAML 自动解析某些内容为对应的布尔语义。包括:y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF

expose

暴露端口,但不映射到宿主机,只被连接的服务访问。仅可以指定容器内部的端口为参数:

expose:
 - "3000"
 - "8000"

extends

基于其他模板文件进行扩展。一般情况下,推荐在基础模板中只定义一些可以共享的镜像和环境变量,在扩展模板中具体指定应用变量、链接、数据卷等信息。

例如已有一个 webapp 服务,定义一个基础模板文件为 common.yml:

# common.yml
webapp:
  build: ./webapp
  environment:
    - DEBUG=false
    - SEND EMAILS=false 

再编写一个新的 development.yml 文件,使用 common.yml 中的 webapp 服务进行扩展:

# development.yml
web:
  extends:
    file: common.yml
    service: webapp
  ports:
    - "8000:8000"
  links:
    - db
  environment:
    - DEBUG=true
db:
  image: postgres

后者会自动继承 common.yml 中的 webapp 服务及环境变量定义。使用 extends 需要注意以下两点:

  • 要避免出现循环依赖。例如 A 依赖 B,B 依赖 C,C 反过来依赖 A 的情况。
  • extends 不会继承 links 和 volumes_from 中定义的容器和数据卷资源。

链接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的外部容器:

external links:
  - redis_1
  - project_db_1:mysql
  - project_db_1:postgresql

extra hosts

类似 Docker 中的 --add-host 参数,指定额外的 host 名称映射信息:

extra_hosts:
  - "googledns:8.8.8.8"
  - "dockerhub:52.1.157.61"

服务容器启动后会在 /etc/hosts 文件中添加 googledns 和 dockerhub 两条内容。

healthcheck

通过命令检查容器是否健康运行。包括检测方法(test)、间隔(interval)、超时(timeout)、重试次数(retries)、启动等待时间(start_period)等:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost"]
  interval: 1m30s
  timeout: 10s
  retries: 3
  start_period: 30s

image

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

image: ubuntu
image: orchardup/postgresql
image: a4bc65fd

如果同时指定了 image 和 build,那 image 不再具有单独使用的意义,而是指定了当前要构建的镜像的名称。也就是说,Compose 构建的镜像将使用 image 中指定的名称 webapp:tag 进行命名:

build: ./dir
image: webapp:tag

labels

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

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

logging

与日志相关的配置,包括一系列子配置。logging.driver 类似于 --log-driver 参数,用于指定日志驱动类型。目前支持三种日志驱动类型:json-file、syslog、none。logging.options 设置日志驱动的相关参数。例如,设置为 syslog 格式:

logging:
  driver: "syslog"
  options:
    syslog address: "tcp://192.168.0.42:123"

或者使用 json-file 格式:

logging:
  driver: "json-file"
  options:
    max-size:"lOOOk"
    max-file:"20"

network mode

设置网络模式:

network mode: "none"
network mode: "bridge"
network mode: "host"
network mode: "service: [service name]"
network_mode: "container:[name or id]"

networks

要加入的网络,使用顶级 networks 下的条目:

services:
  some-service:
    networks:
     - some-network
     - other-network
networks:
  some-network:
  other-network:

pid

与主机系统共享进程命名空间。打开该选项的容器之间,以及和宿主机系统之间,可以通过进程 ID 来相互访问和操作:

pid: "host"

ports

暴露端口信息。使用 宿主端口:容器端口(HOST:CONTAINER)格式,或者仅指定容器的端口(宿主将随机选择端口)均可:

ports:
  - "3000"
  - "8000:8000"
  - "49100:22"
  - "127.0.0.1:8001:8001"

或者可以指定详细设置:

ports:
  - target: 80
    published: 8080
    protocol: tcp
    mode: ingress

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

restart

指定重启策略,可以为 no(不重启),always(总是),on-failure(失败时),unless-stopped(除非停止)。例如:

restart: unless-stopped

sysctls

配置容器内的内核参数。例如指定连接数为 4096 和开启 TCP 的 syncookies:

sysctls:
  net.core.somaxconn: 4096
  net.ipv4.tcp_syncookies: 1

ulimits

指定容器的 ulimits 限制值。例如指定最大进程数为 65535,指定文件旬柄数为 20000(软限制)和 40000(系统硬限制):

ulimits:
  nproc: 65535
  nofile:
    soft: 20000
    hard: 40000

volumes

数据卷所挂载路径设置。可以设置宿主机路径(HOST:CONTAINER)或加上访问模式(HOST:CONTAINER:ro)。 支持如 verdriver_optsexternallabelsname 等子配置,该指令中路径支持相对路径:

volumes:
  - /var/lib/mysql
  - cache/:/tmp/cache
  - ~/configs:/etc/configs/:ro

卷的 bindvolume 的混合使用示例:

version: "3.2"
services:
  web:
    image: nginx:alpine
    volumes:
      # 卷 (volume)
      - type: volume
        source: mydata
        target: /data
        volume:
          nocopy: true
      # 挂载 (bind)
      - type: bind
        source: ./static
        target: /opt/app/static
db:
    image: postgres:latest
    volumes:
      - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"
      - "dbdata:/var/lib/postgresql/data"
volumes:
  mydata:
  dbdata:

操作命令

使用 docker-composedocker compose 命令执行。常见选项如下:

选项 说明
-f, --file FILE 指定使用的 Compose 模板文件,默认为 docker-compose.yml,可多次指定。
-p, --project-name NAME 指定项目名称,默认将使用所在目录名称作为项目名。
-H, --host HOST 指定所操作的 Docker 服务地址。
–tls 启用 TLS,如果指定 -tlsverify 则默认开启。
–tlscacert CA_PATH 信任的 TLS CA 的证书。
–tlscert CLIENT_CERT_PATH 客户端使用的 TLS 证书。
–tlskey TLS_KEY_PATH TLS 的私钥文件路径。
–tlsverify 使用 TLS 校验连接对方。
–project-directory PATH 指定工作目录,默认为 Compose 文件所在路径。

运行命令

使用 docker-compose up 命令,可以让 Compose 完成 Docker Compose 项目的镜像构建、容器创建、容器启动和连接容器等操作。下面使用一个最简化的 WordPress 项目示例:

[root@server4 docker]$ vi docker-compose.yml
version: "3"
services:
  wordpress:
    image: wordpress
    depends_on:
      - db
    ports:
      - 8080:80
    links:
      - db
  db:
    image: mariadb
    environment:
      MYSQL_ROOT_PASSWORD: example

默认情况下,docker-compose up 启动的容器都在前台,终端会同时打印所有容器的输出信息,非常方便调试。可以使用快捷键 Ctrl+c 停止前台运行的容器:

[root@server4 docker]$ docker compose up
docker-db-1         | 2021-10-31 22:28:26 0 [Warning] 'proxies_priv' entry '@% root@2b59c74426dd' ignored in --skip-name-resolve mode.
docker-db-1         | 2021-10-31 22:28:26 0 [Note] mysqld: ready for connections.
docker-db-1         | Version: '10.6.4-MariaDB-1:10.6.4+maria~focal'  socket: '/run/mysqld/mysqld.sock'  port: 3306  mariadb.org binary distribution
^CGracefully stopping... (press Ctrl+C again to force)
[+] Running 2/2
 ⠿ Container docker-wordpress-1  Stopped                                          1.6s
 ⠿ Container docker-db-1         Stopped                                          0.4s
canceled

要在后台启动并运行所有容器,可以使用 docker-compose up -d 命令:

[root@server4 docker]$ docker compose up -d
[+] Running 2/2
 ⠿ Container docker-db-1         Started                                     0.6s
 ⠿ Container docker-wordpress-1  Started                                     1.6s

如果服务容器已存在,会尝试停止容器后重新创建,也可以使用 --no-recreate 命令控制只启动停止状态的容器:

[root@server4 docker]$ docker compose -f docker-compose.yml up --no-recreate -d
[+] Running 3/3
 ⠿ Network docker_default        Created                                       0.1s
 ⠿ Container docker-db-1         Started                                       0.7s
 ⠿ Container docker-wordpress-1  Started                                       1.7s

停止命令

通过 docker-compose down 命令,可以让所有通过 up 启动的容器停止,并删除所创建的镜像及网络:

[root@server4 docker]$ docker compose -f docker-compose.yml down
[+] Running 3/3
 ⠿ Container docker-wordpress-1  Removed                                   1.2s
 ⠿ Container docker-db-1         Removed                                   0.4s
 ⠿ Network docker_default        Removed                                   0.1s

示例样本

下面创建一个 Web 服务,包含 Python 程序和 Redis 服务两个容器。

首先准备 web.py 文件,连接 Redis 服务,并显示访问量:

[root@server6 ~]$ mkdir -p app/web
[root@server6 ~]$ vi app/web/web.py
from flask import Flask
from redis import Redis

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

@app.route('/')
def hello():
    redis.incr('number')
    return 'Hello Vi! # %s' % redis.get('number')

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=80, debug=True)
    

编辑依赖第三方库包的名称和版本信息说明文件 requirements.txt

[root@server6 ~]$ vi app/web/requirements.txt
flask==0.10
redis==2.10.3

建立 Dockerfile 文件用来构建 Web 应用镜像:

[root@server6 ~]$ vi app/web/Dockerfile
FROM python:2.7
COPY ./ /web/
WORKDIR /web
RUN pip install -r requirements.txt
CMD python web.py

最后配置 docker-compose.yml 文件,将两个服务写进去:

[root@server6 ~]$ vi app/docker-compose.yml
version: '3.0'
services:
  redis:
    image: redis:3.2
  web:
    build:
      context: /root/app/web
    depends_on:
    - redis
    ports:
    - 8001:80/tcp
    volumes:
    - /root/app/web:/web:rw

使用 docker-compose up 命令来启动应用:

[root@server6 ~]$ docker compose -f app/docker-compose.yml up

启动之后可以通过 http://192.168.2.206:8001/ 来访问。