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年及以后),版本选择非常简单:
-
推荐使用Compose Specification格式 : 不指定
version字段,直接以services开头。这是Docker官方当前推荐的格式,兼容Docker Compose v2。 -
如果必须指定版本 : 使用
version: "3.8",这是v3系列的最后一个版本,功能最全。 -
不要使用v1格式: 已完全废弃,新版本Docker Compose可能不再支持。
-
文件名选择 : 推荐使用
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)
优先级规则(从高到低):
environment中的内联变量(最高优先级)env_file中最后引用的文件env_file中最先引用的文件- 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: 暴露端口(仅容器间)
expose与ports不同,它只在容器间暴露端口,不会映射到主机。
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)模式中,大部分配置会被忽略,但resources的limits在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.resources的limits会生效:
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.yml和docker-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的最佳实践、安全配置、性能优化和常见问题排查。以下是要点总结:
- 编写规范: 使用Compose Specification格式,合理使用YAML锚点复用配置
- 安全实践: 非root运行、最小权限、secrets管理、网络隔离
- 性能优化: alpine镜像、多阶段构建、资源限制、日志限制
- CI/CD集成: 在GitHub Actions和GitLab CI中使用Compose
- 问题排查: 启动顺序、端口冲突、权限问题、网络连接、构建缓存
在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资源,实现从单机到集群的平滑过渡。