07-Docker Compose多容器编排

Docker Compose多容器编排

本文是Docker专栏的第七篇,将全面深入地讲解Docker Compose------Docker官方推出的多容器编排工具。从基础概念到高级配置,从命令详解到实战案例,涵盖Docker Compose的方方面面。无论你是刚接触容器编排的新手,还是希望系统化掌握Compose的开发者,这篇文章都将为你提供完整的知识体系。


引言

在现代软件开发中,一个应用往往不是孤立的。一个典型的Web应用可能包含前端服务器、后端API、数据库、缓存、消息队列等多个组件。如果用原生的docker run命令来启动这些容器,你需要手动管理容器之间的依赖关系、网络连接、数据卷挂载、环境变量传递等诸多细节。当容器数量增多时,这种手动管理方式会变得极其繁琐且容易出错。

Docker Compose正是为了解决这个问题而诞生的。它允许你使用一个YAML文件来定义整个多容器应用的服务、网络和数据卷,然后通过一条命令就能启动、停止和管理整个应用栈。Docker Compose将"基础设施即代码"的理念引入到容器编排中,使得多容器应用的部署变得可重复、可版本化、可移植。

本文将从Docker Compose的基本概念讲起,逐步深入到配置文件的每一个细节,再到命令行的完整用法,最后通过多个真实场景的实战案例,帮助你全面掌握Docker Compose。


第一章 Docker Compose概述

1.1 什么是Docker Compose及解决的问题

1.1.1 Docker Compose的定义

Docker Compose是Docker官方提供的一个工具,用于定义和运行多容器Docker应用程序。它使用YAML文件来配置应用所需的所有服务,然后通过一条命令即可从配置文件中创建并启动所有服务。

简单来说,如果Docker Engine是管理单个容器的工具,那么Docker Compose就是管理多个容器的"指挥家"。

1.1.2 Docker Compose解决的核心问题

在没有Docker Compose之前,开发者面临以下痛点:

痛点一:命令冗长难以管理

假设你需要启动一个包含Nginx、MySQL、Redis的三容器应用,使用原生Docker命令可能是这样的:

bash 复制代码
# 创建网络
docker network create myapp-network

# 启动MySQL容器
docker run -d \
  --name mysql \
  --network myapp-network \
  -e MYSQL_ROOT_PASSWORD=root123 \
  -e MYSQL_DATABASE=myapp \
  -v mysql-data:/var/lib/mysql \
  mysql:8.0

# 启动Redis容器
docker run -d \
  --name redis \
  --network myapp-network \
  -v redis-data:/data \
  redis:7-alpine

# 启动Nginx容器
docker run -d \
  --name nginx \
  --network myapp-network \
  -p 80:80 \
  -v ./nginx.conf:/etc/nginx/nginx.conf:ro \
  nginx:1.25

这只是三个容器,命令就已经非常冗长了。如果你的应用有10个甚至20个容器,管理这些命令将是一场噩梦。

痛点二:容器依赖关系难以管理

在实际应用中,容器之间往往存在依赖关系。比如,后端API需要等数据库启动并就绪后才能启动。使用原生Docker命令,你只能通过脚本和睡眠等待来处理这种依赖,这既不可靠也不优雅。

痛点三:环境一致性难以保证

开发环境、测试环境、生产环境的容器配置需要保持一致。如果使用脚本管理,很容易出现某个环境遗漏了某个配置项的情况。

痛点四:生命周期管理复杂

停止、删除、重建容器时,需要按特定顺序操作。比如,删除容器时需要先删依赖方再删被依赖方,删除数据卷需要额外确认等。

Docker Compose通过声明式配置文件优雅地解决了上述所有问题:

yaml 复制代码
# docker-compose.yml - 同样的三容器应用,用Compose只需一个文件
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: myapp
    volumes:
      - mysql-data:/var/lib/mysql

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data

  nginx:
    image: nginx:1.25
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - mysql
      - redis

volumes:
  mysql-data:
  redis-data:

然后只需一条命令即可启动整个应用:

bash 复制代码
docker compose up -d
1.1.3 Docker Compose的适用场景

Docker Compose适用于以下场景:

场景 说明
开发环境 一键启动完整的开发环境,包括应用、数据库、缓存等
自动化测试 在CI/CD流水线中快速启动测试环境,测试完成后一键销毁
单机部署 在单台服务器上部署完整的多容器应用
原型验证 快速搭建技术原型,验证架构设计
学习实验 快速搭建各种中间件组合进行学习实验

需要注意的是,Docker Compose主要用于单机编排。如果你需要跨多台服务器的集群编排,应该使用Docker Swarm或Kubernetes。不过,Docker Compose文件可以平滑迁移到Docker Swarm(通过docker stack deploy命令),这也是它的一个重要优势。

1.2 Docker Compose vs Docker CLI

为了更好地理解Docker Compose的价值,我们来对比一下Docker CLI和Docker Compose的区别。

1.2.1 功能对比
对比维度 Docker CLI (docker run) Docker Compose
配置方式 命令行参数,内联配置 YAML文件,声明式配置
多容器管理 需要手动逐个启动 一条命令启动所有容器
依赖管理 需要脚本控制启动顺序 内置depends_on依赖管理
网络管理 手动创建和连接网络 自动创建项目网络
数据卷管理 手动创建和挂载 声明式定义和管理
可重复性 命令记录在脚本中,容易遗漏 配置文件版本化管理
可读性 长命令难以阅读 YAML结构清晰易读
团队协作 脚本可能因人而异 统一配置文件,团队共享
环境覆盖 需要多套脚本 多文件覆盖机制
适用规模 1-3个容器 3-50个容器(单机)
1.2.2 命令对比

以启动一个带数据卷的MySQL容器为例:

Docker CLI方式:

bash 复制代码
# 创建数据卷
docker volume create mysql-data

# 创建网络
docker network create app-net

# 启动容器
docker run -d \
  --name mysql \
  --network app-net \
  -e MYSQL_ROOT_PASSWORD=root123 \
  -e MYSQL_DATABASE=myapp \
  -e MYSQL_USER=appuser \
  -e MYSQL_PASSWORD=apppass \
  -v mysql-data:/var/lib/mysql \
  -p 3306:3306 \
  --restart unless-stopped \
  mysql:8.0 \
  --character-set-server=utf8mb4 \
  --collation-server=utf8mb4_unicode_ci

Docker Compose方式:

yaml 复制代码
# docker-compose.yml
services:
  mysql:
    image: mysql:8.0
    container_name: mysql
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: myapp
      MYSQL_USER: appuser
      MYSQL_PASSWORD: apppass
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - "3306:3306"
    restart: unless-stopped
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    networks:
      - app-net

volumes:
  mysql-data:

networks:
  app-net:
bash 复制代码
# 一条命令搞定
docker compose up -d

可以看到,Docker Compose将所有配置集中在一个可读性极强的YAML文件中,大大降低了管理复杂度。

1.2.3 何时使用Docker CLI vs Docker Compose
  • 使用Docker CLI: 快速测试单个容器、临时运行一次性任务、调试单个容器
  • 使用Docker Compose: 管理多容器应用、开发环境搭建、CI/CD流水线、需要可重复部署的场景

实际上,两者并不互斥。在日常开发中,开发者通常会同时使用两者:用Docker Compose管理应用整体架构,用Docker CLI进行单个容器的调试和排查。

1.3 Docker Compose版本演进(v1/v2区别)

1.3.1 Docker Compose v1

Docker Compose v1是用Python编写的独立工具,命令格式为docker-compose(带连字符)。它是通过pip安装的独立可执行文件:

bash 复制代码
# v1安装方式(已废弃)
pip install docker-compose

v1的特点:

  • 独立的Python程序
  • 命令格式为docker-compose(带连字符)
  • 与Docker Engine通过REST API通信
  • 性能相对较低(Python启动开销)
  • 不支持Docker CLI插件机制
1.3.2 Docker Compose v2

Docker Compose v2是用Go语言完全重写的版本,作为Docker CLI的插件集成。命令格式改为docker compose(空格分隔,不带连字符)。

v2的特点:

  • 用Go重写,性能显著提升
  • 作为Docker CLI插件,无需单独安装
  • 命令格式为docker compose(空格分隔)
  • 与Docker CLI深度集成,共享配置和认证
  • 支持GPU、更好的构建集成
  • 更好的错误信息和调试体验
1.3.3 v1与v2详细对比
对比项 Compose v1 Compose v2
开发语言 Python Go
命令格式 docker-compose docker compose
安装方式 独立安装(pip/binary) Docker CLI插件(随Docker安装)
性能 较慢(Python启动开销) 更快(Go编译,无启动开销)
兼容性 兼容旧版Docker 需要Docker 20.10+
维护状态 已停止维护(EOL) 活跃维护
GPU支持 需要额外配置 原生支持
构建集成 独立构建逻辑 docker build共享逻辑
配置文件 docker-compose.yml compose.yaml(推荐)
项目名 基于目录名 基于目录名(可覆盖)
1.3.4 从v1迁移到v2

如果你还在使用v1,以下是从v1迁移到v2的关键步骤:

bash 复制代码
# 1. 卸载v1
pip uninstall docker-compose
# 或者
sudo rm /usr/local/bin/docker-compose

# 2. 确认v2已安装(Docker Desktop自带v2)
docker compose version

# 3. 迁移脚本中的命令
# 将所有 docker-compose 替换为 docker compose
# 例如:
# docker-compose up -d  =>  docker compose up -d
# docker-compose down   =>  docker compose down

v1到v2的行为差异需要注意:

bash 复制代码
# v1中,如果不指定项目名,默认使用目录名(小写,去除特殊字符)
# v2中,行为相同,但增加了一个COMPOSE_PROJECT_NAME环境变量
# v2中,如果没有compose.yaml/docker-compose.yml文件,会报错而不是搜索

# v2中,docker compose ls命令可以列出所有Compose项目
docker compose ls

1.4 Docker Compose安装与验证

1.4.1 各平台安装方式

Windows (Docker Desktop):

Docker Desktop for Windows自带Docker Compose v2,无需单独安装:

powershell 复制代码
# 安装Docker Desktop后,直接验证
docker compose version

macOS (Docker Desktop):

bash 复制代码
# Docker Desktop for Mac自带Docker Compose v2
docker compose version

Linux (Ubuntu/Debian):

bash 复制代码
# 方式1: 通过Docker官方仓库安装(推荐)
# 安装Docker Engine时,v2会作为插件自动安装
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 方式2: 手动下载二进制文件
# 下载最新版本
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
curl -SL https://github.com/docker/compose/releases/download/v2.24.0/docker-compose-linux-x86_64 \
  -o $DOCKER_CONFIG/cli-plugins/docker-compose
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose

Linux (CentOS/RHEL):

bash 复制代码
# 安装Docker Engine和Compose插件
sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl start docker
sudo systemctl enable docker
1.4.2 验证安装

安装完成后,通过以下命令验证:

bash 复制代码
# 查看Docker Compose版本
docker compose version

# 预期输出类似:
# Docker Compose version v2.24.0

# 查看Docker Compose帮助
docker compose --help

# 查看Docker版本(确认Docker Engine正常)
docker version
1.4.3 配置Docker Compose

Docker Compose的一些行为可以通过环境变量来配置:

bash 复制代码
# 设置项目名称(默认为目录名)
export COMPOSE_PROJECT_NAME=myapp

# 设置Compose文件路径(默认为当前目录下的compose.yaml或docker-compose.yml)
export COMPOSE_FILE=docker-compose.yml:docker-compose.override.yml

# 设置并行操作数
export COMPOSE_PARALLEL_LIMIT=10

# 设置超时时间
export COMPOSE_HTTP_TIMEOUT=120

# 禁止ANSI控制字符
export COMPOSE_ANSI=never

1.5 compose文件的版本格式(v1/v2/v3区别及选择)

1.5.1 Compose文件版本历史

Docker Compose文件格式经历了多个版本的演进,每个版本都引入了新的特性:

版本 引入时间 说明 Docker Engine要求
Version 1 2014年 最早的格式,无version字段,无services层级 1.9+
Version 2 2016年 引入version: "2",services层级,网络/卷定义 1.10+
Version 2.1 2016年 增加healthcheck,depends_on条件等 1.12+
Version 2.2 2017年 增加init,cpu_count等 1.13+
Version 2.3 2017年 增加target,credential_spec等 17.06+
Version 3 2017年 为Swarm设计,引入deploy配置 1.13+
Version 3.1 2017年 增加secrets配置 1.13+
Version 3.2 2017年 增加configs配置 17.06+
Version 3.3 2017年 增加rollback_config 17.06+
Version 3.4 2017年 增加order,failure_action 17.09+
Version 3.5 2017年 增加isolation 17.12+
Version 3.6 2018年 增加tmpfs mount 18.09+
Version 3.7 2018年 增加start_period 18.06+
Specification 2020年 统一规范,不再使用version字段 无版本限制
1.5.2 各版本格式示例

Version 1格式(已废弃):

yaml 复制代码
# 没有version字段,没有services层级
# 所有服务直接定义在顶层
web:
  image: nginx:1.25
  ports:
    - "80:80"
db:
  image: mysql:8.0
  environment:
    MYSQL_ROOT_PASSWORD: root123

Version 2格式:

yaml 复制代码
version: "2"

services:
  web:
    image: nginx:1.25
    ports:
      - "80:80"
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123

networks:
  default:
    driver: bridge

Version 3格式:

yaml 复制代码
version: "3.8"

services:
  web:
    image: nginx:1.25
    ports:
      - "80:80"
    depends_on:
      - db
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 512M

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123

networks:
  default:
    driver: bridge

Compose Specification格式(推荐):

yaml 复制代码
# 不需要version字段
# 这是当前推荐的格式
services:
  web:
    image: nginx:1.25
    ports:
      - "80:80"
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123

networks:
  default:
    driver: bridge
1.5.3 v2与v3的关键区别
特性 Version 2.x Version 3.x
volumes_from 支持 不支持(已移除)
links 支持(已弃用) 不支持
cpu/memory限制 在服务级别直接设置 在deploy.resources中设置
depends_on条件 支持(condition) 不支持(3.x中移除,Specification中恢复)
deploy配置 不支持 支持(Swarm专用)
Swarm兼容 不兼容 兼容
extends 支持 不支持(3.x中移除,Specification中恢复)
网络DNSRR 不支持 支持(endpoint_mode: dnsrr)
1.5.4 版本选择建议

在当前时代(2024年及以后),版本选择非常简单:

  1. 推荐使用Compose Specification格式 : 不指定version字段,直接以services开头。这是Docker官方当前推荐的格式,兼容Docker Compose v2。

  2. 如果必须指定版本 : 使用version: "3.8",这是v3系列的最后一个版本,功能最全。

  3. 不要使用v1格式: 已完全废弃,新版本Docker Compose可能不再支持。

  4. 文件名选择 : 推荐使用compose.yaml(或compose.yml),Docker Compose会优先查找这个文件名。docker-compose.yml仍然兼容,但不再是首选。

yaml 复制代码
# 当前推荐的compose.yaml文件格式
# 不需要version字段
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DEBUG=true
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

  redis:
    image: redis:7-alpine

1.6 Docker Compose与Docker Swarm的关系

1.6.1 Docker Swarm简介

Docker Swarm是Docker原生的容器编排系统,它可以将多台Docker主机组成一个集群,在集群上调度和管理容器。Docker Swarm内置在Docker Engine中,无需额外安装。

1.6.2 Compose与Swarm的关系

Docker Compose和Docker Swarm有着密切的关系:

  • Compose用于定义 : Compose文件(compose.yaml)定义了应用的架构,包括服务、网络、数据卷等。
  • Swarm用于执行: Swarm集群负责在多节点上调度和运行这些服务。

Docker Compose文件可以直接用于Swarm部署,通过docker stack deploy命令:

bash 复制代码
# 在Swarm集群中部署Compose文件
docker stack deploy -c docker-compose.yml myapp

# 查看部署的服务
docker stack services myapp

# 删除部署
docker stack rm myapp
1.6.3 Compose vs Swarm vs Kubernetes
对比项 Docker Compose Docker Swarm Kubernetes
部署模式 单机 多机集群 多机集群
复杂度
学习曲线 平缓 中等 陡峭
适用规模 小型(<50容器) 中型(<500容器) 大型(无上限)
高可用 不支持 支持 支持
自动扩缩容 手动 支持 支持(HPA/VPA)
滚动更新 基本支持 支持 支持
服务发现 DNS DNS + VIP DNS + Service
负载均衡 基本支持 内置Ingress Ingress Controller
配置管理 env文件 Secrets/Configs ConfigMap/Secret
存储管理 本地卷 本地卷/外部卷 PV/PVC/StorageClass
监控 基本支持 基本支持 丰富生态
1.6.4 Compose文件在Swarm中的限制

当使用Compose文件部署到Swarm时,有一些限制需要注意:

yaml 复制代码
services:
  web:
    image: nginx:1.25
    # 以下配置在Swarm中会被忽略:
    build: .                    # Swarm不支持构建,必须使用预构建镜像
    container_name: web         # Swarm中容器名由调度器自动分配
    ports:
      - "80:80"                 # 在Swarm中转换为published端口
    # 以下配置在Swarm中生效:
    deploy:
      replicas: 3               # 副本数
      update_config:            # 滚动更新配置
        parallelism: 1
        delay: 10s
        failure_action: rollback
      rollback_config:          # 回滚配置
        parallelism: 0
        order: stop-first
      restart_policy:           # 重启策略
        condition: on-failure
        max_attempts: 3
      placement:                # 调度约束
        constraints:
          - node.role == manager
        preferences:
          - spread: node.labels.zone
      resources:                # 资源限制
        limits:
          cpus: "0.5"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 256M
      labels:                   # 服务标签
        - "com.myapp.description=Web Service"

需要注意的是,在纯Docker Compose(非Swarm)模式下,deploy配置大部分会被忽略,只有replicas可以通过--scale参数间接使用,resources的限制在本地模式下也会部分生效。


第二章 docker-compose.yml文件结构

2.1 YAML基础语法回顾

在深入Docker Compose配置之前,我们先回顾一下YAML的基础语法,因为Compose文件就是用YAML格式编写的。

2.1.1 YAML基本规则
yaml 复制代码
# 1. 大小写敏感
Key: value    # 这是一个键值对
key: Value    # 这是另一个键值对(与上面的不同)

# 2. 使用缩进表示层级关系(只能用空格,不能用Tab)
parent:
  child1: value1
  child2:
    grandchild: value2

# 3. 缩进的空格数不重要,但同层级的元素必须左对齐
# 以下两种写法等价:
a:
  b:
    c: 1
  d: 2

a:
    b:
        c: 1
    d: 2

# 4. 注释以#开头
# 这是注释
key: value  # 行内注释

# 5. 字符串通常不需要引号,但包含特殊字符时需要
plain_string: hello
quoted_string: "hello world"
special_string: "hello: world"    # 包含冒号需要引号
single_quoted: 'hello world'      # 单引号也可以
2.1.2 YAML数据类型
yaml 复制代码
# 字符串
string_value: "hello"
string_no_quotes: hello
string_multiline: |
  This is a
  multi-line string
  preserving line breaks
string_folded: >
  This is a folded
  string where line breaks
  become spaces

# 整数
integer_value: 42

# 浮点数
float_value: 3.14

# 布尔值
boolean_true: true
boolean_false: false

# 空值
null_value: null
null_tilde: ~
null_implicit:

# 日期时间
date_value: 2024-01-15
datetime_value: 2024-01-15T10:30:00Z

# 列表(数组)
list_inline: [item1, item2, item3]
list_block:
  - item1
  - item2
  - item3

# 字典(对象)
dict_inline: {key1: value1, key2: value2}
dict_block:
  key1: value1
  key2: value2

# 嵌套结构
nested:
  - name: item1
    value: 100
  - name: item2
    value: 200
2.1.3 YAML在Compose中的常见用法
yaml 复制代码
# 列表形式 - 用于多个值
ports:
  - "80:80"
  - "443:443"
  - "8080:8080"

# 字典形式 - 用于键值对配置
environment:
  MYSQL_ROOT_PASSWORD: root123
  MYSQL_DATABASE: myapp
  DEBUG: "true"

# 列表中嵌套字典 - 用于复杂配置
volumes:
  - source: ./config
    target: /etc/app/config
    read_only: true
  - source: data-volume
    target: /var/lib/data

# 多行字符串 - 用于脚本或配置
command: >
  bash -c "
  python manage.py migrate &&
  python manage.py collectstatic --noinput &&
  gunicorn myapp.wsgi:application --bind 0.0.0.0:8000
  "
2.1.4 YAML常见陷阱
yaml 复制代码
# 陷阱1: 冒号后面必须有空格
# 错误
key:value
# 正确
key: value

# 陷阱2: 不能用Tab键缩进
# 错误(使用了Tab)
services:
\tweb:
\t\timage: nginx
# 正确(使用空格)
services:
  web:
    image: nginx

# 陷阱3: 字符串中的特殊字符
# 问题: 冒号会被解析为键值分隔符
# 错误
command: echo hello:world
# 正确
command: "echo hello:world"
# 或
command: echo "hello:world"

# 陷阱4: 版本号需要引号
# 问题: 3.8会被解析为浮点数3.8
# 可能出错
version: 3.8
# 正确
version: "3.8"

# 陷阱5: 端口映射需要引号
# 问题: 80:80可能被解析为时间格式
# 可能出错
ports:
  - 80:80
# 正确
ports:
  - "80:80"

2.2 compose文件顶层结构

Docker Compose文件的顶层结构包含以下几个主要部分:

yaml 复制代码
# 1. name(可选): 项目名称
name: myapp

# 2. version(可选,已弃用): 文件版本
# 在Compose Specification中不再需要
# version: "3.8"

# 3. services(必需): 定义所有服务
services:
  webapp:
    image: nginx:1.25
  api:
    build: ./api
  db:
    image: postgres:16

# 4. networks(可选): 定义自定义网络
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true

# 5. volumes(可选): 定义命名数据卷
volumes:
  db-data:
    driver: local
  log-data:
    driver: local

# 6. configs(可选): 定义配置文件(Swarm模式)
configs:
  nginx_config:
    file: ./nginx.conf

# 7. secrets(可选): 定义敏感数据
secrets:
  db_password:
    file: ./secrets/db_password.txt

# 8. include(可选): 引入其他compose文件
include:
  - docker-compose.monitoring.yml

# 9. extensions(可选): 自定义扩展字段
x-custom-config:
  reusable_key: reusable_value

各顶层元素的作用:

顶层元素 是否必需 作用 示例
name 可选 定义项目名称,影响资源命名前缀 name: myapp
services 必需 定义所有容器服务 见上方示例
networks 可选 定义自定义网络 见上方示例
volumes 可选 定义命名数据卷 见上方示例
configs 可选 定义配置(Swarm模式) 见上方示例
secrets 可选 定义敏感数据 见上方示例
include 可选 引入其他compose文件 见上方示例
x-* 可选 自定义扩展字段,可复用 见上方示例

2.3 services配置项总览

services是Compose文件中最核心的部分,每个服务定义了一个容器的配置。下面是所有可用的服务配置项总览:

