IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。
在第 11 篇中,我们第一次接触了 docker-compose.yml,体验了从"手动敲 8 条命令"到"一条 docker compose up -d 启动整个应用栈"的效率跃升。但当时我们只是"照葫芦画瓢"地写了一份配置文件------对每一行配置的含义、最佳实践和灵活用法还没有深入展开。
今天,我们就来彻底拆解 Compose 文件,把 services、networks、volumes 这三大核心模块吃透。掌握了这些,当你后面进入 Kubernetes 看到 Service、Ingress、ConfigMap、PersistentVolumeClaim 时,会发现它们的设计思路一脉相承------都是从"手动管理资源"到"声明式管理资源"的进化。
一、Compose 文件结构总览
一个完整的 Compose 文件由三个顶级元素构成:
bash
services: # 定义应用服务
...
networks: # 定义网络
...
volumes: # 定义数据卷
...
此外还有 configs 和 secrets(用于 Swarm 模式,本篇暂不涉及)。我们重点解析前三个。
1.1 项目名称与资源命名
Compose 管理的所有资源(容器、网络、卷)都会自动加上项目名称作为前缀,避免不同项目之间命名冲突。默认的项目名称是 docker-compose.yml 所在目录的名称。例如目录名为 flask-redis-counter,那么:
-
网络
app-net实际创建为flask-redis-counter_app-net -
卷
redis-data实际创建为flask-redis-counter_redis-data -
容器
redis实际名称为redis(因为显式指定了container_name,如果未指定,则为项目名-服务名-序号)
你可以通过 -p 参数或 COMPOSE_PROJECT_NAME 环境变量自定义项目名称:
bash
docker compose -p myproject up -d
二、services 详解:服务的核心配置
services 是 Compose 文件中最核心的模块,每个服务可以配置的选项非常多。我将按功能分类逐一拆解,并用我们贯穿的 Flask + Redis 应用来演示。
2.1 镜像与构建
指定服务使用的镜像有两种方式:
直接使用已有镜像:
bash
services:
redis:
image: redis:alpine # 从仓库拉取
从 Dockerfile 构建:
bash
services:
flask-app:
build: . # 使用当前目录的 Dockerfile
# 或者更详细的写法
build:
context: ./app # 构建上下文路径
dockerfile: Dockerfile.dev # 指定 Dockerfile 文件名
args: # 构建参数
- ENV=production
如果同时指定了 build 和 image,Compose 会用 image 的值作为构建后的镜像名称(相当于自动执行 docker build -t <image> .):
bash
services:
flask-app:
build: .
image: flask-redis-counter:latest
2.2 容器名称与重启策略
bash
services:
flask-app:
container_name: flask-app # 固定容器名(不指定则自动生成)
restart: unless-stopped # 重启策略
重启策略与 docker run --restart 一致,可选值:no、always、on-failure、unless-stopped。
注意 :如果计划使用 --scale 扩容,不要 指定固定的 container_name,因为每个副本必须有不同的名称。扩容场景下应让 Compose 自动生成名称。
2.3 端口映射
bash
services:
flask-app:
ports:
- "5000:5000" # 宿主机端口:容器端口
- "5001:5000" # 可以映射多个端口
- "127.0.0.1:5002:5000" # 只绑定到宿主机特定 IP
等价于 docker run -p 5000:5000。
2.4 环境变量
bash
services:
flask-app:
environment:
- FLASK_ENV=production
- REDIS_HOST=redis
- DB_PASSWORD=secret123 # ⚠️ 不要在 YAML 中硬编码敏感信息
# 或从文件加载
env_file:
- .env
直接写在 YAML 中的环境变量会被提交到 Git 仓库,敏感信息(密码、API 密钥)不应硬编码。更好的方式是使用 .env 文件或 Docker Secrets(Swarm 模式),这将在第 13 篇和第 33 篇中专门展开。
2.5 数据卷挂载
bash
services:
flask-app:
volumes:
# 命名卷挂载
- flask-logs:/app/logs
# Bind Mount(宿主机绝对路径)
- /home/user/config:/app/config:ro
# 相对路径 Bind Mount(相对于 Compose 文件所在目录)
- ./data:/app/data
# 匿名卷
- /app/tmp
-
第一行对应
docker run -v flask-logs:/app/logs(命名卷,生产环境推荐) -
第二行对应
docker run -v /home/user/config:/app/config:ro(Bind Mount,开发环境常用) -
第三行是相对路径 Bind Mount,
./data相对于 docker-compose.yml 所在目录 -
第四行只指定容器内路径,Docker 自动创建匿名卷
挂载目录的注意事项 :当使用 Bind Mount 时,宿主机目录会完全覆盖容器内对应路径。如果宿主机目录为空,容器内原来镜像中的文件会被"隐藏"。这常导致"挂载后服务启动失败"的困惑------比如挂载了一个空的 ./config 到容器的 /etc/nginx/conf.d,Nginx 就找不到任何配置文件了。
2.6 网络配置
bash
services:
flask-app:
networks:
- app-net
- monitor-net # 可以连接多个网络
# 也可以为每个网络指定别名
networks:
app-net:
aliases:
- web
- api
网络别名(aliases)让其他容器可以通过多个名称访问这个服务,等价于 docker run --network-alias。
2.7 启动命令与入口点
bash
services:
redis:
command: redis-server --appendonly yes --maxmemory 256mb
# 或者用数组格式(推荐)
command: ["redis-server", "--appendonly", "yes"]
command 会覆盖镜像默认的 CMD,但不覆盖 ENTRYPOINT。
2.8 健康检查
bash
services:
redis:
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
等价于 Dockerfile 中的 HEALTHCHECK 指令,但 Compose 中定义可以覆盖镜像内置的检查规则。
2.9 服务依赖与启动顺序
bash
services:
flask-app:
depends_on:
redis:
condition: service_healthy # 等待 Redis 健康检查通过
db:
condition: service_started # 仅等待 db 容器启动(不等服务就绪)
三种条件:
-
service_started:仅等待容器启动 -
service_healthy:等待健康检查通过(推荐) -
service_completed_successfully:等待一次性任务完成(如数据库迁移),退出码为 0
depends_on只控制启动顺序,不保证依赖服务完全就绪 。这就是为什么 condition: service_healthy 如此重要------它把"启动顺序"和"服务就绪"绑定在了一起。
2.10 资源限制(Compose v2+)
bash
services:
flask-app:
deploy:
resources:
limits:
cpus: '0.50' # 最多使用 50% 单核 CPU
memory: 256M # 内存上限
reservations:
cpus: '0.25' # 至少保留 25% 单核 CPU
memory: 128M # 至少保留内存
注意 :deploy.resources 在 docker compose up 中会被忽略 ,仅在 Swarm 模式(docker stack deploy)下生效。单机 Compose 场景下如需限制资源,请使用 docker run 的 --cpus 和 --memory 参数,或者在 Compose 文件中使用已弃用但依然有效的 V2 格式的 mem_limit 和 cpus(不推荐,兼容性差)。在实际生产环境中,资源限制应交给 Kubernetes 的 ResourceQuota 和 LimitRange 管理。
2.11 完整示例:Flask + Redis 的 services 配置
综合以上知识,我们把 Flask + Redis 应用的 services 部分打磨得更完善:
bash
services:
redis:
image: redis:alpine
restart: unless-stopped
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
networks:
- app-net
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
flask-app:
image: flask-redis-counter:2.0
restart: unless-stopped
ports:
- "5000:5000"
environment:
- FLASK_ENV=production
volumes:
- flask-logs:/app/logs
networks:
- app-net
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 5s
三、networks 详解:自定义网络配置
Compose 默认会为每个项目创建一个默认网络(项目名_default),所有未显式声明 networks 的服务都会连接到这个默认网络。但我们强烈建议显式声明自定义网络,以获得更好的控制和隔离性。
bash
networks:
app-net:
driver: bridge # 默认驱动,单机桥接网络
frontend:
driver: bridge
backend:
driver: bridge
3.1 自定义子网和网关
bash
networks:
app-net:
driver: bridge
ipam:
config:
- subnet: 172.25.0.0/16
gateway: 172.25.0.1
这在你需要固定 IP 段、与外部系统对接时非常有用。
3.2 外部网络
有时候你希望容器连接到一个已经存在的 Docker 网络(由其他项目或手动创建),而非由 Compose 自动创建:
bash
networks:
existing-net:
external: true # 表示该网络已在外部创建
然后在服务中引用:
bash
services:
flask-app:
networks:
- existing-net
external: true 告诉 Compose:"不要创建新网络,去连接那个已经存在的网络"。如果网络不存在,docker compose up 会报错。
3.3 网络别名
在同一网络中,可以通过 aliases 为服务定义额外的 DNS 名称:
bash
services:
redis:
networks:
app-net:
aliases:
- cache
- redis-cache
现在,其他容器可以通过 cache 或 redis-cache 来解析到这个 Redis 容器,实现了简单的"服务发现别名"。在 K8s 中,这对应着 Service 的 name 和 spec.clusterIP 的 DNS A 记录。
四、volumes 详解:持久化存储配置
bash
volumes:
redis-data: # 命名卷,Docker 自动管理存储路径
flask-logs:
4.1 卷驱动
默认使用 local 驱动(存储在宿主机本地磁盘)。也可以使用第三方驱动,如 NFS、AWS EBS:
bash
volumes:
nfs-data:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.100,nolock,soft,rw
device: ":/export/data"
4.2 外部卷
和外部网络类似,可以使用已有的卷:
bash
volumes:
existing-data:
external: true
这对于数据迁移或跨项目共享数据卷很有用。
五、完整的 docker-compose.yml 最终版
综合所有知识,我们为 Flask + Redis 计数器应用编写一份生产就绪的 Compose 文件:
bash
# ============================================================
# Flask + Redis 计数器应用 ------ Docker Compose 生产配置
# 系列贯穿案例 v3.0
# ============================================================
services:
redis:
image: redis:alpine
restart: unless-stopped
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
networks:
app-net:
aliases:
- cache
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
flask-app:
image: flask-redis-counter:2.0
restart: unless-stopped
ports:
- "5000:5000"
environment:
- FLASK_ENV=production
- REDIS_HOST=cache # 使用网络别名
volumes:
- flask-logs:/app/logs
networks:
- app-net
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 5s
volumes:
redis-data:
flask-logs:
networks:
app-net:
driver: bridge
ipam:
config:
- subnet: 172.25.0.0/16
5.1 启动与验证
bash
# 启动
docker compose up -d
# 查看资源
docker compose ps
docker network ls | grep app-net
docker volume ls | grep flask-redis-counter
# 测试别名
docker compose exec flask-app nslookup cache
六、命令速查表
七、本篇总结
-
services :每个服务涵盖镜像、端口、环境变量、卷挂载、网络、健康检查、依赖等全套配置,任何一个
docker run参数在 Compose 中都有对应的声明式写法。 -
networks :自定义网络提供了 DNS 解析和网络隔离,
aliases实现服务别名,external接入已存在的网络。 -
volumes:命名卷是生产持久化的首选,外部卷和第三方驱动扩展了存储能力。
-
演进视角 :你现在看到的
services、networks、volumes,在 Kubernetes 中会对应到Deployment、Service、PersistentVolumeClaim。Compose 是单机上的声明式编排,而 K8s 是集群级的声明式编排------核心理念完全一致,只是规模和抽象层次不同。
下一篇------第 13 篇:Compose 环境变量与配置管理,我们将深入探讨如何优雅地管理多环境配置,告别硬编码,让同一份 Compose 文件在开发、测试、生产环境中灵活切换。
想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !