Docker(二)Docker Composer使用(应用服务组装、部署构建、网络连通设置等)

Docker三剑客:DockerEngine、DockerCompose、DockerMachine

Compose的英文含义为组成、创作、构成,在隔离环境中运行应用程序并与之交互,这个就是Compose的主要目标

1. Docker Compose概述

1.1. Docker Compose 介绍

Docker Compose是一个用于定义和运行多容器Docker应用程序的工具,在隔离环境中运行应用程序并与之交互,这个就是Compose的主要目标

使用Compose,可以使用YAML文件(docker-compose.yml)来配置应用程序的服务,然后,使用单个命令docker-compose up,可以从配置中创建并启动所有(依赖)服务,比如数据库,队列,高速缓存,Web服务的API,等等。

使用Compose命令行工具,您可以使用单个命令(docker-compose up)为每个依赖项创建和启动一个或多个容器,Compose可以将多页“开发人员入门指南”,简化为单个机器可读的Compose配置文件和一些命令。

  • 使用场景:
    • 多应用开发环境
    • 自动化测试环境
    • 单机部署

1.1.1. Compose特性

  1. 单主机上的多环境隔离:基于使用项目名称将环境彼此隔离(单个环境的多个副本、避免CI环境相互干扰、共享主机上面防止同名服务)
  2. 应用存储方面,创建容器保留卷数据:容器未删除的话(仅停止),主机卷不会被删除;若容器被删除,在docker volume prune时候,会被移除!
  3. 仅重建有变更的容器:缓存用于创建容器的配置,加速容器启动
  4. 变量在多环境之间组合:支持环境变量设置和传递变更到容器(extends字段

1.1.2. Compose功能 - 管理应用程序整个生命周期的命令

  1. 服务启动,停止和创建/重建/重新编排
  2. 服务运行状态查看
  3. 服务的日志流式输出
  4. 服务上运行一次性命令

1.1.3. Compose安装

参考:https://docs.docker.com/compose/install/

Mac下的Docker Desktop for Mac已经附带安装了Docker Compose,另外Docker Compose支持远程Docker主机操作

1.2. Compose构建应用流程概述

  1. 基于Dockerfile定义应用运行环境
  2. 基于docker-compose.yml构建应用程序的服务,以便它们可以在隔离的环境中一起运行。
  3. 运行docker-compose up或者compose start运行整个应用
// 自动化测试环境
$ docker-compose up -d
$ ./run_tests
$ docker-compose down

1.2.1. 准备应用

可以是Go、PHP、Python、JAVA、NodeJS应用

1.2.2. 准备应用环境(Dockerfile)

  • FROM:镜像
  • WORKDIR:工作目录
  • ENV:指定构建过程中使用到的环境变量,注意与docker run的ARG的区别
  • RUN:执行指定命令,
  • COPY:复制内容,注意与ADD支持解压区别(COPY语义性更好)
  • CMD:容器默认命令,可以与ENTRYPOINT配合使用(CMD转为参数了)

参考:https://docs.docker.com/engine/reference/builder/

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"]

1.2.3. 准备服务组装(docker-compose.yml)

此Compose文件定义了两个服务:web和redis。

  • version:配置版本
  • services:服务组成
    • web:服务名称
    • redis
  • 卷定义
  • 单个服务支持:
    • 镜像如何build
    • 端口映射
    • 数据绑定
    • 环境变量配置
    • 服务依赖
// docker-compose.yml 示例
version: '3'
services:
  web:
    build: .
    ports:
        - "5000:5000"
    volumes:
        - .:/code
        - logvolume01:/var/log
    environment:
      FLASK_ENV: development
    links:
        - redis
  redis:
    image: redis
volumes:
  logvolume01: {}

1.2.4. 创建并启动应用(docker-compose up)

首次运行会基于拉取Docker镜像或者基于本地Dockerfile构建容器,来创建应用服务:

  • 创建基础网络
  • 拉取两个基础服务的镜像(python、redis)以及创建一个应用镜像(一共3个)
  • 运行应用服务(启动python以及redis容器)
  • 创建数据卷,与web日志中的/var/log绑定(其他方式有文件夹共享、卷、绑定卷)
  • 环境变量设置,将FLASK设定在开发模式下

1.3. Compose命令概述

容器扩容,可以在配置中deploy下的replicas下指定,基于Swarm Mode

不要使用docker-compose scale web=2 worker=3或者docker-compose up --scale web=2 worker=3

1.3.1. 构建、移除、重启、停掉等

// 创建并启动应用
$ docker-compose up
// 构建启动,Detached mode
$ docker-compose up -d
// 移除
$ docker-compose rm
// 开始、暂停、重启、停止
$ docker-compose start、pause、restart、down
// 停止容器并删除卷
$ docker-compose down --volumes

1.3.2. 容器命令执行

// 服务运行一次性命令
$ docker-compose run web env
// 获取交互式命令,类似于docker exec
$ docker-compose exec web sh

1.3.3. 镜像推、拉

// 镜像推、拉
$ docker-compose pull/push
// 捆绑推送
$ docker-compose bundle

1.3.4. 信息查看

// 进程查看相关
$ docker-compose ps
// 端口
$ docker-compose port
// 日志
$ docker-compose logs
// top
$ docker-compose top

2. docker-compose.yml配置使用

参见:https://docs.docker.com/compose/compose-file/

Tips,使用版本3的Compose堆栈文件来实现多容器应用程序,服务定义和swarm模式,文件扩展名.yml.yaml扩展名都支持。

docker-compose.yml文件是定义了应用软件的相关服务组成,以及每个组成的服务的各自的容器构建、端口映射、网络、存储卷、部署、服务依赖的YAML文件,等同于:

  • docker container create
  • docker network create
  • docker volume create

由于一些CMD, EXPOSE,VOLUME,ENV指令都已经在Dockerfile中已指定,故无需再在docker-compose.yml再次指定

2.1. 镜像构建上下文、参数设定 - (build)

  1. 最直接的,直接使用某个镜像构建服务:
// 指定镜像
image: webapp:tag
  1. 基于目录下的Dockerfile以及相关上下文以及参数构建服务,相关布尔值可以使用true、false、yes、no、off、on来设定(需加括号):
    • 支持上下文
    • 支持参数设定
    • 支持lables标签设定
// dir目录
build: ./dir
// 上下文,相对路径的话,相对于Compose文件的位置(也是构建Docker上下文的路径)
build:
  context: ./dir
  args:
    - buildno=1     // 注意在FROM指令前后的差异
    - gitcommithash // 基于环境值设定
  lables:
    - "com.example.description=Accounting webapp"
    - "com.example.department=Finance"
    - "com.example.label-with-empty-value"
  1. 资源设定:
    • 支持/dev/shm(共享内存)
build:
  context: .
  shm_size: "2gb"
  1. 多阶段构建,指定target阶段名称
build:
  context: .
  target: prod

2.2. 按服务依赖顺序启停 - (depends_on)

// 先移动db和redis,在启动web
docker-compose up
// 先停止web,再停止db和redis
docker-compose stop

// compose-file.yml
version: "3.7"
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

更多服务启停依赖,参考:https://docs.docker.com/compose/startup-order/

2.3. 部署和运行服务 - (deploy)

注意:Compose does not support ‘deploy’ configuration - use docker stack deploy to deploy to a swarm

2.3.1. 部署模式、部署更新回滚策略

  • endpoint_mode:服务发现方式,为客户端连接到群集的外部指定服务发现方法。
    • vip: 默认设置,Docker为服务设定VIP,而无需客户端知道有多少节点(dockerd主机)参与服务,这是是默认设置
    • dnsrr: 基于DNS服务发现,基于DNS查询到的DNS条目IP,客户端随后挑选其中一个进行连接
  • labels:针对服务设定的标签(web>deploy>labels);在容器上设定标签,需要提升至服务下级(web>lables)
  • mode: 设定集群节点的容器部署模式
    • global:每个节点一个容器
    • replicated: 每个节点指定数量(replicas指定)的多个容器(默认模式)
  • replicas: replicated模式下(默认)应运行的容器数
  • update_config:配置服务应如何更新,用于配置滚动更新
    • delay:一组容器更新等待时间
    • failure_action:失败措施(continue|rollback|pause(默认))
    • monitor: 更新后监视时间以检测失败(ns|us|ms|s|m|h)
    • max_failure_ratio: 失败率
    • order:更新期间的操作顺序(start-first|stop-first(默认))
  • rollback_config:配置在更新失败的情况下应如何回滚服务
version: "3.7"
services:
  redis:
    image: redis:alpine
    deploy:
      replicas: 6
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure

2.3.2. 部署和运行服务资源限定 - (deploy > resources)

示例中,redis服务被限制为使用不超过50M的内存和0.50(单核的50%)可用处理时间(CPU),并且保留20M了内存和0.25CPU时间(始终可用)。

  • limits:资源使用限制
    • cpus
    • memory
  • reservations:保留资源
version: "3.7"
services:
  redis:
    image: redis:alpine
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 50M
        reservations:
          cpus: '0.25'
          memory: 20M

2.4. 记录容器运行的服务日志 - (logging)

通常可以基于docker run --log-opt指定容器的日志选项。基于docker-compose.yml配置中,可以单独指定相关日志驱动和其选项(比如日志旋转大小,保留日志数量等)

  • driver驱动类型支持:
    • json-file(默认)
    • syslog
    • none
    • journald
  • options日志选项类型支持

json-filejournald驱动支持docker-compose logs或者docker-compose up

// 服务容器的日志记录驱动程序
version: "3.7"
services:
  some-service:
    image: some-service
    logging:
      driver: "json-file"
      options:
        max-size: "200k"
        max-file: "10"

2.5. 设置服务网络 - (network)

通常可以基于docker client --network指定容器的网络选项。基于docker-compose.yml配置中,可以单独指定服务的网络

  • networks - 网络模式:服务的顶层
    • bridge
    • host
    • none
    • service:[service name]
    • container:[container name/id]
  • aliases - 网络别名:
    • 同网络上面的其他容器或服务,可以基于该网络别名,与别名的容器通信
    • Tips:网络范围的别名可以由多个容器共享(无法保证名称解析为确定的哪个容器),甚至可以由多个服务共享(比如共享的代理服务)
  • app_net - 指定服务的应用网络,包含每个静态地址的子网

2.5.1. 为容器服务指定静态的IP地址

如果需要IPv6寻址,则enable_ipv6必须设置该选项

version: "3.7"
services:
  app:
    image: nginx:alpine
    networks:
      app_net:
        ipv4_address: 172.16.238.10
        ipv6_address: 2001:3984:3989::10
networks:
  app_net:
    ipam:
      driver: default
      config:
        - subnet: "172.16.238.0/24"
        - subnet: "2001:3984:3989::/64"

2.5.2. 顶级networks键允许您指定要创建的网络

  • driver:
    • overlay:Swarm集群
    • bridge:单主机
    • host
    • none
  • attachable:仅在driver设置为时使用overlay,如果设置为true,则除了服务之外,独立容器可以附加到此网络。
    • 它可以与也从其他Docker守护程序连接到覆盖网络的服务和独立容器进行通信。
  • driver_opts
  • external:如果设置为true,则指定此网络已在Compose之外创建(否则直接报错)。

2.6. 设置容器通信端口 - (expose、ports)

  • expose: 端口暴露,暴露给被链接的服务访问,不将它们发布到主机
  • ports:
    • 短语法:指定ports(HOST:CONTAINER)或仅指定容器端口(选择短暂的主机端口),使用低于60的容器端口时可能会遇到错误的结果
    • 长语法:target - 容器内的端口、published - 公开端口、prtotocl - udp|tcp、mode - host|ingress
// 短语法端口映射或仅开启容器端口
ports:
 - "3000"
 - "3000-3005"
 - "8000:8000"
 - "9090-9091:8080-8081"
 - "49100:22"
 - "127.0.0.1:8001:8001"
 - "127.0.0.1:5000-5010:5000-5010"
 - "6060:6060/udp"
// 长语法模式
ports:
  - target: 80
    published: 8080
    protocol: tcp
    mode: host

2.7. 容器重启策略 - (restart)

restart: "no" // 容器不会重启
restart: always // 容器总是自动重启
restart: on-failure // 仅失败重启
restart: unless-stopped // 除主动停止外,总是自动重启

2.8. 容器卷 - (volumes)

  • 默认情况下,在容器内创建的所有文件都存储在可写容器层中。这意味着:
    1. 容器不存在,数据会丢失
    2. 无法轻松移动或者管控容器数据
    3. 写入容器的可写层需要存储驱动程序来管理文件系统
  • 主机和容器存储文件的交互方式:
    • volume,卷:卷存储在主机上的文件系统中,非Docker进程不应修改文件系统的这一部分,卷是在Docker中保留数据的最佳方式。
    • bind,绑定挂载:主机的文件系统和容器中文件系统绑定,可以在主机和容器中同步修改
    • tmpfs,tmpfs挂载,仅存储在主机的内存中,npipe命令管道(windows)
  • 卷指令设置
    • 短语法(示例db):HOST:CONTAINER
      • 若是相对路径,则主机上面路径相对于Compose配置文件的目录
    • 长语法

2.8.1. Volumes示例

version: "3.7"
services:
  web:
    image: nginx:alpine
    volumes:
      - type: volume
        source: mydata
        target: /data
        volume:
          nocopy: true
      - 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:

2.8.2. Volumes语法说明

  • 短语法:
volumes:
  # 通过卷,仅指定一个容器的路径,让Docker主机创建一个匿名卷
  - /var/lib/mysql
  # 通过绑定卷,将Docker主机(/opt/data)路径映射到容器路径(/var/lib/mysql)
  - /opt/data:/var/lib/mysql
  # 通过绑定卷,将Docker主机(./cache,相对于ComposeFile的)路径,映射到容器路径(/tmp/cache)
  - ./cache:/tmp/cache
  # 通过绑定卷,相对路径,映射到容器路径,并设定容器针对该目录只读
  - ~/configs:/etc/configs/:ro
  # 通过命名卷绑定(提前Docker主机创建了命名卷)
  - datavolume:/var/lib/mysql
  • 长语法:
    • type:安装类型,支持volumebindtmpfsnpipe
    • source:挂载源,卷名或者主机路径
    • target:容器安装卷或映射的路径
    • bind:配置其他绑定选项(propagation)
    • volume:nocopy,禁止从容器复制数据
    • tmpfs:size(配置tmpfs大小)
    • consistency:consistent(容器与主机读写一致)、cached(容器读的是缓存)、delegated(容器的实体是权威的)

参考:https://docs.docker.com/compose/compose-file/#volumes-for-services-swarms-and-stack-files

2.8.3. 服务、Swarm和Stack以及卷

使用服务,Swarm群组(多个Dockerd主机组合在一起)和docker-stack.yml文件时,请记住,支持服务的任务(容器)可以部署在群中的任何节点上,每次更新服务时,容器可能是在不同的节点上。

如果没有具有指定源的命名卷,Docker会为支持服务(Services)的每个任务创建一个匿名卷,删除关联的容器后,匿名卷不会保留。

如果希望数据保持不变,请使用可识别多主机的命名卷和卷驱动程序,以便可以从任何节点访问数据。或者,对服务设置约束,以便将其任务部署在具有卷的节点上。

2.8.4. 跨服务重用命名卷 - (volumes_from)

  1. 双服务作为共享卷设置,顶级volumes键下的条目可以为空,在这种情况下,它使用引擎配置的默认驱动程序(在大多数情况下,这是local驱动程序),(可选)您可以使用以下键进行配置:
    • driver:卷驱动程序(默认是local)
    • driver_opts:卷驱动相关设置,比如驱动类型、选项、驱动
    • external:
    • name:为卷命名
version: "3.7"
services:
  db:
    image: db
    volumes:
      - data-volume:/var/lib/db
  backup:
    image: backup-service
    volumes:
      - data-volume:/var/lib/backup/data
volumes:
  data-volume:
  example:
    driver_opts:
      type: "nfs"
      o: "addr=10.40.0.199,nolock,soft,rw"
      device: ":/docker/example"
  data:
    external: true

2.9. 基础设施杂项相关

  • sysctls:设置容器内核参数
  • ulimits:设置容器的默认ulimits
  • userns_mode
  • dns相关
    • dns:dns服务IP
    • dns_search:dns搜寻域
  • entrypoint:覆盖Dockerfile中默认进入点以及CMD命令
  • 构建变量相关
    • env_file:从文件添加环境变量,可以是单个值或列表,env文件每行都基于VAR=VAL模式,#为注释;另外文件的顺序很重要(尤其是存在重名变量)
    • environment:环境变量,没有指定则从构建机器上的环境变量
  • external_links:外部链接(推荐使用network网络链接),公共共享服务容器,外部docker-compose.yml,指定容器名称
  • extra_hosts:为容器添加host映射
  • devices:设备映射
  • healthcheck:容器状态转变(starting=>健康监测通过=>healthy=>连续失败=>unhealthy)
    • 通过在容器内运行命令、禁用从基础映像继承的任何运行状况检查
    • interval:间隔多久监测一次
    • timeout:超时算失败一次
    • retries:最大重试次数,重试连续的健康检查失败才能考虑容器unhealthy
    • start-period:提供引导的容器提供初始化时间(容器启动期间,监测失败不算重试失败)
  • pid:将PID模式设置为主机PID模式。这打开了容器和主机操作系统之间的PID地址空间共享。
  • 容器停止信号
    • stop_grace_period
    • stop_signal
  • 其他相关配置
    • domainname
    • hostname
    • ipc
    • mac_address
    • privileged
    • read_only
    • shm_size
    • stdin_open
    • tty
    • user
    • working_dir
  • 持续时间格式:us,ms,s,m和h,比如5h34m56s
  • 指定字节值:b,k/kb,m/mb,g/gb
  • container_name:指定自定义容器名称
  • init:容器内运行init
env_file:
  - ./common.env
  - ./apps/web.env
  - /opt/secrets.env
environment:
  - RACK_ENV=development
  - SHOW=true
  - SESSION_SECRET
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost"]
  interval: 1m30s
  timeout: 10s
  retries: 3
  start_period: 40s
extra_hosts:
  - "somehost:162.242.195.82"
  - "otherhost:50.31.209.229"
sysctls:
  net.core.somaxconn: 1024
  net.ipv4.tcp_syncookies: 0

2.10. 配置、秘钥 - (configs、secrets)

顶级configs声明指定每个服务为基础授予对配置的访问权限,配置文件等信息统一管控,类似于顶级的volumes以及networkds

顶级secrets声明定义或引用 可以授予此堆栈中的服务的机密,使用服务secrets可以配置基于每个服务授予对秘密的访问权限。秘密的来源是file或external

configs:
  my_first_config:
    file: ./config_data
  my_second_config:
    external:
      name: redis_config
secrets:
  my_first_secret:
    file: ./secret_data
  my_second_secret:
    external: true

2.11. 变量替换 - (支持.env文件)

若环境变量包含POSTGRES_VERSION=9.3,则以下镜像会选择postgres:9.3,如果未设置环境变量,则使用空字符串Compose替换。

可以使用Compose自动查找的.env文件为环境变量设置默认值,shell环境中设置的值将覆盖.env文件中设置的值(.env仅针对docker-compose up有效,不对docker stack deploy有效)。

另外变量支持默认语法,类似于shell中变量的默认值使用:

  • ${VARIABLE:-default}:环境变量没设置或设置为空,则为默认的default
  • ${VARIABLE-default}
  • ${VARIABLE:?err}
  • ${VARIABLE?err}
db:
  image: "postgres:${POSTGRES_VERSION}"

3. 调试

3.1. 针对应用内容器网络连通调试

以下测试为了方便,均以nginx:alpine镜像启动,模拟代理层<=>服务层<=>数据存储层通信。

代理层:仅可与服务层(websrv-a、websrv-b)正常网络通信 服务层:websrv可以与代理层、数据存储层通信 数据层:仅可与服务层通信

  • 网络连通性应用模拟 - docker-compose.yml
version: "3.7"
services:
  proxy:
    image: "nginx:alpine"
    deploy:
      replicas: 2
    networks:
      - net-frontend
    extra_hosts:
      - "tkstorm_web:127.0.0.1"
      - "ioio.cool:127.0.0.1"
  websrv-a:
    image: "nginx:alpine"
    networks:
      - net-frontend
      - net-backend
  websrv-b:
    image: "nginx:alpine"
    networks:
      - net-frontend
      - net-backend
  db:
    image: "nginx:alpine"
    networks:
      - net-backend
  mq:
    image: "nginx:alpine"
    networks:
      - net-backend
networks:
    net-frontend:
    net-backend:
  • proxy容器路由情况
$ docker-compose exec proxy route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.18.0.1      0.0.0.0         UG    0      0        0 eth0
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
  • websrv容器路由情况(以websrv-a为例)
$ docker-compose exec websrv-a route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.19.0.1      0.0.0.0         UG    0      0        0 eth0
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth1
172.19.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
  • db、mq容器路由情况(以db为例)
$ docker-compose exec db route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.19.0.1      0.0.0.0         UG    0      0        0 eth0
172.19.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
  • 网络查看
$ docker network ls
NETWORK ID          NAME                        DRIVER              SCOPE
83162e33b5ad        bridge                      bridge              local
220b80e495d7        host                        host                local
dd8cff4a042c        network_test_net-backend    bridge              local
206ffbb7bf2f        network_test_net-frontend   bridge              local
02f2210c0a6e        none                        null                local

针对容器网络互通,可以查看另外一篇文章容器网络互连

3.2. 其他相关错误

3.2.1. docker-compose.yml版本问题

// 不支持命名网络,可以通过修改下docker-compose.yml版本解决,比如`version: "3.7"`
docker compose networks.net-proxy value Additional properties are not allowed ('name' was unexpected)

4. 小结

文章对docker compose进行了简要概览,docker compose主要目的是对服务进行编排(即组合多个服务成一个应用),docker-compose.yml就是一份应用服务的配置清单,标注清楚了服务的依赖服务以及其配置!

通过docker-compose.yml配置文件,docker引擎可以指定应用的每个服务的镜像如何构建,容器如何部署(部署模式、部署份数),容器之间网络是如何互通的,容器内进程对存储卷的读写以及存储设置(支持volume、bind等类型),并且还支持运行容器的资源大小设置(比如打开文件数、服务内存使用大小等),更进一步的配置详情需要参考对应的官方文档。

4.1. 参考

  1. https://docs.docker.com/compose/compose-file/