yaml 复制代码
services:
  service_name:
    # ===== 镜像与构建 =====
    image: nginx:1.25                    # 使用现有镜像
    build:                               # 从Dockerfile构建
      context: ./dir
      dockerfile: Dockerfile
      args:
        BUILD_ARG: value
      cache_from:
        - image:cache
      target: builder
    pull_policy: always                  # 镜像拉取策略

    # ===== 运行配置 =====
    command: ["nginx", "-g", "daemon off;"]  # 覆盖启动命令
    entrypoint: /app/entrypoint.sh       # 覆盖入口点
    container_name: my-nginx             # 容器名称
    hostname: myapp-host                 # 容器主机名
    domainname: example.com              # 域名
    user: "1000:1000"                    # 运行用户
    working_dir: /app                    # 工作目录
    tty: true                            # 分配伪终端
    stdin_open: true                     # 保持标准输入打开

    # ===== 环境配置 =====
    environment:                         # 环境变量(内联)
      - DEBUG=true
      - PORT=8080
    env_file:                            # 环境变量文件
      - .env
      - .env.production

    # ===== 网络与端口 =====
    ports:                               # 端口映射(主机:容器)
      - "8080:80"
    expose:                              # 仅暴露给其他容器
      - "9000"
    networks:                            # 加入的网络
      - frontend
      - backend
    extra_hosts:                         # 添加主机名映射
      - "api.local:192.168.1.100"
    dns:
      - 8.8.8.8
      - 8.8.4.4

    # ===== 存储配置 =====
    volumes:                             # 数据卷挂载
      - ./data:/app/data
      - data-volume:/var/lib/data
    tmpfs:
      - /tmp
      - /run

    # ===== 依赖与启动顺序 =====
    depends_on:                          # 服务依赖
      db:
        condition: service_healthy
      redis:
        condition: service_started

    # ===== 生命周期管理 =====
    restart: unless-stopped              # 重启策略
    healthcheck:                         # 健康检查
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

    # ===== 资源限制 =====
    deploy:                              # 部署配置(Swarm)
      replicas: 3
      resources:
        limits:
          cpus: "1.0"
          memory: 1G
        reservations:
          cpus: "0.5"
          memory: 512M
    mem_limit: 1g                        # 内存限制(非Swarm)
    cpus: "1.0"                          # CPU限制(非Swarm)

    # ===== 日志配置 =====
    logging:                             # 日志驱动配置
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

    # ===== 安全配置 =====
    privileged: false                    # 特权模式
    cap_add:                             # 添加内核能力
      - NET_ADMIN
    cap_drop:                            # 删除内核能力
      - ALL
    security_opt:                        # 安全选项
      - no-new-privileges:true
    read_only: true                      # 只读文件系统
    sysctls:                             # 内核参数
      net.core.somaxconn: 1024
    ulimits:                             # 资源限制
      nproc: 65535
      nofile:
        soft: 20000
        hard: 40000

    # ===== 元数据 =====
    labels:                              # 标签
      - "com.myapp.role=web"
    environment:
      - TZ=Asia/Shanghai

    # ===== 其他 =====
    profiles: ["dev"]                    # 配置文件(条件启动)
    extends:                             # 继承其他服务配置
      file: base.yml
      service: base-web
    secrets:                             # 挂载敏感数据
      - db_password
    configs:                             # 挂载配置
      - nginx_config
    init: true                           # 使用init进程
    stop_grace_period: 30s               # 停止宽限期
    stop_signal: SIGTERM                 # 停止信号

2.4 网络定义与配置

在Compose文件中,networks顶层元素用于定义自定义网络,服务可以通过networks配置项加入这些网络。

yaml 复制代码
services:
  web:
    image: nginx:1.25
    networks:
      - frontend
      - backend

  api:
    image: myapi:latest
    networks:
      - backend
      - internal

  db:
    image: postgres:16
    networks:
      - internal

networks:
  # 基本网络定义
  frontend:
    driver: bridge                        # 网络驱动
    name: myapp-frontend                  # 网络名称(可选)

  # 内部网络(不可访问外部)
  backend:
    driver: bridge
    internal: true                        # 内部网络,隔离外部访问

  # 外部网络(引用已存在的网络)
  internal:
    name: existing-network
    external: true                        # 引用外部已创建的网络

  # 带详细配置的网络
  custom-net:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.default_bridge: "false"
      com.docker.network.bridge.enable_icc: "true"
      com.docker.network.bridge.host_binding_ipv4: "0.0.0.0"
      com.docker.network.bridge.name: "br-custom"
      com.docker.network.enable_ipv6: "true"
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/16
          ip_range: 172.20.0.0/24
          gateway: 172.20.0.1
          aux_addresses:
            host1: 172.20.0.2
            host2: 172.20.0.3
    labels:
      - "com.myapp.network=custom"
    enable_ipv6: true                     # 启用IPv6

网络配置项说明:

配置项 说明 示例
driver 网络驱动 bridge/overlay/host/none
name 网络名称 myapp-network
internal 是否为内部网络 true/false
external 是否引用外部网络 true/false
driver_opts 驱动选项 见上方示例
ipam IP地址管理 见上方示例
labels 网络标签 列表形式
enable_ipv6 启用IPv6 true/false

服务中引用网络的更多配置:

yaml 复制代码
services:
  web:
    image: nginx:1.25
    networks:
      # 简单引用
      - frontend

      # 带别名引用
      backend:
        aliases:
          - web-backend
          - nginx-server

      # 带优先级引用
      internal:
        priority: 100

      # 指定静态IP
      custom-net:
        ipv4_address: 172.20.0.10
        ipv6_address: 2001:db8::10
        link_local_ips:
          - 169.254.0.1
        mac_address: 02:42:ac:14:00:0a

2.5 数据卷定义与配置

volumes顶层元素用于定义命名数据卷,这些数据卷可以在多个服务之间共享和持久化数据。

yaml 复制代码
services:
  db:
    image: postgres:16
    volumes:
      - db-data:/var/lib/postgresql/data
      - db-backup:/backup

  cache:
    image: redis:7
    volumes:
      - cache-data:/data

volumes:
  # 基本数据卷定义
  db-data:
    name: myapp-db-data                 # 数据卷名称(可选)

  # 带驱动配置的数据卷
  db-backup:
    driver: local                       # 本地驱动
    driver_opts:
      type: nfs                         # NFS类型
      device: ":/path/to/nfs/share"
      o: addr=192.168.1.100,rw,size=1048576

  # 带标签的数据卷
  cache-data:
    driver: local
    labels:
      - "com.myapp.volume=cache"
      - "com.myapp.retention=7days"

  # 外部数据卷(引用已存在的)
  external-data:
    name: existing-volume
    external: true                      # 引用外部已创建的数据卷

  # 指定主机路径绑定
  log-data:
    driver: local
    driver_opts:
      type: none
      device: /var/log/myapp
      o: bind

数据卷配置项说明:

配置项 说明 示例
driver 数据卷驱动 local/nfs/cluster
name 数据卷名称 myapp-data
external 是否引用外部数据卷 true/false
driver_opts 驱动选项 见上方示例
labels 数据卷标签 列表形式

2.6 全局配置项

2.6.1 name - 项目名称
yaml 复制代码
# 定义项目名称
# 影响所有资源的命名前缀: <project_name>_<service_name>_<instance>
name: myapp

services:
  web:
    image: nginx:1.25
    # 容器名默认为: myapp-web-1

  db:
    image: postgres:16
    # 容器名默认为: myapp-db-1

如果不指定name,Compose会使用当前目录名作为项目名(转为小写,去除特殊字符)。

2.6.2 include - 引入其他文件
yaml 复制代码
# main compose file: compose.yaml
include:
  - docker-compose.monitoring.yml
  - docker-compose.logging.yml

services:
  app:
    image: myapp:latest
    ports:
      - "8080:8080"
yaml 复制代码
# docker-compose.monitoring.yml
services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"

include会将其他Compose文件的服务、网络、数据卷合并到当前文件中。

2.6.3 x-* 扩展字段
yaml 复制代码
# 定义可复用的配置片段
x-common-env: &common-env
  TZ: Asia/Shanghai
  LANG: en_US.UTF-8

x-common-logging: &common-logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

x-restart-policy: &restart-policy
  restart: unless-stopped

services:
  web:
    image: nginx:1.25
    environment:
      <<: *common-env                    # 引用公共环境变量
      NGINX_PORT: 80
    logging: *common-logging             # 引用公共日志配置
    <<: *restart-policy                  # 引用重启策略

  api:
    image: myapi:latest
    environment:
      <<: *common-env
      APP_PORT: 8080
    logging: *common-logging
    <<: *restart-policy

2.7 一个完整的compose文件模板解析

下面是一个包含大部分配置项的完整Compose文件模板,我们对每个部分进行详细解析:

yaml 复制代码
# ==========================================
# 项目名称
# ==========================================
name: mywebapp

# ==========================================
# 服务定义
# ==========================================
services:
  # ----------------------------------------
  # 前端服务: Nginx反向代理
  # ----------------------------------------
  nginx:
    image: nginx:1.25-alpine
    container_name: nginx-proxy
    hostname: nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - nginx-logs:/var/log/nginx
    networks:
      - frontend
    depends_on:
      - flask-app
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"
    labels:
      - "app=mywebapp"
      - "component=nginx"
      - "environment=production"
    profiles:
      - production

  # ----------------------------------------
  # 后端服务: Flask应用
  # ----------------------------------------
  flask-app:
    build:
      context: ./flask-app
      dockerfile: Dockerfile
      args:
        - PYTHON_VERSION=3.11
      target: production
    image: mywebapp/flask-app:latest
    container_name: flask-app
    hostname: flask
    restart: unless-stopped
    command: >
      gunicorn --bind 0.0.0.0:5000
      --workers 4
      --threads 2
      --timeout 120
      app:app
    environment:
      - FLASK_APP=app.py
      - FLASK_ENV=production
      - DATABASE_URL=postgresql://appuser:apppass@postgres:5432/myapp
      - REDIS_URL=redis://redis:6379/0
      - SECRET_KEY=${SECRET_KEY:-defaultsecret}
    env_file:
      - .env
    expose:
      - "5000"
    networks:
      - frontend
      - backend
    volumes:
      - ./flask-app/app:/app/app:ro
      - app-logs:/app/logs
      - app-uploads:/app/uploads
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    deploy:
      replicas: 1
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 128M
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "10"
    labels:
      - "app=mywebapp"
      - "component=api"
    stop_grace_period: 30s
    init: true

  # ----------------------------------------
  # 数据库服务: PostgreSQL
  # ----------------------------------------
  postgres:
    image: postgres:16-alpine
    container_name: postgres-db
    hostname: postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secretpass}
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C"
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./postgres/init:/docker-entrypoint-initdb.d:ro
      - ./postgres/conf/postgresql.conf:/etc/postgresql/postgresql.conf:ro
    command: postgres -c config_file=/etc/postgresql/postgresql.conf
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  # ----------------------------------------
  # 缓存服务: Redis
  # ----------------------------------------
  redis:
    image: redis:7-alpine
    container_name: redis-cache
    hostname: redis
    restart: unless-stopped
    command: >
      redis-server
      --maxmemory 256mb
      --maxmemory-policy allkeys-lru
      --appendonly yes
      --requirepass ${REDIS_PASSWORD:-redispass}
    volumes:
      - redis-data:/data
    networks:
      - backend
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    sysctls:
      net.core.somaxconn: 1024

# ==========================================
# 网络定义
# ==========================================
networks:
  frontend:
    name: mywebapp-frontend
    driver: bridge
    labels:
      - "network=frontend"

  backend:
    name: mywebapp-backend
    driver: bridge
    internal: true
    labels:
      - "network=backend"

# ==========================================
# 数据卷定义
# ==========================================
volumes:
  postgres-data:
    name: mywebapp-postgres-data
    labels:
      - "volume=postgres-data"

  redis-data:
    name: mywebapp-redis-data
    labels:
      - "volume=redis-data"

  nginx-logs:
    name: mywebapp-nginx-logs

  app-logs:
    name: mywebapp-app-logs

  app-uploads:
    name: mywebapp-app-uploads

# ==========================================
# 配置文件(Swarm模式)
# ==========================================
configs:
  nginx_config:
    file: ./nginx/nginx.conf

# ==========================================
# 敏感数据
# ==========================================
secrets:
  db_password:
    file: ./secrets/db_password.txt
  redis_password:
    file: ./secrets/redis_password.txt

这个模板涵盖了Compose文件的几乎所有主要配置项。在后续章节中,我们将对每个配置项进行更详细的讲解。


第三章 Services配置详解(上)

本章将详细讲解services中最常用的配置项,包括镜像、构建、命令、环境变量、端口、数据卷、网络和依赖关系等。

3.1 image: 指定镜像

image配置项用于指定服务使用的Docker镜像。如果镜像在本地不存在,Compose会尝试从远程仓库拉取。

yaml 复制代码
services:
  # 方式1: 指定完整镜像名
  web:
    image: nginx:1.25-alpine

  # 方式2: 使用latest标签(不推荐生产环境)
  cache:
    image: redis:latest

  # 方式3: 使用完整仓库地址
  private-app:
    image: registry.example.com:5000/myapp:v2.1

  # 方式4: 使用镜像摘要(最安全,确保不可变)
  database:
    image: postgres:16@sha256:3c2d6820e5e19e3d4b8e2a1a4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3

  # 方式5: 与build同时使用(为构建的镜像命名)
  api:
    build: ./api                    # 从Dockerfile构建
    image: myapp/api:latest         # 构建后的镜像名称
3.1.1 pull_policy - 镜像拉取策略
yaml 复制代码
services:
  web:
    image: nginx:1.25
    pull_policy: always             # 每次启动都拉取最新镜像
    # pull_policy: missing          # 仅本地不存在时拉取(默认)
    # pull_policy: never            # 从不拉取,只使用本地镜像
    # pull_policy: build            # 总是从Dockerfile构建
    # pull_policy: if_not_present   # 等同于missing
拉取策略 说明 适用场景
always 每次都拉取最新 开发环境,确保最新代码
missing 本地不存在时拉取 默认行为,生产环境推荐
never 从不拉取 离线环境
build 总是从Dockerfile构建 开发调试

3.2 build: 从Dockerfile构建

当服务需要从源代码构建镜像时,使用build配置项代替(或配合)image

3.2.1 基本用法
yaml 复制代码
services:
  # 方式1: 简写 - 指定构建上下文目录
  api:
    build: ./api                    # 在./api目录下查找Dockerfile

  # 方式2: 简写 - 指定Dockerfile路径
  api:
    build: ./api/Dockerfile.prod    # 使用指定的Dockerfile文件

  # 方式3: 完整配置
  api:
    build:
      context: ./api                # 构建上下文目录
      dockerfile: Dockerfile.prod   # Dockerfile文件名(相对于context)
3.2.2 build完整配置项
yaml 复制代码
services:
  api:
    build:
      # 构建上下文路径(包含Dockerfile和构建所需文件的目录)
      context: ./api
      # 也可以使用URL
      # context: https://github.com/user/repo.git#main:api

      # Dockerfile文件名(相对于context),默认为Dockerfile
      dockerfile: Dockerfile.prod

      # 构建参数(对应Dockerfile中的ARG指令)
      args:
        PYTHON_VERSION: "3.11"      # 键值对形式
        - NODE_VERSION=20           # 列表形式
        # 使用环境变量
        - GITHUB_TOKEN=${GITHUB_TOKEN}

      # 缓存来源镜像(加速构建)
      cache_from:
        - type: registry
          ref: myapp/api:cache
        - type: local
          src: /tmp/.build-cache

      # 构建目标阶段(对应Dockerfile中的FROM ... AS <target>)
      target: production

      # 额外标签(构建后同时打多个标签)
      tags:
        - myapp/api:latest
        - myapp/api:${TAG:-dev}
        - registry.example.com/myapp/api:latest

      # 构建时使用的平台
      platforms:
        - linux/amd64
        - linux/arm64

      # 构建标签
      labels:
        - "com.myapp.build-date=2024-01-15"
        - "com.myapp.version=1.0.0"

      # 构建网络模式
      network: host                 # 构建时使用的网络
      # network: none
      # network: default

      # 是否使用BuildKit(推荐true)
      # Compose v2默认使用BuildKit
3.2.3 build与image配合使用
yaml 复制代码
services:
  api:
    # build用于构建镜像,image用于为构建的镜像命名
    # 构建完成后,镜像会被标记为myapp/api:latest
    build:
      context: ./api
      dockerfile: Dockerfile
    image: myapp/api:latest

    # 如果镜像已存在,使用docker compose up不会重新构建
    # 需要使用 --build 标志强制重建:
    # docker compose up -d --build
3.2.4 多阶段构建示例
dockerfile 复制代码
# ./api/Dockerfile
# ==========================================
# 阶段1: 构建阶段
# ==========================================
FROM python:3.11-slim AS builder

# 设置构建参数
ARG PYTHON_VERSION=3.11
ENV PYTHONUNBUFFERED=1

WORKDIR /build

# 安装构建依赖
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# ==========================================
# 阶段2: 开发阶段
# ==========================================
FROM python:3.11-slim AS development

WORKDIR /app

# 从builder阶段复制安装的包
COPY --from=builder /root/.local /root/.local
COPY . .

ENV PATH=/root/.local/bin:$PATH
ENV FLASK_ENV=development

CMD ["python", "app.py"]

# ==========================================
# 阶段3: 生产阶段
# ==========================================
FROM python:3.11-slim AS production

WORKDIR /app

# 从builder阶段复制安装的包
COPY --from=builder /root/.local /root/.local
COPY . .

ENV PATH=/root/.local/bin:$PATH
ENV FLASK_ENV=production

# 安装gunicorn
RUN pip install --user gunicorn

# 使用非root用户运行
RUN useradd -m -u 1000 appuser
USER appuser

CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]
yaml 复制代码
# docker-compose.yml - 对应的Compose配置
services:
  api-dev:
    build:
      context: ./api
      target: development          # 构建到开发阶段
    image: myapp/api:dev
    ports:
      - "5000:5000"
    volumes:
      - ./api:/app                 # 开发模式挂载源代码
    environment:
      - FLASK_DEBUG=1

  api-prod:
    build:
      context: ./api
      target: production           # 构建到生产阶段
    image: myapp/api:prod
    ports:
      - "5000:5000"
    environment:
      - FLASK_DEBUG=0
    restart: always

3.3 command与entrypoint: 覆盖启动命令

3.3.1 command - 覆盖CMD

command配置项覆盖Dockerfile中的CMD指令,设置容器启动时的默认命令。

yaml 复制代码
services:
  # 方式1: 字符串形式(shell形式)
  web:
    image: nginx:1.25
    command: nginx -g "daemon off;"

  # 方式2: 列表形式(exec形式,推荐)
  web:
    image: nginx:1.25
    command: ["nginx", "-g", "daemon off;"]

  # 方式3: 多行命令
  api:
    image: myapp:latest
    command: >
      bash -c "
      python manage.py migrate &&
      python manage.py collectstatic --noinput &&
      gunicorn myapp.wsgi:application --bind 0.0.0.0:8000 --workers 4
      "

  # 方式4: 使用脚本文件
  api:
    image: myapp:latest
    command: ["/app/entrypoint.sh"]
    volumes:
      - ./entrypoint.sh:/app/entrypoint.sh:ro
3.3.2 entrypoint - 覆盖ENTRYPOINT

entrypoint配置项覆盖Dockerfile中的ENTRYPOINT指令。

yaml 复制代码
services:
  api:
    image: myapp:latest
    # 覆盖entrypoint
    entrypoint: ["/app/entrypoint.sh"]
    # command作为entrypoint的参数
    command: ["--config", "/app/config.yaml"]

  # entrypoint和command的配合关系:
  # 最终执行: /app/entrypoint.sh --config /app/config.yaml
3.3.3 command与entrypoint的关系
Dockerfile配置 Compose配置 实际执行结果
CMD "nginx" nginx
CMD "nginx" command: "redis" redis
ENTRYPOINT "app" app
ENTRYPOINT "app" command: "--debug" app --debug
ENTRYPOINT "app" CMD "--help" command: "--version" app --version
CMD "nginx" entrypoint: "app" app
ENTRYPOINT "app" entrypoint: "newapp" command: "--debug" newapp --debug
3.3.4 实际应用场景
yaml 复制代码
services:
  # 场景1: 启动前执行数据库迁移
  web:
    image: myapp:latest
    entrypoint: >
      bash -c "
      echo 'Waiting for database...' &&
      sleep 10 &&
      python manage.py migrate &&
      echo 'Starting server...' &&
      gunicorn myapp.wsgi:application --bind 0.0.0.0:8000
      "

  # 场景2: 使用启动脚本
  web:
    image: myapp:latest
    entrypoint: ["/docker-entrypoint.sh"]
    command: ["gunicorn", "myapp.wsgi:application", "--bind", "0.0.0.0:8000"]

  # 场景3: 覆盖MySQL的启动参数
  db:
    image: mysql:8.0
    command: >
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
      --max-connections=200
      --innodb-buffer-pool-size=1G

  # 场景4: 覆盖Redis的启动参数
  redis:
    image: redis:7-alpine
    command: >
      redis-server
      --maxmemory 256mb
      --maxmemory-policy allkeys-lru
      --appendonly yes
      --requirepass ${REDIS_PASSWORD}

3.4 environment与env_file: 环境变量配置

3.4.1 environment - 内联环境变量
yaml 复制代码
services:
  db:
    image: postgres:16
    # 方式1: 字典形式(推荐,更清晰)
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: secretpass
      POSTGRES_HOST_AUTH_METHOD: scram-sha-256

  api:
    image: myapp:latest
    # 方式2: 列表形式
    environment:
      - DEBUG=true
      - PORT=8080
      - DATABASE_URL=postgresql://appuser:secretpass@db:5432/myapp
      - REDIS_URL=redis://redis:6379/0
      # 引用变量(支持插值)
      - SECRET_KEY=${SECRET_KEY:-defaultsecret}
      - API_KEY=${API_KEY}
3.4.2 env_file - 环境变量文件

当环境变量较多或包含敏感信息时,使用env_file从文件加载:

bash 复制代码
# .env文件内容示例
DEBUG=true
PORT=8080
DATABASE_URL=postgresql://appuser:secretpass@db:5432/myapp
REDIS_URL=redis://redis:6379/0
SECRET_KEY=my-super-secret-key
API_KEY=abc123xyz

# 注意: .env文件中的值不需要引号
# 每行一个键值对,格式为 KEY=VALUE
# 以#开头的行被视为注释
# 空行会被忽略
yaml 复制代码
services:
  api:
    image: myapp:latest
    # 方式1: 引用单个文件
    env_file:
      - .env

    # 方式2: 引用多个文件(后面的文件覆盖前面的)
    env_file:
      - .env                    # 基础环境变量
      - .env.local              # 本地覆盖(开发环境专用)
      - .env.production         # 生产环境覆盖

    # 方式3: 详细配置形式
    env_file:
      - path: .env              # 文件路径
        required: true          # 文件必须存在(默认true)
      - path: .env.local        # 文件路径
        required: false         # 文件可选(不存在不报错)
3.4.3 environment与env_file优先级
yaml 复制代码
services:
  api:
    image: myapp:latest
    env_file:
      - .env                    # 文件中: DEBUG=true, PORT=8080
    environment:
      - DEBUG=false             # 内联覆盖文件中的值
      - NEW_VAR=newvalue        # 新增变量
    # 最终环境变量:
    # DEBUG=false (environment覆盖env_file)
    # PORT=8080 (来自env_file)
    # NEW_VAR=newvalue (来自environment)

优先级规则(从高到低):

  1. environment中的内联变量(最高优先级)
  2. env_file中最后引用的文件
  3. env_file中最先引用的文件
  4. Dockerfile中的ENV指令(最低优先级)
3.4.4 环境变量最佳实践
yaml 复制代码
services:
  api:
    image: myapp:latest
    environment:
      # 1. 非敏感配置直接写在compose文件中
      - APP_NAME=MyApp
      - APP_ENV=production
      - LOG_LEVEL=info

      # 2. 敏感配置使用变量插值,从.env文件读取
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0

      # 3. 使用默认值
      - WORKER_PROCESSES=${WORKER_PROCESSES:-4}
      - WORKER_TIMEOUT=${WORKER_TIMEOUT:-120}
    env_file:
      - path: .env
        required: true
      - path: .env.secrets      # 密码等敏感信息
        required: false
bash 复制代码
# .env文件(不提交到Git)
DB_USER=appuser
DB_PASSWORD=secretpass
DB_NAME=myapp
REDIS_PASSWORD=redispass

# .env.secrets文件(更敏感的数据)
JWT_SECRET=very-long-jwt-secret
API_KEY=production-api-key

3.5 ports: 端口映射

ports配置项用于将容器端口映射到主机端口,使外部可以访问容器服务。

3.5.1 短语法
yaml 复制代码
services:
  web:
    image: nginx:1.25
    ports:
      # 格式: "主机端口:容器端口"
      - "8080:80"               # 主机8080端口映射到容器80端口

      # 格式: "容器端口"(仅指定容器端口,主机随机分配)
      - "443"                   # 主机随机端口映射到容器443端口

      # 带协议
      - "8080:80/tcp"           # TCP协议(默认)
      - "5353:5353/udp"         # UDP协议

      # 带IP地址绑定
      - "127.0.0.1:8080:80"     # 仅本机可访问
      - "0.0.0.0:8080:80"       # 所有网络接口可访问(默认)

      # 端口范围
      - "3000-3005:3000-3005"   # 映射端口范围
3.5.2 长语法
yaml 复制代码
services:
  web:
    image: nginx:1.25
    ports:
      # 完整的长语法
      - target: 80              # 容器端口(必需)
        published: "8080"       # 主机端口(可选,不指定则随机)
        protocol: tcp           # 协议: tcp/udp(默认tcp)
        host_ip: 127.0.0.1      # 绑定的主机IP(可选)
        mode: host              # 模式: host/ingress(Swarm)
        # host: 直接映射到主机
        # ingress: Swarm集群模式

      # 仅暴露容器端口
      - target: 443
        protocol: tcp

      # UDP端口
      - target: 5353
        published: "5353"
        protocol: udp
        host_ip: 0.0.0.0
3.5.3 端口映射对比表
写法 说明 示例
"8080:80" 主机8080映射到容器80 最常用
"80" 随机主机端口映射到容器80 测试用
"127.0.0.1:8080:80" 仅本机访问 安全场景
"8080:80/tcp" 指定TCP协议 明确协议
"5353:5353/udp" UDP端口 DNS服务
"3000-3005:3000-3005" 端口范围 多端口服务
target: 80, published: 8080 长语法 复杂配置
3.5.4 查看端口映射
bash 复制代码
# 查看端口映射
docker compose ps

# 查看特定服务的端口
docker compose port web 80

# 输出示例:
# 0.0.0.0:8080

3.6 expose: 暴露端口(仅容器间)

exposeports不同,它只在容器间暴露端口,不会映射到主机。

yaml 复制代码
services:
  api:
    image: myapp:latest
    expose:
      - "5000"                   # 暴露5000端口(仅同网络容器可访问)
      - "5001"
    # expose不映射到主机,外部无法直接访问

  # 对比ports和expose
  nginx:
    image: nginx:1.25
    ports:
      - "80:80"                  # 映射到主机,外部可访问
    expose:
      - "9000"                   # 仅容器间可访问

  # api服务可以通过nginx:9000访问nginx的9000端口
  # 外部只能通过主机80端口访问nginx
3.6.1 ports vs expose 对比
对比项 ports expose
访问范围 主机和容器 仅同网络容器
主机端口 占用主机端口 不占用主机端口
安全性 需要配置防火墙 默认安全
典型场景 Web服务、API网关 数据库、内部服务
语法 短/长语法 仅短语法
yaml 复制代码
services:
  # 典型用法: 前端服务用ports,后端服务用expose
  nginx:
    image: nginx:1.25
    ports:
      - "80:80"
      - "443:443"
    networks:
      - frontend

  flask-app:
    image: myapp:latest
    expose:
      - "5000"
    networks:
      - frontend
      - backend

  postgres:
    image: postgres:16
    expose:
      - "5432"
    networks:
      - backend

  redis:
    image: redis:7
    expose:
      - "6379"
    networks:
      - backend

3.7 volumes: 数据卷挂载(短语法与长语法)

volumes配置项用于将数据持久化或共享文件/目录到容器中。

3.7.1 短语法
yaml 复制代码
services:
  db:
    image: postgres:16
    volumes:
      # 方式1: 命名数据卷
      - db-data:/var/lib/postgresql/data
      # db-data在顶层volumes中定义

      # 方式2: 主机路径绑定挂载
      - ./config/postgresql.conf:/etc/postgresql/postgresql.conf:ro
      # :ro表示只读挂载

      # 方式3: 匿名数据卷
      - /var/lib/postgresql/data
      # 不指定源,自动创建匿名卷

      # 方式4: 挂载主机目录
      - ./data:/app/data
      # 相对路径相对于compose文件所在目录

      # 方式5: 带权限的挂载
      - ./config:/app/config:ro    # 只读
      - ./logs:/app/logs:rw        # 读写(默认)
      - ./cache:/app/cache:delegated  # delegated模式(macOS性能优化)

      # 方式6: Windows路径
      # - C:\Users\data:/app/data
      # - /c/Users/data:/app/data  # 使用正斜杠
3.7.2 长语法
yaml 复制代码
services:
  db:
    image: postgres:16
    volumes:
      # 命名数据卷(长语法)
      - type: volume
        source: db-data
        target: /var/lib/postgresql/data
        read_only: false
        volume:
          nocopy: true            # 创建容器时不从容器复制数据到卷

      # 绑定挂载(长语法)
      - type: bind
        source: ./config/postgresql.conf
        target: /etc/postgresql/postgresql.conf
        read_only: true

      # tmpfs挂载(内存文件系统)
      - type: tmpfs
        target: /tmp
        tmpfs:
          size: 100000000         # 100MB
          mode: 1777              # 权限模式

      # 带一致性配置的绑定挂载
      - type: bind
        source: ./src
        target: /app/src
        consistency: delegated    # cached/delegated/consistent
3.7.3 挂载类型对比
挂载类型 说明 持久化 性能 适用场景
volume(命名卷) Docker管理的数据卷 数据库数据、应用数据
bind(绑定挂载) 直接挂载主机目录 中(Windows/Mac低) 配置文件、源代码
tmpfs 内存文件系统 最高 临时文件、缓存
匿名卷 无名称的数据卷 容器内临时数据
3.7.4 volumes实际应用
yaml 复制代码
services:
  web:
    image: nginx:1.25
    volumes:
      # 1. 挂载配置文件
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      # 2. 挂载SSL证书
      - ./nginx/ssl:/etc/nginx/ssl:ro
      # 3. 持久化日志
      - nginx-logs:/var/log/nginx
      # 4. 静态文件
      - static-files:/usr/share/nginx/html:ro

  api:
    image: myapp:latest
    volumes:
      # 1. 开发模式: 挂载源代码实现热重载
      - ./src:/app/src
      # 2. 挂载配置文件
      - ./config/app.yaml:/app/config.yaml:ro
      # 3. 持久化上传文件
      - app-uploads:/app/uploads
      # 4. 持久化日志
      - app-logs:/app/logs
      # 5. tmpfs用于临时文件
      - type: tmpfs
        target: /tmp

volumes:
  nginx-logs:
  static-files:
  app-uploads:
  app-logs:

3.8 networks: 网络配置

在服务级别,networks配置项用于指定该服务加入的网络。

3.8.1 基本用法
yaml 复制代码
services:
  web:
    image: nginx:1.25
    networks:
      # 方式1: 加入单个网络
      - frontend

      # 方式2: 加入多个网络
      - frontend
      - backend

      # 方式3: 带别名
      - frontend:
          aliases:
            - web-server
            - nginx-proxy

      # 方式4: 带IPv4地址
      - custom-net:
          ipv4_address: 172.20.0.10

      # 方式5: 带优先级
      - frontend:
          priority: 100
      - backend:
          priority: 50

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true
  custom-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16
3.8.2 网络别名与服务发现
yaml 复制代码
services:
  # 通过网络别名,其他服务可以通过别名访问
  web:
    image: nginx:1.25
    networks:
      app-network:
        aliases:
          - nginx          # 其他容器可以用 nginx 访问
          - web-server     # 也可以用 web-server 访问
          - proxy          # 也可以用 proxy 访问

  # 默认情况下,其他容器可以通过服务名 "api" 访问
  api:
    image: myapp:latest
    networks:
      - app-network
    # 其他容器可以用以下名称访问:
    # - api (服务名,自动注册)
    # 不需要额外配置,Compose会自动为服务名创建DNS记录

networks:
  app-network:
    driver: bridge
3.8.3 网络隔离示例
yaml 复制代码
services:
  # 前端服务 - 可被外部访问
  nginx:
    image: nginx:1.25
    ports:
      - "80:80"
    networks:
      - frontend              # 只在前端网络
    depends_on:
      - flask-app

  # 应用服务 - 桥接前后端
  flask-app:
    image: myapp:latest
    networks:
      - frontend              # 前端网络(接收nginx请求)
      - backend               # 后端网络(访问数据库)
    # flask-app同时在两个网络中,起到桥梁作用

  # 数据库服务 - 仅后端可访问
  postgres:
    image: postgres:16
    networks:
      - backend               # 只在后端网络
    # 外部和前端服务都无法直接访问postgres

  # 缓存服务 - 仅后端可访问
  redis:
    image: redis:7
    networks:
      - backend               # 只在后端网络

networks:
  frontend:
    driver: bridge
    # 前端网络,可访问外部
  backend:
    driver: bridge
    internal: true             # 内部网络,完全隔离

3.9 depends_on: 服务依赖与启动顺序(含condition条件)

depends_on配置项用于定义服务之间的依赖关系,控制启动和停止顺序。

3.9.1 基本用法
yaml 复制代码
services:
  web:
    image: nginx:1.25
    depends_on:
      - api                   # web依赖api,api先启动
    # 启动顺序: api -> web
    # 停止顺序: web -> api

  api:
    image: myapp:latest
    depends_on:
      - db                    # api依赖db,db先启动
      - redis                 # api依赖redis,redis先启动
    # 启动顺序: db,redis -> api

  db:
    image: postgres:16

  redis:
    image: redis:7
3.9.2 条件依赖(condition)

基本的depends_on只保证依赖服务先启动(容器创建),但不保证服务已就绪。使用condition可以更精确地控制:

yaml 复制代码
services:
  api:
    image: myapp:latest
    depends_on:
      db:
        condition: service_healthy    # 等待db健康检查通过
      redis:
        condition: service_started    # 等待redis启动(默认)
      migration:
        condition: service_completed_successfully  # 等待migration服务成功退出
    # 支持的condition:
    # service_started: 依赖服务启动即继续(默认)
    # service_healthy: 依赖服务健康检查通过才继续
    # service_completed_successfully: 依赖服务成功退出才继续

  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s

  redis:
    image: redis:7
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  # 一次性迁移服务
  migration:
    image: myapp:latest
    command: python manage.py migrate
    depends_on:
      db:
        condition: service_healthy
    restart: "no"                    # 不需要重启
3.9.3 depends_on的局限性
yaml 复制代码
# depends_on只控制启动顺序,不保证服务真的就绪
# 例如: MySQL容器启动了,但MySQL服务可能还在初始化

# 问题示例:
services:
  api:
    image: myapp:latest
    depends_on:
      - mysql                    # mysql容器启动了,但MySQL可能还没准备好
    # 如果api立即连接mysql,可能会失败

  mysql:
    image: mysql:8.0
    # 没有healthcheck,depends_on只保证容器启动

# 正确做法: 配合healthcheck
services:
  api:
    image: myapp:latest
    depends_on:
      mysql:
        condition: service_healthy    # 等待MySQL健康检查通过
    # 这样api启动时,MySQL一定已经准备好了

  mysql:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
      interval: 5s
      timeout: 5s
      retries: 20
      start_period: 30s
3.9.4 完整的依赖管理示例
yaml 复制代码
services:
  # 第一层: 基础服务
  postgres:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    restart: unless-stopped

  # 第二层: 数据库迁移(一次性任务)
  migrate:
    image: myapp:latest
    command: python manage.py migrate --no-input
    depends_on:
      postgres:
        condition: service_healthy
    restart: "no"
    # 这个服务执行完迁移后退出

  # 第三层: 应用服务
  api:
    image: myapp:latest
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s
    restart: unless-stopped

  # 第四层: 前端代理
  nginx:
    image: nginx:1.25
    depends_on:
      api:
        condition: service_healthy
    ports:
      - "80:80"
    restart: unless-stopped

第四章 Services配置详解(下)

本章继续讲解services中的高级配置项,包括重启策略、健康检查、部署配置、日志、安全配置等。

4.1 restart: 重启策略

restart配置项定义容器退出时的重启行为。

yaml 复制代码
services:
  # 策略1: 不重启(默认)
  one-shot:
    image: alpine
    command: echo "hello"
    restart: "no"                  # 注意: "no"必须加引号(YAML中no是布尔值false)
    # 容器退出后不重启

  # 策略2: 总是重启
  web:
    image: nginx:1.25
    restart: always
    # 无论退出码如何,总是重启
    # 适合核心服务

  # 策略3: 除非手动停止,否则总是重启
  api:
    image: myapp:latest
    restart: unless-stopped
    # 容器异常退出时重启
    # 手动停止(docker stop)后不会重启
    # 适合大多数场景(推荐)

  # 策略4: 仅在非零退出码时重启
  worker:
    image: celery:latest
    restart: on-failure
    # 只有退出码非0时才重启

  # 策略5: 带最大重试次数
  worker:
    image: celery:latest
    restart: on-failure:5
    # 非零退出码重启,最多重试5次
4.1.1 重启策略对比
策略 说明 手动停止后 正常退出(0) 异常退出(非0) 适用场景
no 不重启 不重启 不重启 不重启 一次性任务
always 总是重启 不重启 重启 重启 核心服务
unless-stopped 除非手动停止 不重启 重启 重启 推荐使用
on-failure 失败时重启 不重启 不重启 重启 工作进程
4.1.2 restart与depends_on的关系
yaml 复制代码
services:
  api:
    image: myapp:latest
    restart: unless-stopped
    depends_on:
      - db
    # 如果db重启,api不会自动重启
    # 如果api崩溃,会自动重启
    # depends_on只在首次启动时控制顺序

  # 如果需要依赖服务重启时也重启,需要使用healthcheck + depends_on condition
  api:
    image: myapp:latest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy

4.2 healthcheck: 健康检查

healthcheck配置项用于定义容器的健康检查机制,让Docker知道容器是否真正"健康"运行。

4.2.1 基本配置
yaml 复制代码
services:
  api:
    image: myapp:latest
    healthcheck:
      # 检查命令(必须返回0为健康,非0为不健康)
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]

      # 检查间隔(默认30s)
      interval: 30s

      # 超时时间(默认30s)
      timeout: 10s

      # 重试次数(默认3),连续失败这么多次才标记为unhealthy
      retries: 3

      # 启动宽限期(默认0s),在此期间失败不计入重试
      start_period: 40s

      # 开始间隔(Compose Specification特有)
      # 在start_period之后,正常interval之前的检查间隔
      start_interval: 5s
4.2.2 test的几种写法
yaml 复制代码
services:
  # 方式1: CMD形式(exec形式,推荐)
  api:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      # 直接执行命令,不需要shell

  # 方式2: CMD-SHELL形式(shell形式)
  api:
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:5000/health || exit 1"]
      # 通过shell执行,可以使用管道、逻辑运算等

  # 方式3: 字符串形式
  api:
    healthcheck:
      test: curl -f http://localhost:5000/health || exit 1

  # 方式4: 禁用健康检查
  # 如果镜像中定义了HEALTHCHECK,可以用NONE禁用
  api:
    healthcheck:
      test: ["NONE"]
      # 或者
      disable: true
4.2.3 常见服务的健康检查
yaml 复制代码
services:
  # PostgreSQL健康检查
  postgres:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  # MySQL健康检查
  mysql:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$${MYSQL_ROOT_PASSWORD}"]
      # 注意: $$转义为$,避免Compose变量插值
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s

  # Redis健康检查
  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      # 如果有密码: test: ["CMD", "redis-cli", "-a", "$$REDIS_PASSWORD", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

  # MongoDB健康检查
  mongodb:
    image: mongo:7
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  # Elasticsearch健康检查
  elasticsearch:
    image: elasticsearch:8.11.0
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:9200/_cluster/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  # Web应用健康检查
  web:
    image: nginx:1.25
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost/"]
      # 或者使用curl:
      # test: ["CMD", "curl", "-f", "http://localhost/"]
      # 注意: alpine镜像可能没有curl,使用wget
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

  # 自定义应用健康检查(Python)
  flask-app:
    image: myapp:latest
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')"]
      # 不依赖curl/wget的健康检查方式
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
4.2.4 健康检查状态
bash 复制代码
# 查看容器健康状态
docker compose ps
# STATUS列显示: Up (healthy) / Up (unhealthy) / Up (health: starting)

# 查看健康检查日志
docker inspect --format='{{json .State.Health}}' <container_name> | jq

# 健康状态转换:
# starting -> healthy (首次检查通过)
# starting -> unhealthy (start_period内检查失败超过retries次)
# healthy -> unhealthy (正常运行后检查失败超过retries次)
# unhealthy -> healthy (恢复后检查通过)

4.3 deploy: 部署配置(仅Swarm模式)

deploy配置项主要用于Docker Swarm模式,定义服务在集群中的部署方式。在纯Compose(非Swarm)模式中,大部分配置会被忽略,但resourceslimits在Compose v2中也会生效。

4.3.1 完整deploy配置
yaml 复制代码
services:
  api:
    image: myapp:latest
    deploy:
      # 副本数(Swarm模式)
      replicas: 3

      # 资源限制(在Compose v2中也部分生效)
      resources:
        limits:
          cpus: "1.0"              # CPU限制(核心数)
          memory: 512M             # 内存限制
          pids: 100                # 进程数限制
        reservations:
          cpus: "0.5"              # CPU预留
          memory: 256M             # 内存预留
          devices:
            - driver: nvidia       # GPU预留
              count: 1
              capabilities: [gpu]

      # 更新配置(滚动更新)
      update_config:
        parallelism: 2             # 每次更新2个副本
        delay: 10s                 # 更新间隔
        failure_action: rollback   # 失败时回滚
        monitor: 10s               # 监控时间
        max_failure_ratio: 0.3     # 最大失败率30%
        order: start-first         # 更新顺序: start-first/stop-first

      # 回滚配置
      rollback_config:
        parallelism: 0             # 0表示同时回滚所有
        delay: 0s
        failure_action: continue
        monitor: 10s
        order: stop-first

      # 重启策略(Swarm模式)
      restart_policy:
        condition: on-failure      # any/none/on-failure
        delay: 5s                  # 重启延迟
        max_attempts: 3            # 最大重试次数
        window: 120s               # 判断成功的时间窗口

      # 调度约束
      placement:
        constraints:
          - node.role == manager           # 只在管理节点
          - node.labels.env == production  # 在有特定标签的节点
          - node.platform.os == linux      # 操作系统约束
        preferences:
          - spread: node.labels.zone       # 按zone分散
          - spread: node.labels.datacenter # 按数据中心分散
        max_replicas_per_node: 1   # 每个节点最多1个副本

      # 端点模式
      endpoint_mode: vip           # vip/dnsrr
      # vip: 虚拟IP(默认,负载均衡)
      # dnsrr: DNS轮询

      # 模式
      mode: replicated             # replicated/global
      # replicated: 指定副本数
      # global: 每个节点一个副本

      # 标签
      labels:
        - "com.myapp.service=api"
        - "com.myapp.tier=backend"
4.3.2 Compose模式中的资源限制

在Compose v2(非Swarm)中,deploy.resourceslimits会生效:

yaml 复制代码
services:
  api:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: "1.0"              # 在Compose中也生效
          memory: 512M             # 在Compose中也生效
        reservations:
          cpus: "0.25"            # 在Compose中也生效
          memory: 128M

  # 等效的旧式写法(不推荐,使用deploy替代)
  api-old:
    image: myapp:latest
    mem_limit: 512m
    mem_reservation: 128m
    cpus: 1.0
    cpu_count: 1
4.3.3 resources单位说明
yaml 复制代码
deploy:
  resources:
    limits:
      # CPU - 支持小数(核心数)
      cpus: "0.5"        # 0.5个CPU核心
      cpus: "1.0"        # 1个CPU核心
      cpus: "2.5"        # 2.5个CPU核心

      # 内存 - 支持多种单位
      memory: 256M       # 256MB
      memory: 1G         # 1GB
      memory: 1024M      # 等同于1G
      memory: 104857600  # 字节(100MB)
单位 说明 字节数
b 字节 1
k/K 千字节 1,024
m/M 兆字节 1,048,576
g/G 吉字节 1,073,741,824

4.4 logging: 日志配置

logging配置项用于设置容器的日志驱动和日志选项。

yaml 复制代码
services:
  api:
    image: myapp:latest
    logging:
      # 日志驱动
      driver: json-file          # 默认驱动
      # 其他驱动:
      # - syslog: 发送到syslog
      # - journald: 发送到journald
      # - fluentd: 发送到Fluentd
      # - gelf: 发送到Graylog/Logstash
      # - awslogs: 发送到AWS CloudWatch
      # - gcplogs: 发送到Google Cloud Logging
      # - none: 禁用日志

      # 日志选项(json-file驱动)
      options:
        max-size: "10m"          # 单个日志文件最大10MB
        max-file: "3"            # 最多保留3个日志文件
        labels: "production"     # 添加标签到日志
        env: "os,customer"       # 添加环境变量到日志
        compress: "true"         # 压缩旧日志文件
4.4.1 常用日志驱动配置
yaml 复制代码
services:
  # 1. json-file(默认,推荐单机使用)
  api:
    image: myapp:latest
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"

  # 2. fluentd(推荐集中日志收集)
  api:
    image: myapp:latest
    logging:
      driver: fluentd
      options:
        fluentd-address: localhost:24224
        fluentd-async-connect: "true"
        tag: myapp.api

  # 3. syslog
  api:
    image: myapp:latest
    logging:
      driver: syslog
      options:
        syslog-address: "tcp://192.168.1.100:514"
        syslog-facility: "daemon"
        tag: "myapp-api"

  # 4. gelf(Graylog/Logstash)
  api:
    image: myapp:latest
    logging:
      driver: gelf
      options:
        gelf-address: "udp://localhost:12201"
        tag: "myapp-api"

  # 5. awslogs(AWS CloudWatch)
  api:
    image: myapp:latest
    logging:
      driver: awslogs
      options:
        awslogs-region: "us-east-1"
        awslogs-group: "myapp-logs"
        awslogs-stream-prefix: "api"

  # 6. none(禁用日志)
  debug:
    image: myapp:latest
    logging:
      driver: none
4.4.2 日志配置最佳实践
yaml 复制代码
# 统一日志配置(使用YAML锚点复用)
x-logging: &default-logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "5"
    compress: "true"

services:
  api:
    image: myapp:latest
    logging: *default-logging

  web:
    image: nginx:1.25
    logging: *default-logging

  worker:
    image: celery:latest
    logging: *default-logging
bash 复制代码
# 查看容器日志
docker compose logs                 # 查看所有服务日志
docker compose logs api             # 查看api服务日志
docker compose logs -f api          # 实时跟踪日志
docker compose logs --tail 100 api  # 最后100行
docker compose logs --since 30m     # 最近30分钟
docker compose logs -t              # 显示时间戳

4.5 labels与container_name

4.5.1 labels - 容器标签
yaml 复制代码
services:
  api:
    image: myapp:latest
    # 方式1: 列表形式
    labels:
      - "com.myapp.role=api"
      - "com.myapp.version=1.0.0"
      - "com.myapp.environment=production"
      - "maintainer=dev-team@company.com"

    # 方式2: 字典形式
    labels:
      com.myapp.role: "api"
      com.myapp.version: "1.0.0"
      com.myapp.environment: "production"
      maintainer: "dev-team@company.com"

标签的用途:

  • 组织和管理容器
  • 与监控系统集成
  • 与编排系统集成
  • 自动化脚本识别
bash 复制代码
# 通过标签筛选容器
docker ps --filter "label=com.myapp.role=api"

# 查看容器标签
docker inspect --format='{{json .Config.Labels}}' <container_name>
4.5.2 container_name - 容器名称
yaml 复制代码
services:
  api:
    image: myapp:latest
    container_name: my-api-server
    # 指定固定的容器名称
    # 注意: 使用container_name会限制scale能力(不能有多个同名容器)

  # 不推荐在需要扩缩容的服务上使用container_name
  # 不指定时,Compose自动生成: <project>_<service>_<index>
  # 例如: myapp_api_1
yaml 复制代码
# container_name的适用场景
services:
  # 适合: 单实例服务
  nginx:
    image: nginx:1.25
    container_name: nginx-proxy       # 单实例,可以固定名称

  postgres:
    image: postgres:16
    container_name: postgres-db       # 单实例,可以固定名称

  # 不适合: 需要扩缩容的服务
  # 以下配置如果用--scale 3会出错(同名冲突)
  worker:
    image: celery:latest
    # 不指定container_name,允许 --scale worker=3
    # 容器名: myapp_worker_1, myapp_worker_2, myapp_worker_3

4.6 user, working_dir, hostname, domainname

yaml 复制代码
services:
  api:
    image: myapp:latest

    # user: 指定运行用户
    user: "1000:1000"              # UID:GID
    # user: "1000"                 # 仅UID
    # user: "appuser"              # 用户名(需要在镜像中存在)
    # user: "appuser:appgroup"     # 用户名:组名

    # working_dir: 工作目录
    working_dir: /app              # 相当于Dockerfile中的WORKDIR
    # 容器启动后的当前目录

    # hostname: 容器主机名
    hostname: api-server           # 容器内的主机名
    # 影响容器内的hostname命令输出

    # domainname: 域名
    domainname: example.com        # 容器的域名
    # 影响容器内的domainname命令输出

    # 实际示例
    command: >
      bash -c "
      echo 'Running as user: $$(whoami)' &&
      echo 'Working directory: $$(pwd)' &&
      echo 'Hostname: $$(hostname)' &&
      echo 'Domain: $$(domainname)' &&
      python app.py
      "
4.6.1 安全用户配置
yaml 复制代码
services:
  # 生产环境推荐使用非root用户运行
  api:
    image: myapp:latest
    user: "1000:1000"              # 使用特定的UID:GID
    working_dir: /app
    volumes:
      - app-data:/app/data
      - ./app:/app:ro

  # 如果镜像中没有创建用户,可以在command中创建
  api:
    image: node:20-alpine
    user: "node"                   # node镜像自带node用户
    working_dir: /home/node/app
    volumes:
      - ./src:/home/node/app
    command: node index.js

4.7 sysctls, ulimits, privileged, cap_add/cap_drop

4.7.1 sysctls - 内核参数
yaml 复制代码
services:
  redis:
    image: redis:7-alpine
    sysctls:
      # 方式1: 字典形式
      net.core.somaxconn: 1024
      net.ipv4.tcp_keepalive_time: 600
      # 方式2: 列表形式
      - net.ipv4.ip_local_port_range=10000 65535

  # 常用sysctls
  api:
    image: myapp:latest
    sysctls:
      net.core.somaxconn: 1024           # 连接队列长度
      net.ipv4.tcp_max_syn_backlog: 4096 # SYN队列
      net.ipv4.tcp_keepalive_time: 600   # keepalive时间
      net.ipv4.ip_local_port_range: "10000 65535"  # 本地端口范围
      fs.file-max: 65535                 # 最大文件描述符
4.7.2 ulimits - 资源限制
yaml 复制代码
services:
  api:
    image: myapp:latest
    ulimits:
      # 简写形式(同时设置soft和hard)
      nproc: 65535                  # 最大进程数
      nofile: 65535                 # 最大文件描述符数

      # 详细形式(分别设置soft和hard)
      nofile:
        soft: 20000
        hard: 40000
      nproc:
        soft: 10000
        hard: 20000
      memlock:
        soft: -1                    # 无限制
        hard: -1
      as:
        soft: -1                    # 地址空间无限制
        hard: -1
4.7.3 privileged - 特权模式
yaml 复制代码
services:
  # 特权模式(不推荐,安全风险大)
  debugger:
    image: myapp:latest
    privileged: true                # 容器获得几乎所有主机权限
    # 特权模式可以:
    # - 访问所有设备
    # - 加载内核模块
    # - 修改网络配置
    # - 执行几乎所有特权操作
    # 仅用于调试,不要在生产环境使用

  # 替代方案: 只添加必要的capabilities
  api:
    image: myapp:latest
    privileged: false               # 不使用特权模式(默认)
    cap_add:
      - NET_ADMIN                   # 网络管理权限
      - SYS_PTRACE                  # 进程跟踪权限
    # 只添加真正需要的权限
4.7.4 cap_add与cap_drop - 内核能力管理
yaml 复制代码
services:
  # 最小权限原则: 先删除所有能力,再添加需要的
  api:
    image: myapp:latest
    cap_drop:
      - ALL                         # 删除所有Linux能力
    cap_add:
      - NET_BIND_SERVICE            # 绑定1024以下端口
      - CHOWN                       # 修改文件所有者
      - SETUID                      # 设置用户ID
      - SETGID                      # 设置组ID

  # 常用Linux能力
  services:
    network-tool:
      image: myapp:latest
      cap_add:
        - NET_ADMIN                 # 网络配置(iptables等)
        - NET_RAW                   # 原始网络包(RAW socket)
        - NET_BROADCAST             # 广播
        - SYS_ADMIN                 # 系统管理(慎用)
        - SYS_PTRACE                # 进程跟踪
        - SYS_TIME                  # 设置系统时间
        - DAC_OVERRIDE              # 绕过文件权限检查
        - KILL                      # 发送信号
        - SETPCAP                   # 设置进程能力
        - AUDIT_WRITE               # 审计日志写入
能力 说明 安全等级
CHOWN 修改文件所有者
DAC_OVERRIDE 绕过文件权限
FOWNER 绕过文件所有者检查
KILL 发送信号
NET_BIND_SERVICE 绑定特权端口
NET_RAW 原始网络访问
SETUID 设置用户ID
SYS_ADMIN 系统管理 高(慎用)
SYS_PTRACE 进程跟踪
SYS_TIME 设置系统时间
4.7.5 security_opt - 安全选项
yaml 复制代码
services:
  api:
    image: myapp:latest
    security_opt:
      - no-new-privileges:true      # 禁止获取新权限
      - label:user:USER             # SELinux用户标签
      - label:role:ROLE             # SELinux角色标签
      - label:type:TYPE             # SELinux类型标签
      - label:level:LEVEL           # SELinux级别标签
      - apparmor:profile_name       # AppArmor配置文件
      - seccomp:unconfined          # Seccomp配置(不推荐)

4.8 secrets与configs: 敏感数据管理

4.8.1 secrets - 敏感数据
yaml 复制代码
services:
  db:
    image: postgres:16
    secrets:
      # 方式1: 短语法
      - db_password
      # 密码会挂载到 /run/secrets/db_password

      # 方式2: 长语法
      - source: db_password
        target: postgres_password   # 挂载路径改为 /run/secrets/postgres_password
        uid: "1000"                 # 文件所有者UID
        gid: "1000"                 # 文件所有者GID
        mode: 0400                  # 权限(仅所有者可读)

# 顶层secrets定义
secrets:
  # 方式1: 从文件读取
  db_password:
    file: ./secrets/db_password.txt

  # 方式2: 外部引用(需要预先创建)
  ssl_certificate:
    name: myapp_ssl_cert
    external: true

  # 方式3: 使用环境变量
  api_key:
    environment: API_KEY
bash 复制代码
# 创建外部secret
echo "my-super-secret-password" | docker secret create db_password -

# 在Swarm中使用
docker stack deploy -c docker-compose.yml myapp
4.8.2 configs - 配置文件
yaml 复制代码
services:
  nginx:
    image: nginx:1.25
    configs:
      # 方式1: 短语法
      - nginx_config
      # 配置会挂载到 /run/secrets/nginx_config (与secrets类似)

      # 方式2: 长语法
      - source: nginx_config
        target: /etc/nginx/nginx.conf   # 自定义挂载路径
        uid: "0"
        gid: "0"
        mode: 0444                      # 所有人可读

# 顶层configs定义
configs:
  # 方式1: 从文件读取
  nginx_config:
    file: ./nginx/nginx.conf

  # 方式2: 外部引用
  external_config:
    name: existing_config
    external: true

  # 方式3: 从内容创建(Compose Specification)
  app_config:
    content: |
      server:
        port: 8080
        host: 0.0.0.0
      database:
        url: postgresql://db:5432/myapp
4.8.3 secrets与configs对比
对比项 secrets configs
用途 敏感数据(密码、密钥) 非敏感配置
默认路径 /run/secrets/ /run/secrets/
权限 通常0400(仅所有者可读) 通常0444(所有人可读)
Swarm支持
Compose支持 文件挂载方式 文件挂载方式

4.9 profiles: 服务配置文件(条件启动)

profiles配置项允许你定义服务的启动条件,实现按需启动不同的服务组合。

yaml 复制代码
services:
  # 核心服务(总是启动)
  api:
    image: myapp:latest
    ports:
      - "8080:8080"
    # 没有profiles,总是启动

  db:
    image: postgres:16
    # 没有profiles,总是启动

  # 开发工具服务(仅开发环境启动)
  adminer:
    image: adminer:latest
    ports:
      - "8081:8080"
    profiles:
      - dev                      # 仅在dev profile激活时启动

  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"
    profiles:
      - dev

  # 监控服务(仅监控环境启动)
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    profiles:
      - monitoring

  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    profiles:
      - monitoring

  # 调试服务
  debug:
    image: nicolaka/netshoot
    command: sleep infinity
    profiles:
      - debug
4.9.1 使用profiles
bash 复制代码
# 1. 默认启动(只启动没有profiles的服务)
docker compose up -d
# 启动: api, db

# 2. 激活dev profile
docker compose --profile dev up -d
# 启动: api, db, adminer, mailhog

# 3. 激活monitoring profile
docker compose --profile monitoring up -d
# 启动: api, db, prometheus, grafana

# 4. 激活多个profiles
docker compose --profile dev --profile monitoring up -d
# 启动: api, db, adminer, mailhog, prometheus, grafana

# 5. 使用环境变量激活profile
export COMPOSE_PROFILES=dev,monitoring
docker compose up -d
# 等同于 --profile dev --profile monitoring

# 6. 启动特定服务(会自动激活其profile)
docker compose up adminer
# 会自动激活dev profile

# 7. 多profile服务
# 一个服务可以属于多个profile
# 当任一profile激活时,该服务都会启动
4.9.2 profiles的实际应用
yaml 复制代码
services:
  # ===== 核心服务(无profile,总是启动) =====
  api:
    image: myapp:latest

  db:
    image: postgres:16

  redis:
    image: redis:7

  # ===== 开发环境(dev profile) =====
  api-dev:
    image: myapp:latest
    command: python app.py --debug
    volumes:
      - ./src:/app/src
    profiles: [dev]
    # 替代生产api服务(使用docker compose --profile dev up --scale api=0 api-dev=1)

  phpmyadmin:
    image: phpmyadmin
    ports: ["8081:80"]
    profiles: [dev]

  # ===== 测试环境(test profile) =====
  test-runner:
    image: myapp:latest
    command: pytest
    profiles: [test]
    depends_on:
      - api
      - db

  selenium:
    image: selenium/standalone-chrome
    profiles: [test]

  # ===== 生产环境(prod profile) =====
  nginx:
    image: nginx:1.25
    ports: ["80:80", "443:443"]
    profiles: [prod]
    depends_on:
      - api

  # ===== 监控(monitoring profile) =====
  prometheus:
    image: prom/prometheus
    profiles: [monitoring]

  grafana:
    image: grafana/grafana
    profiles: [monitoring]

  # ===== 调试(debug profile) =====
  debug-tools:
    image: nicolaka/netshoot
    command: sleep infinity
    profiles: [debug]

4.10 extends: 继承其他服务配置

extends配置项允许一个服务继承另一个服务的配置,避免重复定义。

4.10.1 同文件继承
yaml 复制代码
services:
  # 基础服务模板
  base-service:
    image: myapp:latest
    environment:
      - TZ=Asia/Shanghai
      - LOG_LEVEL=info
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - app-network

  # 继承基础服务并扩展
  api:
    extends: base-service
    ports:
      - "8080:8080"
    environment:
      - APP_ROLE=api
    command: gunicorn app:app --bind 0.0.0.0:8080

  # 继承基础服务并扩展
  worker:
    extends: base-service
    environment:
      - APP_ROLE=worker
    command: celery -A app worker --loglevel=info

  # 继承基础服务并扩展
  scheduler:
    extends: base-service
    environment:
      - APP_ROLE=scheduler
    command: celery -A app beat --loglevel=info
4.10.2 跨文件继承
yaml 复制代码
# base.yml - 基础配置文件
services:
  base-web:
    image: nginx:1.25
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - frontend
yaml 复制代码
# compose.yaml - 主配置文件
services:
  web:
    extends:
      file: base.yml
      service: base-web
    # 继承后添加/覆盖配置
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    container_name: web-server

  web-ssl:
    extends:
      file: base.yml
      service: base-web
    ports:
      - "443:443"
    volumes:
      - ./nginx-ssl.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro

networks:
  frontend:
    driver: bridge
4.10.3 继承规则
yaml 复制代码
# extends的合并规则:
# 1. 简单值(字符串、数字): 子服务覆盖父服务
# 2. 列表: 子服务的列表完全替换父服务的列表
# 3. 字典: 递归合并(子服务的键覆盖父服务相同的键)
# 4. environment/env_file: 合并(子服务添加新键,覆盖相同键)

services:
  parent:
    image: base:latest
    environment:
      A: 1
      B: 2
    ports:
      - "80:80"
    labels:
      - "role=parent"

  child:
    extends: parent
    image: child:latest          # 覆盖image
    environment:
      B: 20                      # 覆盖B
      C: 3                       # 新增C
    ports:
      - "443:443"               # 完全覆盖(不是添加)
    labels:
      - "role=child"            # 完全覆盖

  # child最终配置:
  # image: child:latest
  # environment: {A: 1, B: 20, C: 3}  # 字典合并
  # ports: ["443:443"]                 # 列表覆盖
  # labels: ["role=child"]             # 列表覆盖

4.11 所有配置项速查表

以下是services中所有配置项的完整速查表:

配置项 类型 说明
image string 镜像名称
build string/object 构建配置
command string/list 覆盖CMD
entrypoint string/list 覆盖ENTRYPOINT
environment list/map 环境变量
env_file list/object 环境变量文件
ports list 端口映射
expose list 暴露端口(仅容器间)
volumes list 数据卷挂载
networks list/map 网络配置
depends_on list/map 服务依赖
restart string 重启策略
healthcheck map 健康检查
deploy map 部署配置(Swarm)
logging map 日志配置
labels list/map 容器标签
container_name string 容器名称
hostname string 主机名
domainname string 域名
user string 运行用户
working_dir string 工作目录
tty boolean 伪终端
stdin_open boolean 标准输入
privileged boolean 特权模式
cap_add list 添加内核能力
cap_drop list 删除内核能力
security_opt list 安全选项
read_only boolean 只读文件系统
sysctls list/map 内核参数
ulimits map 资源限制
secrets list/map 敏感数据
configs list/map 配置文件
profiles list 服务配置文件
extends map 继承配置
init boolean 使用init进程
stop_grace_period string 停止宽限期
stop_signal string 停止信号
pull_policy string 镜像拉取策略
tmpfs list/map tmpfs挂载
dns string/list DNS服务器
dns_search string/list DNS搜索域
extra_hosts list/map 主机名映射
ipc string IPC命名空间
mac_address string MAC地址
mem_limit string 内存限制(旧)
mem_reservation string 内存预留(旧)
cpus string CPU限制(旧)
cpu_count int CPU数量(旧)
cpu_percent int CPU百分比(旧)
cpu_shares int CPU权重(旧)
cpu_period int CPU周期(旧)
cpu_quota int CPU配额(旧)
cpuset string CPU绑定(旧)
blkio_config map 块IO配置
cgroup_parent string cgroup父级
device_cgroup_rules list 设备cgroup规则
devices list 设备映射
external_links list 外部链接(旧)
group_add list 添加用户组
isolation string 隔离技术
links list 链接(已废弃)
network_mode string 网络模式
pid string PID命名空间
platform string 目标平台
shm_size string 共享内存大小
storage_opt map 存储驱动选项
volumes_from list 从容器挂载(已废弃)

第五章 Docker Compose命令详解

本章将全面讲解Docker Compose的所有命令及其参数,帮助你熟练操作Compose项目。

5.1 docker compose up(启动)

docker compose up是Docker Compose最核心的命令,用于创建并启动Compose文件中定义的所有服务。

5.1.1 基本用法
bash 复制代码
# 基本启动(前台运行,显示所有日志)
docker compose up

# 后台运行(推荐生产使用)
docker compose up -d
# -d / --detach: 后台运行容器

# 启动特定服务(及其依赖)
docker compose up web
docker compose up web db

# 启动所有服务
docker compose up
5.1.2 完整参数详解
bash 复制代码
docker compose up [OPTIONS] [SERVICE...]

# ===== 常用参数 =====
-d, --detach                  # 后台运行,打印容器名称
--build                       # 启动前重新构建镜像
--no-build                    # 不构建镜像,即使不存在
--pull always|missing|never   # 拉取策略(默认missing)
--no-deps                     # 不启动依赖服务
--force-recreate              # 强制重建容器
--no-recreate                 # 如果容器已存在,不重建
--renew-anon-volumes          # 重建匿名卷
--no-start                    # 创建容器但不启动
--scale SERVICE=NUM           # 设置服务副本数

# ===== 日志相关 =====
--abort-on-container-exit     # 任一容器停止时停止所有容器
--exit-code-from SERVICE      # 返回指定服务的退出码
-t, --timeout TIMEOUT         # 超时时间(秒,默认10)
--wait                        # 等待服务健康
--wait-timeout TIMEOUT        # 等待健康的超时时间
--wait-dependencies           # 等待依赖健康

# ===== 配置文件相关 =====
-f, --file FILE               # 指定Compose文件(可多个)
-p, --project-name NAME       # 指定项目名称
--profile NAME                # 激活profile(可多个)
--env-file FILE               # 指定env文件
--no-color                    # 不使用颜色输出
--ansi never|always|auto      # 控制ANSI输出
5.1.3 常用场景
bash 复制代码
# 场景1: 开发环境启动(前台显示日志)
docker compose up

# 场景2: 生产环境启动(后台运行)
docker compose up -d

# 场景3: 代码变更后重建
docker compose up -d --build

# 场景4: 强制重建所有容器
docker compose up -d --force-recreate

# 场景5: 扩容服务
docker compose up -d --scale worker=3

# 场景6: 启动并等待健康检查通过
docker compose up -d --wait

# 场景7: 使用特定Compose文件
docker compose -f docker-compose.prod.yml up -d

# 场景8: 使用多个Compose文件(后面的覆盖前面的)
docker compose -f docker-compose.yml -f docker-compose.override.yml up -d

# 场景9: 使用特定项目名
docker compose -p myproject up -d

# 场景10: 激活profile
docker compose --profile dev up -d

# 场景11: 启动后运行测试,测试完成后自动停止
docker compose up --abort-on-container-exit --exit-code-from test

# 场景12: 只启动指定服务,不启动依赖
docker compose up --no-deps web

# 场景13: 拉取最新镜像后启动
docker compose up -d --pull always
5.1.4 --scale 扩缩容示例
bash 复制代码
# 扩容worker到3个实例
docker compose up -d --scale worker=3

# 同时扩容多个服务
docker compose up -d --scale worker=3 --scale web=2

# 注意:
# 1. 扩容的服务不能有container_name(会导致命名冲突)
# 2. 扩容的服务不能有固定端口映射(会导致端口冲突)
# 3. 使用expose而非ports可以让多实例共存

# 示例compose文件(支持扩容):
services:
  web:
    image: nginx:1.25
    # 不设container_name
    # 不设固定端口(使用expose)
    expose:
      - "80"
    deploy:
      replicas: 2        # 也可以用deploy指定默认副本数

  worker:
    image: celery:latest
    # 不设container_name
    # 工作进程不需要端口

5.2 docker compose down(停止删除)

docker compose down用于停止并删除容器、网络和默认网络。

5.2.1 基本用法
bash 复制代码
# 停止并删除所有容器和网络
docker compose down

# 同时删除数据卷(慎用!)
docker compose down -v
# -v / --volumes: 删除compose文件中定义的命名数据卷

# 同时删除镜像
docker compose down --rmi all
# --rmi all: 删除所有服务使用的镜像
# --rmi local: 只删除自定义构建的镜像(没有tag的)

# 删除匿名卷
docker compose down --remove-orphans
# --remove-orphans: 删除compose文件中未定义但属于本项目的容器

# 指定超时时间
docker compose down -t 30
# -t / --timeout: 超时时间(秒,默认10)
5.2.2 down命令的行为详解
bash 复制代码
# down命令会执行以下操作:
# 1. 停止所有容器(发送SIGTERM,等待超时后发送SIGKILL)
# 2. 删除容器
# 3. 删除默认网络
# 4. 如果加-v,删除命名数据卷
# 5. 如果加--rmi,删除镜像

# 不会删除的内容:
# - 外部网络(external: true)
# - 外部数据卷(external: true)
# - 绑定挂载的主机目录

# 示例: 彻底清理(开发环境)
docker compose down -v --remove-orphans --rmi local
5.2.3 down vs stop
bash 复制代码
# down: 停止 + 删除容器 + 删除网络
docker compose down
# 完全清理,下次up需要重新创建

# stop: 仅停止容器,不删除
docker compose stop
# 容器仍存在,下次start很快
# 数据和网络都保留

# 选择建议:
# 开发环境日常使用: stop/start(快速)
# 需要重新配置时: down/up(干净)
# 彻底清理时: down -v(删除数据)

5.3 docker compose start/stop/restart/pause/unpause

5.3.1 start - 启动已停止的容器
bash 复制代码
# 启动所有已停止的服务
docker compose start

# 启动特定服务
docker compose start web
docker compose start web db redis
5.3.2 stop - 停止容器(不删除)
bash 复制代码
# 停止所有服务
docker compose stop

# 停止特定服务
docker compose stop web

# 指定超时时间
docker compose stop -t 30
5.3.3 restart - 重启容器
bash 复制代码
# 重启所有服务
docker compose restart

# 重启特定服务
docker compose restart api

# 重启前发送信号
docker compose restart -t 30 api
5.3.4 pause/unpause - 暂停/恢复容器
bash 复制代码
# 暂停容器(冻结进程,不停止)
docker compose pause
docker compose pause web

# 恢复暂停的容器
docker compose unpause
docker compose unpause web

# pause vs stop:
# pause: 冻结容器内所有进程(SIGSTOP),内存不释放
# stop: 发送SIGTERM终止进程,释放内存
# pause适合临时暂停,stop适合完全停止

5.4 docker compose ps/logs/top

5.4.1 ps - 查看容器状态
bash 复制代码
# 查看所有服务状态
docker compose ps

# 只显示正在运行的容器
docker compose ps --services
# 列出所有服务名称

# 查看特定服务
docker compose ps web

# 显示所有容器(包括已停止的)
docker compose ps -a
docker compose ps --all

# 以特定格式输出
docker compose ps --format json
docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"

# 显示服务过滤器
docker compose ps --filter "status=running"
docker compose ps --filter "status=exited"

# 输出示例:
# NAME                IMAGE               COMMAND                  STATUS              PORTS
# myapp-web-1         nginx:1.25          "nginx -g 'daemon off;" Up 2 minutes        0.0.0.0:80->80/tcp
# myapp-api-1         myapp:latest        "gunicorn app:app ..."   Up 2 minutes (healthy) 
# myapp-db-1          postgres:16         "docker-entrypoint.s..." Up 2 minutes (healthy)
5.4.2 logs - 查看日志
bash 复制代码
# 查看所有服务日志
docker compose logs

# 查看特定服务日志
docker compose logs web
docker compose logs web db

# 实时跟踪日志(类似tail -f)
docker compose logs -f
docker compose logs -f web

# 显示最后N行
docker compose logs --tail 100
docker compose logs --tail 50 web

# 显示指定时间后的日志
docker compose logs --since 30m          # 最近30分钟
docker compose logs --since 2024-01-15T10:00:00
docker compose logs --until 1h           # 1小时前的日志

# 显示时间戳
docker compose logs -t
docker compose logs --timestamps

# 不使用颜色
docker compose logs --no-color

# 只显示特定服务的日志
docker compose logs web api

# 组合使用
docker compose logs -f --tail 50 --since 10m web
5.4.3 top - 查看容器进程
bash 复制代码
# 查看所有容器中运行的进程
docker compose top

# 查看特定服务的进程
docker compose top web

# 输出示例:
# myapp-web-1
# UID    PID    PPID   C   STIME   TTY   TIME       CMD
# root   1      0      0   10:30   ?     00:00:00   nginx: master process nginx -g daemon off;
# nginx  31     1      0   10:30   ?     00:00:00   nginx: worker process

5.5 docker compose exec/run

5.5.1 exec - 在运行中的容器执行命令
bash 复制代码
# 在运行中的容器执行命令
docker compose exec web ls /etc/nginx

# 进入容器的shell
docker compose exec web sh
docker compose exec web bash

# 以特定用户执行
docker compose exec --user root web sh

# 设置环境变量
docker compose exec -e DEBUG=true web python manage.py shell

# 不分配TTY
docker compose exec -T web cat /etc/nginx/nginx.conf

# 指定工作目录
docker compose exec -w /app api python script.py

# 多个索引(如果服务有多个副本)
docker compose exec --index 2 web sh
bash 复制代码
# 常见使用场景:

# 1. 进入数据库CLI
docker compose exec db psql -U appuser -d myapp
docker compose exec db mysql -u root -p myapp

# 2. 进入Redis CLI
docker compose exec redis redis-cli

# 3. 执行数据库迁移
docker compose exec api python manage.py migrate

# 4. 创建管理员用户
docker compose exec api python manage.py createsuperuser

# 5. 查看容器内文件
docker compose exec web cat /etc/nginx/nginx.conf

# 6. 在容器内安装包(临时)
docker compose exec api pip install debugpy

# 7. 查看网络配置
docker compose exec web ip addr
docker compose exec web cat /etc/hosts
5.5.2 run - 创建新容器执行命令
bash 复制代码
# 创建新容器执行命令(不使用正在运行的容器)
docker compose run web echo "hello"

# 与exec的区别:
# exec: 在已运行的容器中执行
# run: 创建新的容器执行(类似docker run)

# 常用参数
docker compose run --rm web psql -h db -U appuser myapp
# --rm: 执行完后自动删除容器

docker compose run --no-deps web python script.py
# --no-deps: 不启动依赖服务

docker compose run -d web sleep 3600
# -d: 后台运行

docker compose run --name mytask web python manage.py mytask
# --name: 指定容器名

docker compose run -e DEBUG=true web python manage.py test
# -e: 设置环境变量

docker compose run --entrypoint sh web -c "echo hello"
# --entrypoint: 覆盖entrypoint

# 常见使用场景:

# 1. 运行一次性任务
docker compose run --rm api python manage.py flush

# 2. 运行测试
docker compose run --rm test pytest -v

# 3. 执行数据库备份
docker compose run --rm db pg_dump -U appuser myapp > backup.sql

# 4. Django管理命令
docker compose run --rm api python manage.py makemigrations
docker compose run --rm api python manage.py shell

# 5. npm命令
docker compose run --rm frontend npm install
docker compose run --rm frontend npm run build
5.5.3 exec vs run 对比
对比项 exec run
容器状态 在运行中的容器执行 创建新容器执行
依赖服务 不影响 默认会启动依赖
端口映射 使用已有容器 默认不映射端口(除非--service-ports)
资源消耗 低(复用容器) 高(创建新容器)
适用场景 调试、查看、管理 一次性任务、测试
自动清理 不需要 建议使用--rm

5.6 docker compose build/pull/push

5.6.1 build - 构建镜像
bash 复制代码
# 构建所有有build配置的服务
docker compose build

# 构建特定服务
docker compose build api

# 不使用缓存
docker compose build --no-cache

# 使用BuildKit(默认)
docker compose build --progress plain

# 构建并打标签
docker compose build --tag myapp/api:v1.0

# 指定构建参数
docker compose build --build-arg PYTHON_VERSION=3.11

# 指定内存限制
docker compose build --memory 2g

# 并行构建
docker compose build --parallel

# 构建时拉取基础镜像
docker compose build --pull
5.6.2 pull - 拉取镜像
bash 复制代码
# 拉取所有服务的镜像
docker compose pull

# 拉取特定服务
docker compose pull web db

# 拉取时包含删除本地不存在的镜像
docker compose pull --include-deps

# 忽略拉取失败
docker compose pull --ignore-pull-failures

# 静默模式
docker compose pull --quiet
5.6.3 push - 推送镜像
bash 复制代码
# 推送所有服务的镜像
docker compose push

# 推送特定服务
docker compose push api

# 忽略推送失败
docker compose push --ignore-push-failures

5.7 docker compose config(验证配置)

bash 复制代码
# 验证compose文件(检查语法错误)
docker compose config

# 验证并显示解析后的完整配置
docker compose config

# 只显示服务名称
docker compose config --services

# 只显示数据卷名称
docker compose config --volumes

# 只显示网络名称
docker compose config --networks

# 以特定格式输出
docker compose config --format json
docker compose config --format yaml

# 验证但不输出(静默)
docker compose config -q

# 解析变量(显示插值后的结果)
docker compose config
# 默认就会解析变量

# 输出镜像列表
docker compose config --images

# 实际用途:
# 1. 检查compose文件语法
docker compose config -q && echo "OK" || echo "ERROR"

# 2. 查看变量插值后的最终配置
docker compose config

# 3. 在CI/CD中验证配置
docker compose config -q

5.8 docker compose events

bash 复制代码
# 实时监听容器事件
docker compose events

# 以JSON格式输出
docker compose events --json

# 输出示例:
# {
#   "time": "2024-01-15T10:30:00.000000000Z",
#   "type": "container",
#   "action": "start",
#   "service": "web",
#   "attributes": {...}
# }

# 用于监控容器生命周期事件
# 事件类型: create, start, stop, die, kill, pause, unpause, restart, etc.

5.9 docker compose rm

bash 复制代码
# 删除已停止的容器
docker compose rm

# 删除特定服务的容器
docker compose rm web

# 强制删除(不确认)
docker compose rm -f
docker compose rm --force

# 删除数据卷
docker compose rm -v
docker compose rm --volumes

# 停止后删除
docker compose rm -s
docker compose rm --stop

# 组合使用
docker compose rm -s -f -v
# 停止所有服务,然后强制删除容器和数据卷
bash 复制代码
# rm vs down:
# rm: 只删除容器,不删除网络
# down: 删除容器+网络(可选删除数据卷)

# 使用场景:
# rm: 需要保留网络配置时
# down: 需要完全清理时

5.10 docker compose cp

bash 复制代码
# 从容器复制文件到主机
docker compose cp web:/etc/nginx/nginx.conf ./nginx.conf

# 从主机复制文件到容器
docker compose cp ./app.conf web:/etc/app/app.conf

# 复制目录
docker compose cp web:/var/log/nginx ./nginx-logs

# 指定服务实例
docker compose cp --index 1 web:/app/data ./data

5.11 每个命令的完整参数详解

5.11.1 全局参数(所有命令通用)
bash 复制代码
# 全局参数放在compose和子命令之间
docker compose [GLOBAL_OPTIONS] COMMAND [COMMAND_OPTIONS]

# 常用全局参数:
-f, --file FILE               # 指定compose文件(可多次使用)
-p, --project-name NAME       # 项目名称
--profile NAME                # 激活profile
--env-file FILE               # 指定env文件
--compatibility               # 兼容模式(v1兼容)
--no-ansi                     # 禁用ANSI控制字符
--ansi never|always|auto      # ANSI控制
--verbose                     # 详细输出
--log-level DEBUG|INFO|WARN|ERROR|FATAL  # 日志级别
--progress auto|quiet|plain   # 进度显示
--dry-run                     # 模拟运行(不实际执行)
5.11.2 其他有用命令
bash 复制代码
# docker compose ls - 列出所有Compose项目
docker compose ls
docker compose ls --all        # 包括非运行的项目

# docker compose images - 列出镜像
docker compose images
docker compose images web

# docker compose port - 查看端口映射
docker compose port web 80
docker compose port --protocol udp web 5353

# docker compose stats - 资源使用统计
docker compose stats
docker compose stats web

# docker compose version - 版本信息
docker compose version
docker compose version --short

# docker compose create - 创建容器(不启动)
docker compose create
docker compose create web

# docker compose attach - 连接到容器
docker compose attach web
5.11.3 命令速查表
命令 说明 常用参数
up 创建并启动 -d, --build, --scale
down 停止并删除 -v, --rmi, --remove-orphans
start 启动已停止的 [SERVICE]
stop 停止运行 -t, [SERVICE]
restart 重启 -t, [SERVICE]
pause 暂停 [SERVICE]
unpause 恢复暂停 [SERVICE]
ps 查看状态 -a, --format
logs 查看日志 -f, --tail, --since
top 查看进程 [SERVICE]
exec 执行命令 --user, -e, -T
run 新容器执行 --rm, --no-deps, -d
build 构建镜像 --no-cache, --build-arg
pull 拉取镜像 --ignore-pull-failures
push 推送镜像 --ignore-push-failures
config 验证配置 -q, --services, --format
events 监听事件 --json
rm 删除容器 -f, -v, -s
cp 复制文件 [SERVICE]:SRC DEST
ls 列出项目 --all
images 列出镜像 [SERVICE]
stats 资源统计 [SERVICE]

第六章 环境管理与多环境部署

本章讲解如何使用Docker Compose管理不同环境(开发、测试、生产)的配置,实现一套配置适配多个环境。

6.1 使用.env文件管理环境变量

6.1.1 .env文件的作用

.env文件是Docker Compose默认的环境变量文件。当你运行docker compose命令时,Compose会自动读取当前目录下的.env文件。

bash 复制代码
# .env文件示例
# ===== 基础配置 =====
PROJECT_NAME=myapp
ENVIRONMENT=development

# ===== 数据库配置 =====
DB_HOST=db
DB_PORT=5432
DB_NAME=myapp
DB_USER=appuser
DB_PASSWORD=devpassword123

# ===== Redis配置 =====
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=redispassword

# ===== 应用配置 =====
DEBUG=true
SECRET_KEY=dev-secret-key
ALLOWED_HOSTS=localhost,127.0.0.1

# ===== 端口配置 =====
WEB_PORT=8080
API_PORT=5000

# 注意:
# 1. 文件中不能有引号(值不需要引号)
# 2. 以#开头的是注释
# 3. 等号两边不要有空格
# 4. 变量名建议全大写
6.1.2 .env文件的使用方式
yaml 复制代码
# docker-compose.yml
services:
  web:
    image: nginx:1.25
    ports:
      - "${WEB_PORT}:80"             # 使用.env中的WEB_PORT变量

  api:
    image: myapp:latest
    environment:
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}
      - REDIS_URL=redis://:${REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_PORT}/0
      - DEBUG=${DEBUG}
      - SECRET_KEY=${SECRET_KEY}
    ports:
      - "${API_PORT}:5000"

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
bash 复制代码
# .env文件会自动被读取
docker compose up -d
# Compose会自动读取.env文件中的变量值
6.1.3 指定自定义env文件
bash 复制代码
# 使用--env-file指定自定义env文件
docker compose --env-file .env.production up -d

# 使用不同env文件启动不同环境
docker compose --env-file .env.dev up -d      # 开发环境
docker compose --env-file .env.test up -d     # 测试环境
docker compose --env-file .env.prod up -d     # 生产环境

6.2 变量插值({VAR}, {VAR:-default})

Docker Compose支持在YAML文件中使用变量插值,从环境变量或.env文件中读取值。

6.2.1 基本插值语法
yaml 复制代码
services:
  db:
    image: postgres:${POSTGRES_VERSION:-16}
    # ${VAR}: 读取环境变量VAR
    # ${VAR:-default}: 如果VAR未设置或为空,使用default

    environment:
      POSTGRES_DB: ${DB_NAME}                 # 直接引用
      POSTGRES_USER: ${DB_USER:-postgres}     # 带默认值
      POSTGRES_PASSWORD: ${DB_PASSWORD}       # 必须设置(否则为空字符串)
6.2.2 插值语法详解
yaml 复制代码
services:
  web:
    image: nginx:${NGINX_VERSION}

    # 1. 基本引用
    ports:
      - "${WEB_PORT}:80"

    # 2. 带默认值:- (如果未设置或为空,使用默认值)
    environment:
      - DEBUG=${DEBUG:-false}
      - PORT=${PORT:-8080}

    # 3. 带默认值:+ (如果已设置,使用替代值)
    volumes:
      - ${VOLUME_PATH:+./data}:/app/data
      # 如果VOLUME_PATH已设置,使用./data;否则不挂载

    # 4. 带默认值:? (如果未设置,报错并退出)
    environment:
      - SECRET_KEY=${SECRET_KEY:?SECRET_KEY is required}
    # 如果SECRET_KEY未设置,报错: "SECRET_KEY is required"

    # 5. 嵌套变量
    environment:
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}

    # 6. 使用$转义(不进行插值)
    command: echo $$HOME
    # $$会被转义为$,容器内看到的是$HOME
6.2.3 插值规则总结
语法 说明 示例
${VAR} 引用变量 ${DB_HOST}
${VAR:-default} 变量为空或未设置时用默认值 ${PORT:-8080}
${VAR-default} 变量未设置时用默认值 ${PORT-8080}
${VAR:?msg} 变量未设置时报错 ${SECRET_KEY:?required}
${VAR?msg} 变量未设置时报错 ${SECRET_KEY?required}
${VAR:+replacement} 变量已设置时用替代值 ${DEBUG:+--verbose}
$$ 转义为$ $$HOME -> $HOME
6.2.4 插值实战示例
yaml 复制代码
# docker-compose.yml
name: ${PROJECT_NAME:-myapp}

services:
  web:
    image: nginx:${NGINX_VERSION:-1.25}
    ports:
      - "${WEB_PORT:-80}:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    restart: ${RESTART_POLICY:-unless-stopped}
    environment:
      - TZ=${TIMEZONE:-Asia/Shanghai}
    deploy:
      replicas: ${WEB_REPLICAS:-1}
      resources:
        limits:
          memory: ${WEB_MEMORY_LIMIT:-512M}
    profiles: ["${ENVIRONMENT:-dev}", "all"]

  api:
    build:
      context: ./api
      args:
        PYTHON_VERSION: ${PYTHON_VERSION:-3.11}
    image: ${IMAGE_REGISTRY:-myapp}/api:${IMAGE_TAG:-latest}
    environment:
      - DEBUG=${DEBUG:-false}
      - DATABASE_URL=postgresql://${DB_USER:-appuser}:${DB_PASSWORD:?DB_PASSWORD required}@${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-myapp}
      - REDIS_URL=redis://${REDIS_PASSWORD:+:${REDIS_PASSWORD}@}${REDIS_HOST:-redis}:${REDIS_PORT:-6379}/0
    depends_on:
      db:
        condition: service_healthy
    restart: ${RESTART_POLICY:-unless-stopped}
    deploy:
      replicas: ${API_REPLICAS:-1}
      resources:
        limits:
          cpus: "${API_CPU_LIMIT:-1.0}"
          memory: ${API_MEMORY_LIMIT:-512M}

6.3 多环境compose文件(override机制, -f多个文件)

6.3.1 多文件机制

Docker Compose支持通过-f参数指定多个Compose文件,后面的文件会覆盖和扩展前面的文件。

bash 复制代码
# 基础文件 + 环境覆盖文件
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
6.3.2 基础文件
yaml 复制代码
# docker-compose.yml - 基础配置(所有环境共享)
services:
  api:
    build:
      context: ./api
    environment:
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 5s
      timeout: 3s
      retries: 10

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data

volumes:
  db-data:
  redis-data:
6.3.3 开发环境覆盖文件
yaml 复制代码
# docker-compose.dev.yml - 开发环境覆盖
services:
  api:
    build:
      target: development              # 构建到开发阶段
    ports:
      - "5000:5000"                    # 暴露端口用于调试
      - "5678:5678"                    # 调试器端口
    volumes:
      - ./api:/app                     # 挂载源代码,热重载
      - ./api/tests:/app/tests         # 挂载测试
    environment:
      - DEBUG=true
      - FLASK_ENV=development
      - FLASK_DEBUG=1
    command: >
      python -m debugpy --listen 0.0.0.0:5678
      --wait-for-client
      app.py
    deploy:
      replicas: 1                      # 单实例

  db:
    ports:
      - "5432:5432"                    # 暴露数据库端口
    volumes:
      - ./db/dev-init:/docker-entrypoint-initdb.d:ro  # 开发数据初始化

  redis:
    ports:
      - "6379:6379"                    # 暴露Redis端口

  # 开发工具
  adminer:
    image: adminer:latest
    ports:
      - "8081:8080"
    depends_on:
      - db

  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"
6.3.4 生产环境覆盖文件
yaml 复制代码
# docker-compose.prod.yml - 生产环境覆盖
services:
  api:
    build:
      target: production               # 构建到生产阶段
    image: registry.example.com/myapp/api:${TAG:-latest}
    ports: []                          # 不暴露调试端口
    volumes: []                        # 不挂载源代码
    environment:
      - DEBUG=false
      - FLASK_ENV=production
    command: >
      gunicorn app:app
      --bind 0.0.0.0:5000
      --workers ${WORKERS:-4}
      --threads ${THREADS:-2}
      --timeout 120
    deploy:
      replicas: ${API_REPLICAS:-3}    # 多实例
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 128M
      update_config:
        parallelism: 1
        delay: 10s
        failure_action: rollback
    restart: always

  db:
    ports: []                          # 不暴露数据库端口
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2G
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db-data:/var/lib/postgresql/data
      - ./db/prod-init:/docker-entrypoint-initdb.d:ro
      - ./db/postgresql.conf:/etc/postgresql/postgresql.conf:ro
    command: postgres -c config_file=/etc/postgresql/postgresql.conf

  redis:
    ports: []                          # 不暴露Redis端口
    command: >
      redis-server
      --maxmemory 512mb
      --maxmemory-policy allkeys-lru
      --appendonly yes
      --requirepass ${REDIS_PASSWORD}
    deploy:
      resources:
        limits:
          memory: 1G

  # 生产环境的Nginx
  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.prod.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - nginx-logs:/var/log/nginx
    depends_on:
      api:
        condition: service_healthy
    restart: always
    deploy:
      resources:
        limits:
          memory: 256M

volumes:
  nginx-logs:

6.4 docker-compose.override.yml自动覆盖

6.4.1 自动覆盖机制

当你在目录中有docker-compose.ymldocker-compose.override.yml两个文件时,运行docker compose up会自动合并这两个文件(override覆盖base)。

bash 复制代码
# 自动合并(不需要-f)
docker compose up -d
# 等同于:
docker compose -f docker-compose.yml -f docker-compose.override.yml up -d
6.4.2 典型的override使用
yaml 复制代码
# docker-compose.yml - 基础配置(生产级别)
services:
  api:
    build: ./api
    image: myapp/api:latest
    environment:
      - DATABASE_URL=postgresql://appuser:password@db:5432/myapp
    restart: always

  db:
    image: postgres:16
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:
yaml 复制代码
# docker-compose.override.yml - 开发覆盖(不提交到Git)
services:
  api:
    build:
      target: development
    ports:
      - "5000:5000"
      - "5678:5678"
    volumes:
      - ./api:/app
    environment:
      - DEBUG=true
    command: python app.py
    deploy:
      replicas: 1

  db:
    ports:
      - "5432:5432"

  # 添加开发工具
  adminer:
    image: adminer:latest
    ports:
      - "8081:8080"
bash 复制代码
# 开发环境: 直接up(自动合并override)
docker compose up -d

# 生产环境: 显式指定文件(不使用override)
docker compose -f docker-compose.yml up -d
# 或者使用生产覆盖文件
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

6.5 开发/测试/生产环境配置策略

6.5.1 文件组织结构
复制代码
project/
├── docker-compose.yml              # 基础配置
├── docker-compose.override.yml     # 开发覆盖(自动加载,.gitignore)
├── docker-compose.test.yml         # 测试覆盖
├── docker-compose.prod.yml         # 生产覆盖
├── .env                            # 基础环境变量(.gitignore)
├── .env.dev                        # 开发环境变量(.gitignore)
├── .env.test                       # 测试环境变量
├── .env.prod                       # 生产环境变量(.gitignore)
├── Makefile                        # 简化命令
└── ...
6.5.2 Makefile简化操作
makefile 复制代码
# Makefile
.PHONY: dev test prod clean

# 开发环境
dev:
	docker compose --env-file .env.dev up -d

dev-build:
	docker compose --env-file .env.dev up -d --build

dev-logs:
	docker compose --env-file .env.dev logs -f

dev-down:
	docker compose --env-file .env.dev down

dev-shell:
	docker compose --env-file .env.dev exec api bash

# 测试环境
test:
	docker compose -f docker-compose.yml -f docker-compose.test.yml --env-file .env.test up -d --build

test-run:
	docker compose -f docker-compose.yml -f docker-compose.test.yml --env-file .env.test run --rm test pytest -v

test-down:
	docker compose -f docker-compose.yml -f docker-compose.test.yml --env-file .env.test down -v

# 生产环境
prod:
	docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d

prod-build:
	docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod build

prod-push:
	docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod push

prod-down:
	docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod down

# 通用
clean:
	docker compose down -v --remove-orphans --rmi local

ps:
	docker compose ps

logs:
	docker compose logs -f --tail 100
bash 复制代码
# 使用Makefile
make dev          # 启动开发环境
make test         # 启动测试环境
make prod         # 启动生产环境
make clean        # 清理所有
make dev-shell    # 进入开发环境shell

6.6 环境变量优先级规则

Docker Compose的变量来源有多个,它们的优先级从高到低为:

复制代码
1. 命令行 --env-file指定的文件
2. Shell环境变量 (export VAR=value)
3. .env文件 (当前目录)
4. Compose文件中的默认值 (${VAR:-default})
5. Dockerfile中的ENV指令
bash 复制代码
# 示例: 理解优先级

# 1. .env文件中定义
echo "DB_PASSWORD=from_env_file" > .env

# 2. Shell环境变量
export DB_PASSWORD=from_shell

# 3. 运行compose
docker compose up -d
# DB_PASSWORD的值是: from_shell (Shell环境变量优先于.env文件)

# 4. 使用--env-file覆盖
echo "DB_PASSWORD=from_custom_file" > .env.custom
docker compose --env-file .env.custom up -d
# DB_PASSWORD的值是: from_custom_file (--env-file优先级最高)
yaml 复制代码
# compose文件中的默认值
services:
  api:
    environment:
      - DB_PASSWORD=${DB_PASSWORD:-default_password}
      # 优先级:
      # 1. 命令行--env-file
      # 2. Shell环境变量
      # 3. .env文件
      # 4. default_password (最终默认值)

6.7 使用profiles管理不同环境的服务

yaml 复制代码
# 使用profiles在一个文件中管理多环境
services:
  # ===== 核心服务(所有环境) =====
  api:
    image: myapp/api:${TAG:-latest}
    build:
      context: ./api
      target: ${BUILD_TARGET:-development}
    environment:
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
      - DEBUG=${DEBUG:-true}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 5s
      retries: 10

  redis:
    image: redis:7-alpine

  # ===== 开发环境服务 =====
  api-dev:
    image: myapp/api:${TAG:-latest}
    build:
      context: ./api
      target: development
    volumes:
      - ./api:/app
    environment:
      - DEBUG=true
      - FLASK_DEBUG=1
    command: python app.py
    ports:
      - "5000:5000"
      - "5678:5678"
    profiles: ["dev"]
    depends_on:
      db:
        condition: service_healthy

  adminer:
    image: adminer:latest
    ports:
      - "8081:8080"
    profiles: ["dev"]
    depends_on:
      - db

  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"
    profiles: ["dev"]

  # ===== 测试环境服务 =====
  test-runner:
    image: myapp/api:${TAG:-latest}
    build:
      context: ./api
      target: test
    command: pytest -v --cov=app
    profiles: ["test"]
    depends_on:
      api:
        condition: service_healthy
      db:
        condition: service_healthy

  selenium:
    image: selenium/standalone-chrome:latest
    profiles: ["test"]
    shm_size: 2g

  # ===== 生产环境服务 =====
  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    profiles: ["prod"]
    depends_on:
      api:
        condition: service_healthy
    restart: always

  # ===== 监控服务 =====
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    profiles: ["monitoring"]
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    profiles: ["monitoring"]
    volumes:
      - grafana-data:/var/lib/grafana

volumes:
  db-data:
  grafana-data:
bash 复制代码
# 使用不同profile启动不同环境
docker compose --profile dev up -d            # 开发环境
docker compose --profile test up -d           # 测试环境
docker compose --profile prod up -d           # 生产环境
docker compose --profile prod --profile monitoring up -d  # 生产+监控

# 使用环境变量指定profile
export COMPOSE_PROFILES=prod,monitoring
docker compose up -d

6.8 多环境部署实战案例

下面是一个完整的多环境部署案例:

yaml 复制代码
# docker-compose.yml - 基础配置
name: myblog

x-common-env: &common-env
  TZ: Asia/Shanghai

x-common-restart: &common-restart
  restart: unless-stopped

x-common-logging: &common-logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "5"

services:
  blog:
    <<: *common-restart
    build:
      context: ./blog
      dockerfile: Dockerfile
    environment:
      <<: *common-env
      DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
      REDIS_URL: redis://redis:6379/0
      SECRET_KEY: ${SECRET_KEY:?SECRET_KEY is required}
      DEBUG: ${DEBUG:-false}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    logging: *common-logging
    networks:
      - frontend
      - backend

  postgres:
    <<: *common-restart
    image: postgres:16-alpine
    environment:
      <<: *common-env
      POSTGRES_DB: ${DB_NAME:-blog}
      POSTGRES_USER: ${DB_USER:-bloguser}
      POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-bloguser} -d ${DB_NAME:-blog}"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s
    logging: *common-logging
    networks:
      - backend

  redis:
    <<: *common-restart
    image: redis:7-alpine
    command: redis-server ${REDIS_PASSWORD:+--requirepass ${REDIS_PASSWORD}}
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    logging: *common-logging
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true

volumes:
  postgres-data:
  redis-data:
yaml 复制代码
# docker-compose.dev.yml - 开发覆盖
services:
  blog:
    build:
      target: development
    ports:
      - "8000:8000"
      - "5678:5678"
    volumes:
      - ./blog:/app
    environment:
      DEBUG: "true"
      FLASK_DEBUG: "1"
    command: >
      python -m debugpy --listen 0.0.0.0:5678
      --wait-for-client
      -m flask run --host=0.0.0.0 --port=8000
    deploy:
      replicas: 1

  postgres:
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./db/dev-data:/docker-entrypoint-initdb.d:ro

  redis:
    ports:
      - "6379:6379"

  # 开发工具
  adminer:
    image: adminer:latest
    ports:
      - "8081:8080"
    networks:
      - backend
    depends_on:
      - postgres

  redis-insight:
    image: redislabs/redisinsight:latest
    ports:
      - "8001:8001"
    networks:
      - backend
    depends_on:
      - redis
yaml 复制代码
# docker-compose.prod.yml - 生产覆盖
services:
  blog:
    build:
      target: production
    image: ${REGISTRY:-registry.example.com}/myblog:${TAG:-latest}
    ports: []
    volumes: []
    environment:
      DEBUG: "false"
    command: >
      gunicorn app:app
      --bind 0.0.0.0:8000
      --workers ${WORKERS:-4}
      --threads ${THREADS:-2}
      --timeout 120
      --access-logfile -
      --error-logfile -
    deploy:
      replicas: ${REPLICAS:-2}
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 128M
    restart: always

  postgres:
    ports: []
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2G
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./db/postgresql.conf:/etc/postgresql/postgresql.conf:ro
    command: postgres -c config_file=/etc/postgresql/postgresql.conf

  redis:
    ports: []
    command: >
      redis-server
      --maxmemory 512mb
      --maxmemory-policy allkeys-lru
      --appendonly yes
      --requirepass ${REDIS_PASSWORD}
    deploy:
      resources:
        limits:
          memory: 1G

  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.prod.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - nginx-logs:/var/log/nginx
    depends_on:
      blog:
        condition: service_healthy
    restart: always
    deploy:
      resources:
        limits:
          memory: 256M
    networks:
      - frontend

volumes:
  nginx-logs:
bash 复制代码
# .env.dev - 开发环境变量
DB_NAME=blog
DB_USER=bloguser
DB_PASSWORD=devpassword
SECRET_KEY=dev-secret-key
DEBUG=true

# .env.prod - 生产环境变量
DB_NAME=blog
DB_USER=bloguser
DB_PASSWORD=super-secure-production-password
SECRET_KEY=production-super-long-secret-key
REDIS_PASSWORD=redis-secure-password
REGISTRY=registry.example.com
TAG=v1.0.0
WORKERS=4
THREADS=2
REPLICAS=3
bash 复制代码
# 启动开发环境
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.dev up -d

# 启动生产环境
docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d

# 简化: 使用Makefile
# make dev / make prod

第七章 Docker Compose网络与数据卷

本章深入讲解Docker Compose中的网络和数据卷管理,这是多容器应用编排的核心能力。

7.1 默认网络行为

当你在Compose文件中不显式定义网络时,Docker Compose会自动创建一个默认网络。

yaml 复制代码
# 不定义网络 - Compose自动创建默认网络
services:
  web:
    image: nginx:1.25
    # 自动加入默认网络: <project_name>_default

  api:
    image: myapp:latest
    # 自动加入默认网络: <project_name>_default
    # api可以通过 "web" 这个服务名访问nginx
bash 复制代码
# 查看自动创建的网络
docker network ls
# 输出示例:
# NETWORK ID     NAME                    DRIVER    SCOPE
# a1b2c3d4e5f6   myapp_default           bridge    local

# 默认网络名称规则: <project_name>_default
# 如果项目名为myapp,则网络名为myapp_default
7.1.1 默认网络的特点
yaml 复制代码
# Compose自动创建的默认网络具有以下特点:
# 1. 使用bridge驱动
# 2. 网络名: <project_name>_default
# 3. 所有未指定networks的服务自动加入
# 4. 服务间通过服务名进行DNS解析
# 5. 网络在docker compose down时自动删除

# 验证服务间通信
services:
  api:
    image: myapp:latest
    # 在api容器内,可以通过 "db" 访问数据库服务
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
    depends_on:
      - db

  db:
    image: postgres:16
    # Compose自动为db服务创建DNS记录 "db"
bash 复制代码
# 在api容器内测试DNS解析
docker compose exec api ping db
docker compose exec api nslookup db
docker compose exec api getent hosts db

7.2 自定义网络配置

yaml 复制代码
services:
  web:
    image: nginx:1.25
    networks:
      - frontend

  api:
    image: myapp:latest
    networks:
      - frontend
      - backend

  db:
    image: postgres:16
    networks:
      - backend

networks:
  # 自定义bridge网络
  frontend:
    driver: bridge
    name: myapp-frontend-net

  # 内部网络(不可访问外网)
  backend:
    driver: bridge
    internal: true
    name: myapp-backend-net

  # 带IPAM配置的网络
  custom:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.25.0.0/16
          gateway: 172.25.0.1
          ip_range: 172.25.0.0/24

  # overlay网络(需要Swarm模式)
  # swarm-net:
  #   driver: overlay
  #   attachable: true    # 允许独立容器加入

  # host网络模式(直接使用主机网络)
  # host-net:
  #   driver: host

  # none网络模式(无网络)
  # no-net:
  #   driver: none
7.2.1 网络驱动说明
驱动 说明 适用场景
bridge 默认桥接网络 单机容器通信
overlay 覆盖网络 Swarm集群跨主机通信
host 使用主机网络 需要最高网络性能
none 无网络 完全隔离的容器
macvlan MAC地址VLAN 容器需要独立MAC地址
external 引用外部网络 多项目共享网络
7.2.2 自定义IP地址
yaml 复制代码
services:
  web:
    image: nginx:1.25
    networks:
      app-net:
        ipv4_address: 172.20.0.10    # 指定静态IP
        ipv6_address: 2001:db8::10   # IPv6地址
        mac_address: 02:42:ac:14:00:0a  # MAC地址

  api:
    image: myapp:latest
    networks:
      app-net:
        ipv4_address: 172.20.0.20

  db:
    image: postgres:16
    networks:
      app-net:
        ipv4_address: 172.20.0.30

networks:
  app-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16
          gateway: 172.20.0.1
          ip_range: 172.20.0.0/24

7.3 服务间网络隔离

yaml 复制代码
# 多层网络隔离架构
services:
  # ===== 公共层 =====
  # 只有nginx暴露给外部
  nginx:
    image: nginx:1.25
    ports:
      - "80:80"
      - "443:443"
    networks:
      - frontend              # 只在前端网络
    depends_on:
      - web-app

  # ===== 应用层 =====
  # web-app连接前端和后端
  web-app:
    image: myapp:latest
    networks:
      - frontend              # 接收nginx请求
      - app-tier              # 与api通信
    depends_on:
      - api

  # api连接应用层和数据层
  api:
    image: myapi:latest
    networks:
      - app-tier              # 与web-app通信
      - data-tier             # 与数据库通信
    depends_on:
      - postgres
      - redis

  # ===== 数据层 =====
  # 数据库只在数据层网络
  postgres:
    image: postgres:16
    networks:
      - data-tier             # 只有api能访问
    # nginx和web-app都无法直接访问postgres

  redis:
    image: redis:7-alpine
    networks:
      - data-tier             # 只有api能访问

  # ===== 管理层 =====
  # 管理工具在单独网络
  adminer:
    image: adminer:latest
    ports:
      - "8081:8080"
    networks:
      - data-tier             # 需要访问数据库
      - management            # 管理网络
    profiles: [dev]

networks:
  frontend:
    driver: bridge
  app-tier:
    driver: bridge
    internal: true            # 内部网络
  data-tier:
    driver: bridge
    internal: true            # 内部网络,完全隔离
  management:
    driver: bridge

7.4 外部网络引用

当多个Compose项目需要共享网络时,可以使用外部网络。

yaml 复制代码
# 项目A: docker-compose.yml
services:
  api-a:
    image: myapp-a:latest
    networks:
      - shared-network        # 使用共享网络

networks:
  shared-network:
    name: shared-net          # 网络名称
    external: true            # 引用外部已创建的网络
yaml 复制代码
# 项目B: docker-compose.yml
services:
  api-b:
    image: myapp-b:latest
    networks:
      - shared-network        # 同样使用共享网络

networks:
  shared-network:
    name: shared-net          # 必须与项目A的网络名一致
    external: true
bash 复制代码
# 先创建外部网络
docker network create shared-net

# 启动两个项目
cd project-a && docker compose up -d
cd project-b && docker compose up -d

# 项目A和项目B的容器可以通过服务名互相访问
# api-a容器内可以ping api-b

7.5 IPv6配置

yaml 复制代码
services:
  web:
    image: nginx:1.25
    networks:
      ipv6-net:
        ipv4_address: 172.30.0.10
        ipv6_address: 2001:db8::10

networks:
  ipv6-net:
    driver: bridge
    enable_ipv6: true                       # 启用IPv6
    ipam:
      driver: default
      config:
        - subnet: 172.30.0.0/16             # IPv4子网
          gateway: 172.30.0.1
        - subnet: 2001:db8::/64             # IPv6子网
          gateway: 2001:db8::1

7.6 命名数据卷与匿名数据卷

7.6.1 命名数据卷
yaml 复制代码
services:
  db:
    image: postgres:16
    volumes:
      # 命名数据卷 - 在顶层volumes中定义
      - postgres-data:/var/lib/postgresql/data

volumes:
  postgres-data:
    # 基本定义
    # Docker自动管理,名称为: <project>_postgres-data

    # 自定义名称
    name: myapp-postgres-data

    # 自定义驱动
    driver: local
bash 复制代码
# 命名数据卷的特点:
# 1. 有明确的名称,便于管理
# 2. 在docker compose down时不会被删除
# 3. 只有docker compose down -v才删除
# 4. 可以在多个Compose项目间共享

# 查看数据卷
docker volume ls
# DRIVER    VOLUME NAME
# local     myapp-postgres-data

# 查看数据卷详情
docker volume inspect myapp-postgres-data

# 手动删除数据卷
docker volume rm myapp-postgres-data
7.6.2 匿名数据卷
yaml 复制代码
services:
  db:
    image: postgres:16
    volumes:
      # 匿名数据卷 - 只指定容器内路径
      - /var/lib/postgresql/data
      # Docker自动创建匿名卷,名称为随机哈希

  # 或者Dockerfile中定义的VOLUME也会创建匿名卷
  app:
    image: myapp:latest
    # 如果Dockerfile中有: VOLUME /app/data
    # 也会自动创建匿名卷
bash 复制代码
# 匿名数据卷的特点:
# 1. 名称是随机哈希,难以管理
# 2. 在docker compose down时不会被删除(除非加--volumes)
# 3. 容易积累无用数据卷
# 4. 不推荐使用

# 清理匿名数据卷
docker volume prune              # 删除所有未使用的卷
docker volume ls -f dangling=true  # 查看悬空卷
7.6.3 绑定挂载 vs 命名卷对比
特性 绑定挂载(bind) 命名卷(volume)
路径 主机指定路径 Docker管理路径
移植性 差(依赖主机路径)
性能 Linux好,Mac/Win差
备份 直接操作主机文件 需要通过容器
权限 继承主机文件权限 Docker管理
适用场景 配置文件、源代码 数据库数据、持久化数据

7.7 数据卷驱动配置

yaml 复制代码
services:
  db:
    image: postgres:16
    volumes:
      - nfs-data:/var/lib/postgresql/data

volumes:
  # NFS数据卷
  nfs-data:
    driver: local
    driver_opts:
      type: nfs
      device: ":/path/to/nfs/share"
      o: addr=192.168.1.100,rw,size=1048576,hard,timeo=600

  # CIFS/SMB数据卷
  smb-data:
    driver: local
    driver_opts:
      type: cifs
      device: "//192.168.1.200/share"
      o: "username=user,password=pass,uid=1000,gid=1000"

  # tmpfs数据卷
  tmp-data:
    driver: local
    driver_opts:
      type: tmpfs
      device: tmpfs
      o: size=100m,mode=1777

  # 绑定特定路径
  bind-data:
    driver: local
    driver_opts:
      type: none
      device: /data/myapp
      o: bind

  # 块设备
  block-data:
    driver: local
    driver_opts:
      type: ext4
      device: /dev/sdb1

7.8 外部数据卷引用

yaml 复制代码
services:
  db:
    image: postgres:16
    volumes:
      - existing-data:/var/lib/postgresql/data

volumes:
  existing-data:
    name: shared-db-data          # 必须指定名称
    external: true                # 引用外部已存在的卷
    # external: true表示Compose不会创建此卷
    # 卷必须预先通过 docker volume create 创建
bash 复制代码
# 预先创建外部数据卷
docker volume create shared-db-data

# 启动compose
docker compose up -d
# Compose会使用已存在的shared-db-data卷

# 优势: 数据卷在多个Compose项目间共享
# 即使执行docker compose down,数据卷也不会被删除

7.9 网络与数据卷管理实战

下面是一个完整的网络与数据卷管理实战案例:

yaml 复制代码
name: enterprise-app

services:
  # ===== 前端代理 =====
  nginx:
    image: nginx:1.25-alpine
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro          # 配置文件(bind)
      - ./nginx/conf.d:/etc/nginx/conf.d:ro                  # 配置目录(bind)
      - ./nginx/ssl:/etc/nginx/ssl:ro                        # SSL证书(bind)
      - nginx-logs:/var/log/nginx                            # 日志(volume)
      - nginx-cache:/var/cache/nginx                         # 缓存(volume)
    networks:
      - frontend
    depends_on:
      - web
      - api
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"

  # ===== Web前端 =====
  web:
    image: myapp/web:latest
    build:
      context: ./web
      target: production
    expose:
      - "3000"
    networks:
      - frontend
    volumes:
      - web-node-modules:/app/node_modules                   # node_modules(volume)
    environment:
      - NODE_ENV=production
      - API_URL=http://api:5000
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 256M
    restart: unless-stopped

  # ===== API后端 =====
  api:
    image: myapp/api:latest
    build:
      context: ./api
      target: production
    expose:
      - "5000"
    networks:
      - frontend
      - app-tier
    volumes:
      - api-logs:/app/logs                                    # 日志(volume)
      - api-uploads:/app/uploads                              # 上传文件(volume)
      - ./api/config:/app/config:ro                           # 配置(bind)
    environment:
      - DATABASE_URL=postgresql://appuser:${DB_PASSWORD}@postgres:5432/myapp
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
      - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD}@redis:6379/1
      - SECRET_KEY=${SECRET_KEY}
      - DEBUG=false
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "10"

  # ===== Celery Worker =====
  worker:
    image: myapp/api:latest
    build:
      context: ./api
      target: production
    command: celery -A app.celery worker --loglevel=info --concurrency=4
    networks:
      - app-tier
    volumes:
      - api-logs:/app/logs
      - api-uploads:/app/uploads
    environment:
      - DATABASE_URL=postgresql://appuser:${DB_PASSWORD}@postgres:5432/myapp
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
      - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD}@redis:6379/1
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: "0.5"
          memory: 256M
    restart: unless-stopped

  # ===== Celery Beat(定时任务) =====
  scheduler:
    image: myapp/api:latest
    build:
      context: ./api
      target: production
    command: celery -A app.celery beat --loglevel=info
    networks:
      - app-tier
    environment:
      - DATABASE_URL=postgresql://appuser:${DB_PASSWORD}@postgres:5432/myapp
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
      - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD}@redis:6379/1
    depends_on:
      redis:
        condition: service_healthy
    restart: unless-stopped

  # ===== PostgreSQL数据库 =====
  postgres:
    image: postgres:16-alpine
    container_name: postgres-db
    expose:
      - "5432"
    networks:
      - data-tier
    volumes:
      - postgres-data:/var/lib/postgresql/data                # 数据(volume)
      - ./postgres/init:/docker-entrypoint-initdb.d:ro        # 初始化脚本(bind)
      - postgres-backup:/backup                               # 备份(volume)
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2G
    restart: unless-stopped

  # ===== Redis缓存 =====
  redis:
    image: redis:7-alpine
    container_name: redis-cache
    expose:
      - "6379"
    networks:
      - data-tier
    volumes:
      - redis-data:/data                                      # 持久化数据(volume)
      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro  # 配置(bind)
    command: redis-server /usr/local/etc/redis/redis.conf
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 512M
    sysctls:
      net.core.somaxconn: 1024
    restart: unless-stopped

# ==========================================
# 网络定义
# ==========================================
networks:
  # 前端网络 - nginx到web/api
  frontend:
    name: ea-frontend
    driver: bridge
    labels:
      - "network=frontend"
      - "tier=presentation"

  # 应用网络 - web/api/worker之间
  app-tier:
    name: ea-app-tier
    driver: bridge
    internal: true
    labels:
      - "network=app-tier"
      - "tier=application"

  # 数据网络 - api到数据库
  data-tier:
    name: ea-data-tier
    driver: bridge
    internal: true
    labels:
      - "network=data-tier"
      - "tier=data"

# ==========================================
# 数据卷定义
# ==========================================
volumes:
  # ===== 前端数据 =====
  nginx-logs:
    name: ea-nginx-logs
  nginx-cache:
    name: ea-nginx-cache
  web-node-modules:
    name: ea-web-node-modules

  # ===== 应用数据 =====
  api-logs:
    name: ea-api-logs
  api-uploads:
    name: ea-api-uploads

  # ===== 数据库数据 =====
  postgres-data:
    name: ea-postgres-data
    labels:
      - "volume=postgres-data"
      - "backup=true"
  postgres-backup:
    name: ea-postgres-backup
  redis-data:
    name: ea-redis-data

第八章 实战案例: 完整Web应用编排

本章通过一个完整的Nginx + Flask + MySQL + Redis + Celery架构的Web应用编排,将前面学到的所有知识融会贯通。

8.1 案例架构: Nginx + Flask + MySQL + Redis + Celery

复制代码
                    ┌─────────┐
                    │  用户   │
                    └────┬────┘
                         │ HTTP/HTTPS
                         ▼
┌──────────────────────────────────────────────┐
│                  Nginx (反向代理)              │
│              端口: 80/443                      │
└──────────┬───────────────────────────────────┘
           │ proxy_pass
           ▼
┌──────────────────────────────────────────────┐
│              Flask App (Gunicorn)             │
│              端口: 5000 (内部)                 │
│  ┌──────────────────────────────────────────┐ │
│  │  Web路由 + API接口 + WebSocket           │ │
│  └──────────────────────────────────────────┘ │
└─────┬──────────────┬──────────────┬───────────┘
      │              │              │
      ▼              ▼              ▼
┌──────────┐  ┌──────────┐  ┌──────────────┐
│  MySQL   │  │  Redis   │  │ Celery Worker│
│  数据库   │  │  缓存    │  │  异步任务     │
│ 端口:3306 │  │ 端口:6379│  │              │
└──────────┘  └────┬─────┘  └──────┬───────┘
                   │               │
                   └───────┬───────┘
                           ▼
                   ┌──────────────┐
                   │ Celery Beat  │
                   │  定时任务     │
                   └──────────────┘

8.2 编写各服务的Dockerfile

8.2.1 Flask应用Dockerfile
dockerfile 复制代码
# flask-app/Dockerfile

# ==========================================
# 阶段1: 依赖安装
# ==========================================
FROM python:3.11-slim AS builder

# 设置工作目录
WORKDIR /build

# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件
COPY requirements.txt .

# 安装Python依赖
RUN pip install --user --no-cache-dir -r requirements.txt

# ==========================================
# 阶段2: 开发阶段
# ==========================================
FROM python:3.11-slim AS development

# 安装运行时依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 从builder阶段复制Python包
COPY --from=builder /root/.local /root/.local

# 设置工作目录
WORKDIR /app

# 复制应用代码
COPY . .

# 设置环境变量
ENV PATH=/root/.local/bin:$PATH
ENV FLASK_APP=app.py
ENV FLASK_ENV=development
ENV PYTHONPATH=/app

# 开放端口
EXPOSE 5000

# 开发模式启动命令
CMD ["python", "-m", "flask", "run", "--host=0.0.0.0", "--port=5000", "--debug"]

# ==========================================
# 阶段3: 生产阶段
# ==========================================
FROM python:3.11-slim AS production

# 安装运行时依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 从builder阶段复制Python包
COPY --from=builder /root/.local /root/.local

# 创建非root用户
RUN useradd -m -u 1000 appuser

# 设置工作目录
WORKDIR /app

# 复制应用代码
COPY --chown=appuser:appuser . .

# 切换到非root用户
USER appuser

# 设置环境变量
ENV PATH=/root/.local/bin:$PATH
ENV FLASK_APP=app.py
ENV FLASK_ENV=production
ENV PYTHONPATH=/app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# 开放端口
EXPOSE 5000

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD curl -f http://localhost:5000/health || exit 1

# 生产模式启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "--threads", "2", "--timeout", "120", "--access-logfile", "-", "--error-logfile", "-", "app:app"]
8.2.2 Nginx Dockerfile
dockerfile 复制代码
# nginx/Dockerfile
FROM nginx:1.25-alpine

# 复制自定义配置
COPY nginx.conf /etc/nginx/nginx.conf
COPY conf.d/ /etc/nginx/conf.d/

# 复制SSL证书(如果需要)
# COPY ssl/ /etc/nginx/ssl/

# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD wget --spider -q http://localhost/health || exit 1

EXPOSE 80 443

CMD ["nginx", "-g", "daemon off;"]

8.3 编写docker-compose.yml(完整配置)

yaml 复制代码
# docker-compose.yml
name: flaskapp

services:
  # ==========================================
  # Nginx反向代理
  # ==========================================
  nginx:
    image: nginx:1.25-alpine
    container_name: flaskapp-nginx
    hostname: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro          # 主配置
      - ./nginx/conf.d:/etc/nginx/conf.d:ro                  # 站点配置
      - ./nginx/ssl:/etc/nginx/ssl:ro                        # SSL证书
      - nginx-logs:/var/log/nginx                            # 日志持久化
      - nginx-cache:/var/cache/nginx                         # 缓存持久化
    networks:
      - frontend
    depends_on:
      flask-app:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"
    deploy:
      resources:
        limits:
          memory: 128M

  # ==========================================
  # Flask应用
  # ==========================================
  flask-app:
    build:
      context: ./flask-app
      dockerfile: Dockerfile
      target: ${BUILD_TARGET:-production}
      args:
        PYTHON_VERSION: "3.11"
    image: flaskapp/api:${IMAGE_TAG:-latest}
    container_name: flaskapp-api
    hostname: flask-app
    expose:
      - "5000"
    volumes:
      - flask-logs:/app/logs                                  # 应用日志
      - flask-uploads:/app/uploads                            # 上传文件
      - ./flask-app/config:/app/config:ro                     # 配置文件
    environment:
      - FLASK_APP=app.py
      - FLASK_ENV=${FLASK_ENV:-production}
      - SECRET_KEY=${SECRET_KEY:?SECRET_KEY is required}
      - DATABASE_URL=mysql+pymysql://${DB_USER:-flaskuser}:${DB_PASSWORD:?DB_PASSWORD is required}@mysql:3306/${DB_NAME:-flaskapp}
      - REDIS_URL=redis://:${REDIS_PASSWORD:-redispass}@redis:6379/0
      - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD:-redispass}@redis:6379/1
      - CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD:-redispass}@redis:6379/2
      - DEBUG=${DEBUG:-false}
      - TZ=Asia/Shanghai
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
      flask-migrate:
        condition: service_completed_successfully
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 40s
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "10"
    deploy:
      replicas: 1
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 128M
    init: true
    stop_grace_period: 30s

  # ==========================================
  # 数据库迁移(一次性任务)
  # ==========================================
  flask-migrate:
    image: flaskapp/api:${IMAGE_TAG:-latest}
    build:
      context: ./flask-app
      dockerfile: Dockerfile
      target: ${BUILD_TARGET:-production}
    container_name: flaskapp-migrate
    command: >
      bash -c "
      echo 'Waiting for MySQL to be ready...' &&
      python -c '
      import time
      import MySQLdb
      for i in range(30):
          try:
              MySQLdb.connect(host=\"mysql\", user=\"${DB_USER:-flaskuser}\", passwd=\"${DB_PASSWORD}\", db=\"${DB_NAME:-flaskapp}\")
              print(\"MySQL is ready!\")
              break
          except:
              print(f\"Waiting for MySQL... ({i+1}/30)\")
              time.sleep(2)
      else:
          print(\"Failed to connect to MySQL\")
          exit(1)
      ' &&
      flask db upgrade &&
      echo 'Migration completed!'
      "
    environment:
      - FLASK_APP=app.py
      - DATABASE_URL=mysql+pymysql://${DB_USER:-flaskuser}:${DB_PASSWORD:?DB_PASSWORD is required}@mysql:3306/${DB_NAME:-flaskapp}
      - SECRET_KEY=${SECRET_KEY:?SECRET_KEY is required}
    depends_on:
      mysql:
        condition: service_healthy
    restart: "no"
    networks:
      - backend

  # ==========================================
  # Celery Worker(异步任务)
  # ==========================================
  celery-worker:
    image: flaskapp/api:${IMAGE_TAG:-latest}
    build:
      context: ./flask-app
      dockerfile: Dockerfile
      target: ${BUILD_TARGET:-production}
    command: >
      celery -A app.celery worker
      --loglevel=info
      --concurrency=${CELERY_CONCURRENCY:-4}
      --max-tasks-per-child=1000
    environment:
      - DATABASE_URL=mysql+pymysql://${DB_USER:-flaskuser}:${DB_PASSWORD:?DB_PASSWORD is required}@mysql:3306/${DB_NAME:-flaskapp}
      - REDIS_URL=redis://:${REDIS_PASSWORD:-redispass}@redis:6379/0
      - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD:-redispass}@redis:6379/1
      - CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD:-redispass}@redis:6379/2
      - TZ=Asia/Shanghai
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: "0.5"
          memory: 256M
    networks:
      - backend

  # ==========================================
  # Celery Beat(定时任务调度)
  # ==========================================
  celery-beat:
    image: flaskapp/api:${IMAGE_TAG:-latest}
    build:
      context: ./flask-app
      dockerfile: Dockerfile
      target: ${BUILD_TARGET:-production}
    command: >
      celery -A app.celery beat
      --loglevel=info
      --schedule=/tmp/celerybeat-schedule
    environment:
      - DATABASE_URL=mysql+pymysql://${DB_USER:-flaskuser}:${DB_PASSWORD:?DB_PASSWORD is required}@mysql:3306/${DB_NAME:-flaskapp}
      - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD:-redispass}@redis:6379/1
      - CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD:-redispass}@redis:6379/2
      - TZ=Asia/Shanghai
    depends_on:
      redis:
        condition: service_healthy
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - backend

  # ==========================================
  # MySQL数据库
  # ==========================================
  mysql:
    image: mysql:8.0
    container_name: flaskapp-mysql
    hostname: mysql
    command: >
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
      --max-connections=200
      --innodb-buffer-pool-size=${MYSQL_BUFFER_POOL:-512M}
      --slow-query-log=ON
      --long-query-time=2
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD is required}
      MYSQL_DATABASE: ${DB_NAME:-flaskapp}
      MYSQL_USER: ${DB_USER:-flaskuser}
      MYSQL_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
      TZ: Asia/Shanghai
    volumes:
      - mysql-data:/var/lib/mysql                               # 数据持久化
      - ./mysql/conf.d:/etc/mysql/conf.d:ro                     # 自定义配置
      - ./mysql/init:/docker-entrypoint-initdb.d:ro             # 初始化脚本
      - mysql-logs:/var/log/mysql                               # 日志持久化
      - mysql-backup:/backup                                    # 备份目录
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2G
    networks:
      - backend

  # ==========================================
  # Redis缓存
  # ==========================================
  redis:
    image: redis:7-alpine
    container_name: flaskapp-redis
    hostname: redis
    command: >
      redis-server
      --maxmemory ${REDIS_MAXMEMORY:-256mb}
      --maxmemory-policy allkeys-lru
      --appendonly yes
      --appendfsync everysec
      --requirepass ${REDIS_PASSWORD:-redispass}
      --save 900 1
      --save 300 10
      --save 60 10000
    volumes:
      - redis-data:/data                                        # 数据持久化
      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro   # 配置文件
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "$${REDIS_PASSWORD:-redispass}", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    restart: unless-stopped
    sysctls:
      net.core.somaxconn: 1024
    deploy:
      resources:
        limits:
          memory: 512M
    networks:
      - backend

# ==========================================
# 网络定义
# ==========================================
networks:
  frontend:
    name: flaskapp-frontend
    driver: bridge
  backend:
    name: flaskapp-backend
    driver: bridge
    internal: true

# ==========================================
# 数据卷定义
# ==========================================
volumes:
  mysql-data:
    name: flaskapp-mysql-data
  mysql-logs:
    name: flaskapp-mysql-logs
  mysql-backup:
    name: flaskapp-mysql-backup
  redis-data:
    name: flaskapp-redis-data
  nginx-logs:
    name: flaskapp-nginx-logs
  nginx-cache:
    name: flaskapp-nginx-cache
  flask-logs:
    name: flaskapp-flask-logs
  flask-uploads:
    name: flaskapp-flask-uploads

8.4 配置服务依赖与健康检查

在上方完整的docker-compose.yml中,我们已经配置了完整的服务依赖链和健康检查:

bash 复制代码
# 启动顺序(由depends_on + condition控制):
# 1. MySQL启动 -> 等待MySQL健康检查通过
# 2. Redis启动 -> 等待Redis健康检查通过
# 3. flask-migrate启动 -> 等待MySQL健康 -> 执行迁移 -> 成功退出
# 4. flask-app启动 -> 等待MySQL健康 + Redis健康 + 迁移完成
# 5. celery-worker启动 -> 等待MySQL健康 + Redis健康
# 6. celery-beat启动 -> 等待Redis健康
# 7. Nginx启动 -> 等待flask-app健康检查通过

8.5 数据初始化与迁移

bash 复制代码
# MySQL初始化脚本: ./mysql/init/01-create-tables.sql
CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(80) UNIQUE NOT NULL,
    email VARCHAR(120) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS posts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(200) NOT NULL,
    body TEXT,
    user_id INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

-- 创建只读用户(可选)
CREATE USER IF NOT EXISTS 'readonly'@'%' IDENTIFIED BY 'readonlypass';
GRANT SELECT ON flaskapp.* TO 'readonly'@'%';
FLUSH PRIVILEGES;

8.6 Nginx反向代理配置

nginx 复制代码
# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 2048;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # 日志格式
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';

    access_log /var/log/nginx/access.log main;

    # 基本优化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    keepalive_requests 1000;
    types_hash_max_size 2048;
    server_tokens off;
    client_max_body_size 50M;

    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;

    # 上游服务 - Flask应用
    upstream flask_backend {
        # 如果flask-app有多个副本,可以在这里配置负载均衡
        # server flask-app:5000 max_fails=3 fail_timeout=30s;
        # server flask-app-2:5000 max_fails=3 fail_timeout=30s;
        least_conn;
        server flask-app:5000 max_fails=3 fail_timeout=30s;
        keepalive 32;
    }

    # 包含站点配置
    include /etc/nginx/conf.d/*.conf;
}
nginx 复制代码
# nginx/conf.d/default.conf
server {
    listen 80;
    server_name localhost;

    # 健康检查端点
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }

    # API请求代理到Flask
    location /api/ {
        proxy_pass http://flask_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket支持
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # 超时设置
        proxy_connect_timeout 10s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # 健康检查代理
    location /health/api {
        proxy_pass http://flask_backend/health;
        access_log off;
    }

    # 静态文件
    location /static/ {
        alias /app/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

8.7 日志收集配置

yaml 复制代码
# 在docker-compose.yml中添加Fluentd日志收集
services:
  # ... 其他服务 ...

  # 日志收集器
  fluentd:
    image: fluent/fluentd:v1.16-debian
    container_name: flaskapp-fluentd
    volumes:
      - ./fluentd/fluent.conf:/fluentd/etc/fluent.conf:ro
      - fluentd-logs:/fluentd/log
    ports:
      - "24224:24224"
      - "24224:24224/udp"
    networks:
      - frontend
    restart: unless-stopped
    profiles: [monitoring]

# 修改各服务的日志配置,使用fluentd驱动
  flask-app:
    logging:
      driver: fluentd
      options:
        fluentd-address: localhost:24224
        tag: flaskapp.api
复制代码
# fluentd/fluent.conf
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<match flaskapp.**>
  @type file
  path /fluentd/log/flaskapp
  append true
  <format>
    @type json
  </format>
  <buffer>
    @type file
    path /fluentd/log/buffer
    flush_interval 5s
    chunk_limit_size 10m
  </buffer>
</match>

8.8 启动、测试与调试

bash 复制代码
# 1. 创建.env文件
cat > .env << 'EOF'
SECRET_KEY=your-super-secret-key-here
MYSQL_ROOT_PASSWORD=rootpassword123
DB_NAME=flaskapp
DB_USER=flaskuser
DB_PASSWORD=flaskpassword123
REDIS_PASSWORD=redispassword123
IMAGE_TAG=latest
BUILD_TARGET=production
FLASK_ENV=production
DEBUG=false
EOF

# 2. 验证配置
docker compose config -q && echo "Config OK" || echo "Config ERROR"

# 3. 构建镜像
docker compose build

# 4. 启动所有服务
docker compose up -d

# 5. 等待所有服务健康
docker compose up -d --wait

# 6. 查看服务状态
docker compose ps

# 7. 查看日志
docker compose logs -f flask-app
docker compose logs -f nginx

# 8. 测试访问
curl http://localhost/health
curl http://localhost/api/
curl http://localhost/health/api

# 9. 进入容器调试
docker compose exec flask-app bash
docker compose exec mysql mysql -u flaskuser -p flaskapp
docker compose exec redis redis-cli -a redispassword123

# 10. 执行数据库迁移
docker compose exec flask-app flask db upgrade

# 11. 创建管理员用户
docker compose exec flask-app flask create-admin

# 12. 查看Celery任务状态
docker compose exec celery-worker celery -A app.celery inspect active
docker compose exec celery-worker celery -A app.celery inspect registered

8.9 服务扩缩容测试

bash 复制代码
# 扩容Flask应用(需要先移除container_name)
# 修改docker-compose.yml,移除flask-app的container_name
# 然后扩容:
docker compose up -d --scale flask-app=3 --scale celery-worker=4

# 查看扩容后的状态
docker compose ps

# Nginx会自动负载均衡到3个flask-app实例
# (前提是Nginx配置中upstream使用了服务名)

# 缩容
docker compose up -d --scale flask-app=1 --scale celery-worker=2

8.10 完整项目文件结构

复制代码
flaskapp/
├── docker-compose.yml              # 主Compose文件
├── docker-compose.override.yml     # 开发覆盖(.gitignore)
├── docker-compose.prod.yml         # 生产覆盖
├── .env                            # 环境变量(.gitignore)
├── .env.example                    # 环境变量模板
├── Makefile                        # 命令简化
│
├── flask-app/                      # Flask应用
│   ├── Dockerfile                  # 多阶段构建
│   ├── requirements.txt            # Python依赖
│   ├── app.py                      # Flask主应用
│   ├── config.py                   # 配置文件
│   ├── models.py                   # 数据模型
│   ├── tasks.py                    # Celery任务
│   ├── migrations/                 # 数据库迁移
│   ├── static/                     # 静态文件
│   ├── templates/                  # 模板文件
│   ├── tests/                      # 测试文件
│   └── config/                     # 配置目录
│       ├── app.yml
│       └── logging.yml
│
├── nginx/                          # Nginx配置
│   ├── Dockerfile                  # Nginx Dockerfile
│   ├── nginx.conf                  # 主配置
│   ├── conf.d/                     # 站点配置
│   │   └── default.conf
│   └── ssl/                        # SSL证书(.gitignore)
│       ├── server.crt
│       └── server.key
│
├── mysql/                          # MySQL配置
│   ├── conf.d/
│   │   └── mysql.cnf               # MySQL配置
│   └── init/
│       └── 01-create-tables.sql    # 初始化SQL
│
├── redis/                          # Redis配置
│   └── redis.conf                  # Redis配置
│
├── fluentd/                        # 日志收集(可选)
│   └── fluent.conf
│
└── scripts/                        # 脚本
    ├── backup-db.sh                # 数据库备份
    ├── restore-db.sh               # 数据库恢复
    └── deploy.sh                   # 部署脚本

第九章 实战案例: 更多编排场景

本章提供多个贴近真实开发场景的Docker Compose编排案例,涵盖微服务、数据处理、监控和CI/CD等场景。

9.1 微服务架构编排

9.1.1 Spring Cloud微服务编排
yaml 复制代码
# docker-compose.microservices.yml
name: microservices

services:
  # ===== 服务注册与发现 =====
  eureka:
    image: steeltoeoss/eureka-server:latest
    container_name: eureka-server
    ports:
      - "8761:8761"
    environment:
      - EUREKA_SERVER_PORT=8761
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8761/health"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks:
      - micro-net
    restart: unless-stopped

  # ===== 配置中心 =====
  config-server:
    image: myorg/config-server:latest
    container_name: config-server
    ports:
      - "8888:8888"
    environment:
      - SPRING_PROFILES_ACTIVE=native
      - SPRING_CLOUD_CONFIG_SERVER_NATIVE_SEARCHLOCATIONS=/config-repo
    volumes:
      - ./config-repo:/config-repo:ro
    depends_on:
      eureka:
        condition: service_healthy
    networks:
      - micro-net
    restart: unless-stopped

  # ===== API网关 =====
  gateway:
    image: myorg/api-gateway:latest
    container_name: api-gateway
    ports:
      - "8080:8080"
    environment:
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka:8761/eureka/
      - SPRING_PROFILES_ACTIVE=prod
    depends_on:
      eureka:
        condition: service_healthy
      config-server:
        condition: service_started
    networks:
      - micro-net
    restart: unless-stopped

  # ===== 用户服务 =====
  user-service:
    image: myorg/user-service:latest
    environment:
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka:8761/eureka/
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/user_db?useSSL=false
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=rootpassword
      - SPRING_REDIS_HOST=redis
      - SPRING_REDIS_PORT=6379
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
      eureka:
        condition: service_healthy
    networks:
      - micro-net
      - data-net
    restart: unless-stopped
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 512M

  # ===== 订单服务 =====
  order-service:
    image: myorg/order-service:latest
    environment:
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka:8761/eureka/
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/order_db?useSSL=false
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=rootpassword
    depends_on:
      mysql:
        condition: service_healthy
      eureka:
        condition: service_healthy
    networks:
      - micro-net
      - data-net
    restart: unless-stopped
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 512M

  # ===== 商品服务 =====
  product-service:
    image: myorg/product-service:latest
    environment:
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka:8761/eureka/
      - SPRING_DATA_MONGODB_URI=mongodb://mongodb:27017/product_db
    depends_on:
      mongodb:
        condition: service_started
      eureka:
        condition: service_healthy
    networks:
      - micro-net
      - data-net
    restart: unless-stopped

  # ===== MySQL =====
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
    volumes:
      - mysql-data:/var/lib/mysql
      - ./mysql/init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      retries: 10
    networks:
      - data-net
    restart: unless-stopped

  # ===== MongoDB =====
  mongodb:
    image: mongo:7
    volumes:
      - mongo-data:/data/db
    networks:
      - data-net
    restart: unless-stopped

  # ===== Redis =====
  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      retries: 3
    networks:
      - data-net
    restart: unless-stopped

  # ===== 链路追踪 =====
  zipkin:
    image: openzipkin/zipkin:latest
    ports:
      - "9411:9411"
    networks:
      - micro-net
    restart: unless-stopped

networks:
  micro-net:
    driver: bridge
  data-net:
    driver: bridge
    internal: true

volumes:
  mysql-data:
  mongo-data:
  redis-data:

9.2 数据处理管道编排(Kafka + Spark + Elasticsearch)

yaml 复制代码
# docker-compose.data-pipeline.yml
name: data-pipeline

services:
  # ===== Zookeeper(Kafka依赖) =====
  zookeeper:
    image: confluentinc/cp-zookeeper:7.5.0
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    volumes:
      - zookeeper-data:/var/lib/zookeeper
    networks:
      - pipeline-net
    restart: unless-stopped

  # ===== Kafka消息队列 =====
  kafka:
    image: confluentinc/cp-kafka:7.5.0
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
    volumes:
      - kafka-data:/var/lib/kafka
    healthcheck:
      test: ["CMD", "kafka-topics", "--bootstrap-server", "localhost:9092", "--list"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - pipeline-net
    restart: unless-stopped

  # ===== Kafka管理界面 =====
  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    ports:
      - "8080:8080"
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092
    depends_on:
      kafka:
        condition: service_healthy
    networks:
      - pipeline-net
    profiles: [dev]

  # ===== Spark Master =====
  spark-master:
    image: bitnami/spark:3.5
    environment:
      - SPARK_MODE=master
      - SPARK_RPC_AUTHENTICATION_ENABLED=no
      - SPARK_RPC_ENCRYPTION_ENABLED=no
    ports:
      - "7077:7077"
      - "8081:8080"
    volumes:
      - ./spark/jobs:/opt/bitnami/spark/jobs
      - ./spark/config:/opt/bitnami/spark/conf
    networks:
      - pipeline-net
    restart: unless-stopped

  # ===== Spark Worker =====
  spark-worker:
    image: bitnami/spark:3.5
    environment:
      - SPARK_MODE=worker
      - SPARK_MASTER_URL=spark://spark-master:7077
      - SPARK_WORKER_MEMORY=2G
      - SPARK_WORKER_CORES=2
    depends_on:
      - spark-master
    volumes:
      - ./spark/jobs:/opt/bitnami/spark/jobs
    networks:
      - pipeline-net
    restart: unless-stopped
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 3G

  # ===== Elasticsearch =====
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    volumes:
      - es-data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:9200/_cluster/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    networks:
      - pipeline-net
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 2G

  # ===== Kibana可视化 =====
  kibana:
    image: docker.elastic.co/kibana/kibana:8.11.0
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      elasticsearch:
        condition: service_healthy
    networks:
      - pipeline-net
    restart: unless-stopped

  # ===== 数据生产者(示例) =====
  data-producer:
    image: myorg/data-producer:latest
    build: ./producer
    environment:
      - KAFKA_BOOTSTRAP_SERVERS=kafka:29092
      - KAFKA_TOPIC=raw-data
    depends_on:
      kafka:
        condition: service_healthy
    networks:
      - pipeline-net
    restart: unless-stopped

  # ===== 数据消费者/处理器 =====
  data-processor:
    image: myorg/data-processor:latest
    build: ./processor
    environment:
      - KAFKA_BOOTSTRAP_SERVERS=kafka:29092
      - KAFKA_INPUT_TOPIC=raw-data
      - KAFKA_OUTPUT_TOPIC=processed-data
      - ES_HOST=elasticsearch:9200
      - SPARK_MASTER=spark://spark-master:7077
    depends_on:
      kafka:
        condition: service_healthy
      elasticsearch:
        condition: service_healthy
      spark-master:
        condition: service_started
    networks:
      - pipeline-net
    restart: unless-stopped

networks:
  pipeline-net:
    driver: bridge

volumes:
  zookeeper-data:
  kafka-data:
  es-data:

9.3 监控栈编排(Prometheus + Grafana + AlertManager)

yaml 复制代码
# docker-compose.monitoring.yml
name: monitoring

services:
  # ===== Prometheus监控 =====
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./prometheus/rules:/etc/prometheus/rules:ro
      - ./prometheus/alerts:/etc/prometheus/alerts:ro
      - prometheus-data:/prometheus
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.path=/prometheus"
      - "--storage.tsdb.retention.time=30d"
      - "--web.enable-lifecycle"
      - "--web.enable-admin-api"
    networks:
      - monitoring-net
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:9090/-/healthy"]
      interval: 30s
      timeout: 5s
      retries: 3

  # ===== Grafana可视化 =====
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
      - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
    environment:
      - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER:-admin}
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin}
      - GF_USERS_ALLOW_SIGN_UP=false
      - GF_SERVER_ROOT_URL=http://localhost:3000
      - GF_INSTALL_PLUGINS=grafana-piechart-panel
    depends_on:
      - prometheus
    networks:
      - monitoring-net
    restart: unless-stopped

  # ===== AlertManager告警 =====
  alertmanager:
    image: prom/alertmanager:latest
    container_name: alertmanager
    ports:
      - "9093:9093"
    volumes:
      - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
      - alertmanager-data:/alertmanager
    command:
      - "--config.file=/etc/alertmanager/alertmanager.yml"
      - "--storage.path=/alertmanager"
    networks:
      - monitoring-net
    restart: unless-stopped

  # ===== Node Exporter(主机指标) =====
  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - "--path.procfs=/host/proc"
      - "--path.rootfs=/rootfs"
      - "--path.sysfs=/host/sys"
      - "--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)"
    networks:
      - monitoring-net
    restart: unless-stopped

  # ===== cAdvisor(容器指标) =====
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    ports:
      - "8080:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker:/var/lib/docker:ro
      - /dev/disk:/dev/disk:ro
    networks:
      - monitoring-net
    restart: unless-stopped

  # ===== Blackbox Exporter(网络探测) =====
  blackbox-exporter:
    image: prom/blackbox-exporter:latest
    container_name: blackbox-exporter
    ports:
      - "9115:9115"
    volumes:
      - ./blackbox/blackbox.yml:/etc/blackbox_exporter/config.yml:ro
    networks:
      - monitoring-net
    restart: unless-stopped

networks:
  monitoring-net:
    driver: bridge

volumes:
  prometheus-data:
  grafana-data:
  alertmanager-data:
yaml 复制代码
# prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - alertmanager:9093

rule_files:
  - /etc/prometheus/rules/*.yml
  - /etc/prometheus/alerts/*.yml

scrape_configs:
  # Prometheus自身
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  # Node Exporter(主机指标)
  - job_name: "node-exporter"
    static_configs:
      - targets: ["node-exporter:9100"]

  # cAdvisor(容器指标)
  - job_name: "cadvisor"
    static_configs:
      - targets: ["cadvisor:8080"]

  # 应用服务(如果有)
  - job_name: "app-services"
    static_configs:
      - targets: ["app:5000"]
    metrics_path: /metrics

  # Blackbox探测
  - job_name: "blackbox"
    metrics_path: /probe
    params:
      module: [http_2xx]
    static_configs:
      - targets:
          - http://localhost:80
          - http://localhost:3000
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: blackbox-exporter:9115

9.4 CI/CD工具链编排(Jenkins + SonarQube + Nexus)

yaml 复制代码
# docker-compose.cicd.yml
name: cicd

services:
  # ===== Jenkins CI/CD =====
  jenkins:
    image: jenkins/jenkins:lts-jdk17
    container_name: jenkins
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - jenkins-data:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
      - ./jenkins/casc:/var/jenkins_home/casc:ro
    environment:
      - JAVA_OPTS=-Djenkins.install.runSetupWizard=false
      - CASC_JENKINS_CONFIG=/var/jenkins_home/casc
    user: root
    networks:
      - cicd-net
    restart: unless-stopped

  # ===== SonarQube代码质量 =====
  sonarqube:
    image: sonarqube:community
    container_name: sonarqube
    ports:
      - "9000:9000"
    environment:
      - SONAR_JDBC_URL=jdbc:postgresql://sonar-db:5432/sonar
      - SONAR_JDBC_USERNAME=sonar
      - SONAR_JDBC_PASSWORD=sonarpass
      - SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true
    volumes:
      - sonarqube-data:/opt/sonarqube/data
      - sonarqube-logs:/opt/sonarqube/logs
      - sonarqube-extensions:/opt/sonarqube/extensions
    depends_on:
      sonar-db:
        condition: service_healthy
    sysctls:
      - net.core.somaxconn=1024
      - fs.file-max=131072
    ulimits:
      nofile:
        soft: 131072
        hard: 131072
      nproc: 8192
    networks:
      - cicd-net
    restart: unless-stopped

  # ===== SonarQube数据库 =====
  sonar-db:
    image: postgres:16-alpine
    container_name: sonar-db
    environment:
      POSTGRES_DB: sonar
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonarpass
    volumes:
      - sonar-db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U sonar"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - cicd-net
    restart: unless-stopped

  # ===== Nexus制品仓库 =====
  nexus:
    image: sonatype/nexus3:latest
    container_name: nexus
    ports:
      - "8081:8081"
    volumes:
      - nexus-data:/nexus-data
    environment:
      - INSTALL4J_ADD_VM_PARAMS=-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m
    networks:
      - cicd-net
    restart: unless-stopped
    user: nexus

networks:
  cicd-net:
    driver: bridge

volumes:
  jenkins-data:
  sonarqube-data:
  sonarqube-logs:
  sonarqube-extensions:
  sonar-db-data:
  nexus-data:

9.5 开发环境编排(包含热重载、调试工具)

yaml 复制代码
# docker-compose.dev.yml - 完整开发环境
name: dev-environment

services:
  # ===== 前端开发服务器(热重载) =====
  frontend-dev:
    image: node:20-alpine
    container_name: frontend-dev
    working_dir: /app
    volumes:
      - ./frontend:/app                    # 挂载源代码
      - frontend-node-modules:/app/node_modules  # 持久化node_modules
    command: >
      sh -c "
      npm install &&
      npm run dev -- --host 0.0.0.0 --port 3000
      "
    ports:
      - "3000:3000"
      - "3001:3001"                        # HMR WebSocket
    environment:
      - NODE_ENV=development
      - CHOKIDAR_USEPOLLING=true           # 文件监听(macOS/Win需要)
      - WATCHPACK_POLLING=true
    networks:
      - dev-net
    restart: unless-stopped

  # ===== 后端开发服务器(热重载) =====
  backend-dev:
    image: python:3.11-slim
    container_name: backend-dev
    working_dir: /app
    volumes:
      - ./backend:/app                     # 挂载源代码
    command: >
      bash -c "
      pip install -r requirements.txt &&
      pip install debugpy &&
      python -m debugpy --listen 0.0.0.0:5678 --wait-for-client
      -m flask run --host=0.0.0.0 --port=5000 --debug
      "
    ports:
      - "5000:5000"
      - "5678:5678"                        # 调试端口
    environment:
      - FLASK_APP=app.py
      - FLASK_ENV=development
      - FLASK_DEBUG=1
      - DATABASE_URL=postgresql://devuser:devpass@dev-db:5432/devdb
      - REDIS_URL=redis://dev-redis:6379/0
    depends_on:
      dev-db:
        condition: service_healthy
      dev-redis:
        condition: service_started
    networks:
      - dev-net
    restart: unless-stopped

  # ===== 开发数据库 =====
  dev-db:
    image: postgres:16-alpine
    container_name: dev-db
    ports:
      - "5432:5432"                        # 暴露端口用于本地工具连接
    environment:
      POSTGRES_DB: devdb
      POSTGRES_USER: devuser
      POSTGRES_PASSWORD: devpass
    volumes:
      - dev-db-data:/var/lib/postgresql/data
      - ./db/dev-init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U devuser"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - dev-net
    restart: unless-stopped

  # ===== 开发Redis =====
  dev-redis:
    image: redis:7-alpine
    container_name: dev-redis
    ports:
      - "6379:6379"
    volumes:
      - dev-redis-data:/data
    networks:
      - dev-net
    restart: unless-stopped

  # ===== 数据库管理工具 =====
  adminer:
    image: adminer:latest
    container_name: adminer
    ports:
      - "8081:8080"
    depends_on:
      - dev-db
    networks:
      - dev-net
    restart: unless-stopped

  # ===== Redis管理工具 =====
  redis-insight:
    image: redislabs/redisinsight:latest
    container_name: redis-insight
    ports:
      - "8001:8001"
    depends_on:
      - dev-redis
    networks:
      - dev-net
    restart: unless-stopped

  # ===== 邮件测试工具 =====
  mailhog:
    image: mailhog/mailhog
    container_name: mailhog
    ports:
      - "1025:1025"                        # SMTP端口
      - "8025:8025"                        # Web界面
    networks:
      - dev-net
    restart: unless-stopped

  # ===== 调试工具容器 =====
  debug-tools:
    image: nicolaka/netshoot
    container_name: debug-tools
    command: sleep infinity
    networks:
      - dev-net
    restart: unless-stopped
    profiles: [debug]

networks:
  dev-net:
    driver: bridge

volumes:
  frontend-node-modules:
  dev-db-data:
  dev-redis-data:

9.6 每个案例的完整compose文件与解析

以上每个案例都是可以直接使用的完整Compose文件。下面是使用说明:

bash 复制代码
# 微服务编排
docker compose -f docker-compose.microservices.yml up -d

# 数据处理管道
docker compose -f docker-compose.data-pipeline.yml up -d

# 监控栈
docker compose -f docker-compose.monitoring.yml up -d

# CI/CD工具链
docker compose -f docker-compose.cicd.yml up -d

# 开发环境
docker compose -f docker-compose.dev.yml up -d

# 开发环境 + 调试工具
docker compose -f docker-compose.dev.yml --profile debug up -d

第十章 Compose最佳实践与进阶

10.1 compose文件编写规范

yaml 复制代码
# 1. 使用Compose Specification格式(不指定version)
# 2. 文件名使用compose.yaml(推荐)或docker-compose.yml
# 3. 使用2个空格缩进
# 4. 端口映射使用引号: "8080:80"
# 5. 版本号使用引号: "3.8"
# 6. 使用注释说明每个服务的作用
# 7. 使用YAML锚点复用配置
# 8. 敏感信息使用.env文件或secrets

# 规范示例:
name: myapp  # 项目名称

# 可复用配置
x-default-logging: &default-logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "5"

x-default-restart: &default-restart
  restart: unless-stopped

x-default-healthcheck: &default-healthcheck
  interval: 30s
  timeout: 10s
  retries: 3

services:
  web:
    <<: *default-restart
    image: nginx:1.25-alpine
    ports:
      - "${WEB_PORT:-80}:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    logging: *default-logging
    healthcheck:
      <<: *default-healthcheck
      test: ["CMD", "wget", "--spider", "-q", "http://localhost/health"]
    networks:
      - frontend

10.2 使用Compose Specification(统一规范)

Compose Specification是Docker官方推出的统一Compose文件规范,不再使用version字段:

yaml 复制代码
# Compose Specification格式(推荐)
# 不需要version字段
# 所有配置项都是最新的

services:
  app:
    image: myapp:latest
    # 支持所有最新特性
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: ./package.json

10.3 Compose与Docker Swarm集成

bash 复制代码
# 将Compose文件部署到Swarm
docker stack deploy -c docker-compose.yml myapp

# 查看部署状态
docker stack services myapp

# 更新服务
docker service update --image myapp/api:v2 myapp_api

# 扩缩容
docker service scale myapp_api=5

# 删除部署
docker stack rm myapp

10.4 Compose与Kubernetes转换(kompose工具)

bash 复制代码
# 安装kompose
# Linux:
curl -L https://github.com/kubernetes/kompose/releases/download/v1.31.2/kompose-linux-amd64 -o kompose
chmod +x kompose
sudo mv kompose /usr/local/bin/

# 将Compose文件转换为Kubernetes资源
kompose convert -f docker-compose.yml

# 输出:
# deployment-app.yaml
# service-app.yaml
# deployment-db.yaml
# service-db.yaml
# persistentvolumeclaim-db-data.yaml

# 直接部署到Kubernetes
kompose up -f docker-compose.yml

# 从Kubernetes删除
kompose down -f docker-compose.yml
yaml 复制代码
# kompose转换注解示例
services:
  api:
    image: myapp:latest
    ports:
      - "8080:8080"
    # kompose注解
    labels:
      kompose.service.type: LoadBalancer    # 创建LoadBalancer类型Service
      kompose.volume.size: 1Gi              # PVC大小
      kompose.controller.type: deployment   # 控制器类型

10.5 CI/CD中使用Docker Compose

yaml 复制代码
# .github/workflows/test.yml (GitHub Actions示例)
name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and start services
        run: |
          docker compose -f docker-compose.test.yml build
          docker compose -f docker-compose.test.yml up -d --wait

      - name: Run tests
        run: |
          docker compose -f docker-compose.test.yml exec -T api pytest -v --cov=app

      - name: Run linting
        run: |
          docker compose -f docker-compose.test.yml exec -T api flake8 app/
          docker compose -f docker-compose.test.yml exec -T api mypy app/

      - name: Tear down
        if: always()
        run: |
          docker compose -f docker-compose.test.yml down -v
yaml 复制代码
# GitLab CI示例(.gitlab-ci.yml)
test:
  image: docker:24.0
  services:
    - docker:24.0-dind
  script:
    - docker compose -f docker-compose.test.yml build
    - docker compose -f docker-compose.test.yml up -d --wait
    - docker compose -f docker-compose.test.yml exec -T api pytest -v
  after_script:
    - docker compose -f docker-compose.test.yml down -v

10.6 性能优化建议

yaml 复制代码
# 1. 使用alpine镜像减小体积
services:
  web:
    image: nginx:1.25-alpine     # 比nginx:1.25小很多

# 2. 使用多阶段构建减小镜像体积
services:
  api:
    build:
      context: .
      target: production          # 只构建生产阶段

# 3. 合理设置资源限制
services:
  api:
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 128M

# 4. 使用构建缓存
services:
  api:
    build:
      context: .
      cache_from:
        - type: registry
          ref: myapp/api:cache

# 5. 日志大小限制(防止日志撑满磁盘)
x-logging: &default-logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "5"

# 6. 使用init进程(避免僵尸进程)
services:
  api:
    init: true

# 7. 调整DNS和hosts
services:
  api:
    dns:
      - 8.8.8.8
    extra_hosts:
      - "host.docker.internal:host-gateway"

10.7 安全最佳实践

yaml 复制代码
# 1. 使用非root用户
services:
  api:
    user: "1000:1000"
    # 或在Dockerfile中创建用户

# 2. 最小权限原则
services:
  api:
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp
      - /run

# 3. 不在Compose文件中硬编码密码
services:
  db:
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
      # 使用环境变量或secrets

# 4. 使用secrets管理敏感数据
services:
  db:
    secrets:
      - db_password
secrets:
  db_password:
    file: ./secrets/db_password.txt

# 5. 限制网络访问
services:
  db:
    networks:
      - backend           # 内部网络
    # 不暴露端口到主机

# 6. 使用镜像摘要(确保不可变)
services:
  api:
    image: myapp@sha256:abc123...
    # 而不是 myapp:latest

# 7. 定期更新基础镜像
services:
  api:
    image: python:3.11-slim
    pull_policy: always    # 每次拉取最新

10.8 常见问题排查

问题1: 服务启动顺序错误
bash 复制代码
# 问题: API服务在数据库启动前就尝试连接,导致连接失败
# 解决: 使用depends_on + healthcheck

services:
  api:
    depends_on:
      db:
        condition: service_healthy    # 等待数据库健康检查通过
  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 5s
      retries: 10
问题2: 端口冲突
bash 复制代码
# 问题: 端口被占用
# Error starting userland proxy: Bind for 0.0.0.0:80: unexpected error

# 排查:
sudo lsof -i :80          # 查看占用80端口的进程
sudo netstat -tlnp | grep :80

# 解决: 更换端口或停止占用进程
问题3: 数据卷权限问题
bash 复制代码
# 问题: 容器内无法读写挂载的目录
# 原因: 容器内用户UID与主机文件所有者UID不匹配

# 解决方案1: 在Dockerfile中创建与主机相同UID的用户
RUN useradd -u 1000 -m appuser
USER appuser

# 解决方案2: 使用命名卷代替绑定挂载
volumes:
  - app-data:/app/data    # 命名卷由Docker管理权限

# 解决方案3: 修改主机目录权限
chmod -R 777 ./data       # 不推荐,仅开发环境
问题4: 网络连接问题
bash 复制代码
# 问题: 服务间无法通信
# 排查:

# 1. 确认服务在同一网络
docker network inspect myapp_default

# 2. 确认服务名正确
docker compose exec api ping db

# 3. 查看DNS解析
docker compose exec api nslookup db

# 4. 查看容器IP
docker compose exec api ip addr
问题5: 构建缓存失效
bash 复制代码
# 问题: 每次构建都从头开始,速度很慢
# 原因: Dockerfile中COPY指令导致缓存失效

# 解决: 将不常变化的文件放在前面
# 好的Dockerfile顺序:
COPY requirements.txt .        # 先复制依赖文件
RUN pip install -r requirements.txt  # 安装依赖(缓存)
COPY . .                       # 最后复制代码(经常变化)

# 使用BuildKit缓存
docker compose build --build-arg BUILDKIT_CACHE_MOUNT_NS=app
问题6: 容器不停重启
bash 复制代码
# 排查: 查看容器日志
docker compose logs -f api

# 查看退出码
docker compose ps -a
# 退出码:
# 0: 正常退出
# 1: 应用错误
# 137: OOM(内存不足)
# 139: 段错误
# 143: 正常终止(SIGTERM)

# 查看容器事件
docker events --filter container=api

10.9 本章总结与下一期预告

通过本章的学习,我们掌握了Docker Compose的最佳实践、安全配置、性能优化和常见问题排查。以下是要点总结:

  1. 编写规范: 使用Compose Specification格式,合理使用YAML锚点复用配置
  2. 安全实践: 非root运行、最小权限、secrets管理、网络隔离
  3. 性能优化: alpine镜像、多阶段构建、资源限制、日志限制
  4. CI/CD集成: 在GitHub Actions和GitLab CI中使用Compose
  5. 问题排查: 启动顺序、端口冲突、权限问题、网络连接、构建缓存

在Docker专栏的下一篇文章中,我们将学习Docker容器监控与日志管理,深入讲解如何使用Prometheus、Grafana、ELK/EFK等工具对Docker容器进行全面的监控和日志收集,敬请期待。


总结

Docker Compose是容器编排领域不可或缺的工具。它通过简洁的YAML文件,将复杂的多容器应用管理变得简单而优雅。从开发环境的一键启动,到CI/CD流水线的自动化测试,再到单机生产部署,Docker Compose都能胜任。

本文从Docker Compose的基本概念出发,全面深入地讲解了:

  • 核心概念: Compose的定义、与Docker CLI的区别、版本演进
  • 文件结构: YAML语法、顶层结构、services/networks/volumes/configs/secrets
  • 配置详解: 所有services配置项的详细用法,包括镜像、构建、网络、存储、健康检查、部署、日志、安全等
  • 命令操作: 所有docker compose命令的完整参数和实际用法
  • 环境管理: .env文件、变量插值、多环境配置、profiles
  • 网络与存储: 网络隔离、外部网络、数据卷驱动、NFS支持
  • 实战案例: Flask+MySQL+Redis+Celery完整编排、微服务架构、数据处理管道、监控栈、CI/CD工具链、开发环境
  • 最佳实践: 编写规范、安全配置、性能优化、CI/CD集成、问题排查

掌握Docker Compose,你就能以声明式的方式管理复杂的多容器应用,让开发更高效、部署更可靠、运维更轻松。当你的应用规模增长到单机无法承载时,Docker Compose文件还可以通过kompose工具转换为Kubernetes资源,实现从单机到集群的平滑过渡。

相关推荐
brave_zhao3 小时前
vetur是什么
学习
沐苏瑶3 小时前
计算机网络核心笔记:打通 TCP/UDP 与 HTTP/HTTPS 底层逻辑(重点下)
笔记·计算机网络·http
GlueNa2SiO33 小时前
01-Docker入门与核心概念
笔记·docker·容器
lifallen3 小时前
edit-article:AI 味来自跳级
人工智能·学习·ai·ai编程·ai写作
A-刘晨阳3 小时前
Kubernetes 非共享存储详解:emptyDir 与 hostPath 从入门到实践
运维·云原生·容器·kubernetes·hostpath·emptydir
Brilliantwxx4 小时前
【Linux】 开发|Git 版本控制与 gdb/cgdb 调试
linux·笔记·git
xian_wwq4 小时前
【学习笔记】工具设计,Agent 的手比大脑更容易出问题-8/16
笔记·学习·agent
晴天164 小时前
Agent 全栈学习笔记 1-Day14
数据库·笔记·学习
风曦Kisaki4 小时前
# Kubernetes(K8s)笔记Day15:K8s网络工作原理【Pod 网络模型,CNI 网络插件,Calico 工作模式详解,BGP协议,路由反射器】
运维·网络·nginx·云原生·容器·kubernetes