Docker Compose 不只是开发环境的便利工具。配合高级语法和 Swarm 模式,它能胜任从小型集群到中等规模微服务的生产部署。本文是 Docker Compose 入门指南 的进阶篇,聚焦真实生产场景。
Compose File 高级语法
Compose v3+ 引入的 profiles 和 extends 让多环境配置管理变得清晰:
# docker-compose.yml
services:
web:
build: .
profiles: ["dev", "prod"]
extends:
file: common.yml
service: web-base
db:
image: postgres:15-alpine
profiles: ["prod"]
# 仅在开发环境启动的调试工具
mailhog:
image: mailhog/mailhog
profiles: ["dev"]
启动时指定 profile:
docker compose --profile dev up -d
docker compose --profile prod up -d
多文件覆盖策略让生产配置与开发配置分离:
# 基础配置 + 生产覆盖
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# docker-compose.prod.yml
services:
web:
deploy:
replicas: 3
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
健康检查与重启策略
生产环境必须配置健康检查,避免僵尸容器持续接收流量:
services:
web:
image: nginx:alpine
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
| 重启策略 | 行为 | 推荐场景 |
|---|---|---|
no | 从不重启 | 一次性任务 |
always | 任何退出都重启 | 开发环境 |
unless-stopped | 除非手动停止 | 生产默认 |
on-failure | 仅非零退出码重启 | 批处理服务 |
日志驱动与集中收集
默认的 json-file 驱动在日志量大时会导致磁盘耗尽。生产环境应配置外部日志收集:
services:
web:
image: nginx:alpine
logging:
driver: "fluentd"
options:
fluentd-address: localhost:24224
tag: docker.web
常用日志驱动对比:
| 驱动 | 目的地 | 持久化 | 适用场景 |
|---|---|---|---|
json-file | 本地磁盘 | 是 | 开发/小流量 |
syslog | rsyslog | 取决于配置 | 传统运维体系 |
fluentd | Fluentd 节点 | 是 | 统一日志平台 |
loki | Grafana Loki | 是 | 云原生可观测 |
awslogs | CloudWatch | 是 | AWS 基础设施 |
使用 Loki 驱动的完整示例:
x-logging: &default-logging
driver: loki
options:
loki-url: "http://loki:3100/loki/api/v1/push"
loki-retries: "5"
loki-batch-size: "400"
services:
web:
image: myapp
logging: *default-logging
db:
image: postgres:15
logging: *default-logging
可观测性体系的完整落地可参考 Kubernetes 可观测性实战 中的日志和指标收集方案。
Secrets 与 Configs 管理
Swarm 模式的 secrets 将敏感数据安全注入容器,避免写入镜像或环境变量:
# 创建 secret
echo "my_db_password" | docker secret create db_password -
# 创建 config
docker config create nginx_conf ./nginx.conf
version: "3.9"
services:
web:
image: nginx:alpine
secrets:
- source: tls_cert
target: /etc/nginx/ssl/cert.pem
configs:
- source: nginx_conf
target: /etc/nginx/nginx.conf
db:
image: postgres:15
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
tls_cert:
external: true
db_password:
external: true
configs:
nginx_conf:
external: true
Secrets 在容器内以只读文件形式挂载于 /run/secrets/,仅容器内 root 用户可读(权限 0444)。比环境变量更安全,因为环境变量可通过 /proc/<pid>/environ 泄露。关于更全面的容器安全加固方法,请阅读 Docker Rootless 与安全深度剖析。
Swarm 模式部署
将单机 Compose 升级为 Swarm 只需几个命令:
# 初始化 Swarm 集群
docker swarm init --advertise-addr 192.168.1.10
# 其他节点加入
docker swarm join --token <token> 192.168.1.10:2377
# 部署 stack
docker stack deploy -c docker-compose.yml myapp
# 滚动更新
docker service update --image myapp:v2 myapp_web
Swarm vs Kubernetes 选型参考:
| 维度 | Docker Swarm | Kubernetes |
|---|---|---|
| 学习曲线 | 低(主要用 docker CLI) | 高 |
| 节点规模 | < 100 节点 | 数千节点 |
| 存储编排 | 基本支持 | CSI 生态丰富 |
| 服务发现 | 内置 DNS | CoreDNS + Ingress |
| 适用场景 | 中小团队、快速交付 | 大规模、多租户 |
CI/CD 集成示例(GitHub Actions)
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to production
env:
DOCKER_HOST: ssh://deploy@prod-server
run: |
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --remove-orphans
docker system prune -f
此 pipeline 的关键点:BuildKit 的 GitHub Actions 缓存(type=gha)大幅缩短构建时间;--remove-orphans 自动清理已下线的服务容器;docker system prune 定期释放磁盘空间。
有关 Dockerfile 的编写规范,建议先阅读 Dockerfile 最佳实践。
Compose Watch 开发模式与 Bind Mount 最佳实践
开发环境下,频繁重新构建镜像会严重拖慢反馈周期。Docker Compose 提供了 Watch 模式和 Bind Mount 两种方式实现代码热重载。
Bind Mount(传统方式) 将宿主机的代码目录直接挂载到容器内,改动即时生效,无需重建镜像:
services:
web:
build: .
volumes:
- .:/app
- /app/node_modules # 匿名卷隔离 node_modules,避免覆盖容器内依赖
environment:
- NODE_ENV=development
command: ["npm", "run", "dev"]
Bind Mount 使用时需特别注意三点:第一,务必排除 node_modules、vendor、__pycache__ 等自动生成的缓存目录,否则容器运行时依赖会被宿主机空目录覆盖。第二,Mac 和 Windows 下的 Docker Desktop 使用 gRPC FUSE 或 osxfs 进行跨系统文件同步,处理大量小文件时 node_modules 的 I/O 性能可能显著下降,匿名卷隔离是绕开此问题的关键。第三,.env 文件不应被挂载至生产环境容器,避免敏感配置泄露。
Compose Watch(Docker Compose 2.22+ 推荐方式) 在保持 Bind Mount 便利性的同时,引入智能文件监控,只在文件真正变更时触发重建或同步,解决了传统 Bind Mount 中依赖目录被意外覆盖的痛点:
services:
web:
build:
context: .
target: dev
develop:
watch:
- action: sync
path: ./src
target: /app/src
- action: rebuild
path: ./package.json
- action: sync+exec
path: ./config
target: /app/config
exec:
command: ["/bin/sh", "-c", "nginx -s reload"]
develop.watch 支持三种 action:sync 对应 Bind Mount 的文件同步;rebuild 在关键文件(如 package.json、requirements.txt)变更时自动重建镜像;sync+exec 则在同步后执行指定命令(如重载配置),无需重启容器。建议开发团队统一使用 Compose Watch 作为本地开发基座,减少因环境差异导致的问题。
Docker Compose 多阶段构建优化(BuildKit 缓存层 + 离线构建)
生产 Compose 构建同样需要极致的速度与可靠性。在 docker-compose.yml 中启用 BuildKit 缓存,可让 CI/CD 流水线跨构建复用层缓存,避免每次都从零下载所有依赖。
services:
web:
build:
context: .
dockerfile: Dockerfile
cache_from:
- type=local,src=/tmp/.buildx-cache
cache_to:
- type=local,dest=/tmp/.buildx-cache-new,mode=max
args:
BUILDKIT_INLINE_CACHE: "1"
# 使用 Buildx 构建器并挂载缓存
docker buildx create --use && docker buildx build \
--cache-from type=local,src=/tmp/.buildx-cache \
--cache-to type=local,dest=/tmp/.buildx-cache-new,mode=max \
-t myapp:latest .
在隔离网络或私有化部署环境中,离线构建(air-gapped build)是一大挑战。对策是将基础镜像、依赖包和缓存层预先导入到内网 Registry:先在外网执行一次完整构建,将缓存推送到 Registry;在内网 CI 中仅使用 --cache-from 引用该 Registry,无需连接互联网。配合 --mount=type=cache 在 Dockerfile 中缓存 apt、npm、pip 等包管理器的本地缓存,能显著减少离线场景下的构建耗时。
关于多阶段构建的详细原理和各语言模板,请参考 Docker 构建优化完全指南。镜像体积优化的更多技巧,请阅读 Docker 镜像优化实践。
网络隔离与安全:自定义 Bridge / Overlay 网络、加密通信
默认的 bridge 网络将所有容器放在同一二层广播域,缺乏隔离,且所有容器可以互相通信,这在多租户或合规场景中不可接受。
自定义 Bridge 网络 通过用户定义的网桥实现服务级隔离,且支持基于 DNS 的服务发现:
services:
web:
image: myapp:latest
networks:
- frontend
- backend
db:
image: postgres:15
networks:
- backend
networks:
frontend:
driver: bridge
internal: false
backend:
driver: bridge
internal: true # 无外部访问,仅内部通信
internal: true 表示该网络没有通往宿主机的默认路由,适合隔离数据库、缓存等内部组件。自定义 Bridge 还允许通过 ipam 子网配置精确控制 IP 地址分配,避免与已有内网冲突。
Overlay 网络与加密通信 是 Swarm 模式跨主机通信的基石。Overlay 网络将多个物理节点上的容器纳入同一个虚拟二层,底层基于 VXLAN 封装。如果服务间传输敏感数据,必须启用 IPSec 加密:
networks:
backend:
driver: overlay
driver_opts:
encrypted: "true" # 启用 IPSec 加密
attachable: true
encrypted: "true" 在 Swarm 各节点间建立 IPSec 隧道,确保容器间流量在物理网络上传输时不可被嗅探。性能代价约为 10-15% 的吞吐量下降,对于内网基础设施完备的数据中心通常可以接受;若对延迟极度敏感,可考虑在应用层启用 TLS 而非全局 Overlay 加密。此外,为限制东西向流量,可结合 Swarm 的 --publish mode=host|ingress 策略,仅暴露必要端口,减少攻击面。
资源限制与 OOM 调优(memory swappiness、ulimits、pids_limit)
Linux 内核的资源控制子系统 cgroups 配合 Docker Compose 的 deploy.resources 字段,能防止单个容器拖垮宿主机。以下配置展示了生产环境中推荐的完整限制模板:
services:
web:
image: myapp:latest
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
ulimits:
nofile:
soft: 65536
hard: 65536
nproc:
soft: 32768
hard: 32768
pids_limit: 1024
sysctls:
- vm.swappiness=10
内存与 OOM 调优:memory limit 是硬上限,超出后内核 OOM Killer 会直接终止容器进程。为避免关键服务被误杀,可在宿主机调整 oom_score_adj 或在 Compose 中使用 memswap_limit 等于 memory 的设定,强制禁用交换,确保容器行为可预测。vm.swappiness=10 将进程交换倾向降至最低,优先回收文件缓存,减少因内存交换导致的延迟抖动。
文件描述符上限(nofile)决定了单个进程能打开的最大句柄数。高并发 Web 服务若使用默认的 1024,极易因句柄耗尽而拒绝新连接。将软硬限制统一设为 65536 是生产环境的通行做法。
进程数上限(pids_limit)用于防范 Fork Bomb 攻击。容器若被注入恶意脚本并无限 fork,无限制时会耗尽宿主机 PID 空间,导致全局故障。默认无限制,生产环境应显式设为合理值,如 1024。
Nproc 限制(ulimits.nproc)与 pids_limit 协同工作:前者控制单个用户的进程数,后者控制单个容器的进程数。在 Rootless 模式下运行时,nproc 尤为重要,因为宿主机用户级命名空间对进程数有额外约束。
从 Swarm 迁移到 Kubernetes 的渐进式策略
Swarm 的中等规模集群在业务增长到数百节点或需要多租户隔离时,迁移到 Kubernetes 是必然选择。但一次性全量迁移风险极高,推荐采用渐进式策略。
阶段一:容器镜像标准化。Swarm 和 K8s 使用同样的 OCI 镜像格式,因此迁移的第一步不是变动编排层,而是确保 Dockerfile 遵循最佳实践、镜像版本标签(semantic versioning)严格管理,让同一镜像能在两套编排系统中无缝切换。请阅读 Dockerfile 最佳实践 来检验镜像规范。
阶段二:Sidecar 网关层先行部署。在 K8s 集群中部署 Nginx Ingress 或 Istio Gateway,作为流量入口。此时服务主体仍在 Swarm 上。通过 DNS 或外部负载均衡器将部分流量切到 K8s Ingress,测试新集群的稳定性和可观测性。这种方法也称为 “Canary Ingress” 模式,风险最小。
阶段三:无状态服务分批迁移。将 API 服务、前端静态站点等无状态组件优先迁移至 K8s Deployment,利用滚动更新和 HPA 自动扩缩容验证弹性能力。Swarm 中对应服务保持运行,一旦 K8s 侧出现问题,流量可迅速回切。
阶段四:有状态服务和数据层迁移。数据库、消息队列等有状态组件迁移最复杂,建议:先在 K8s 中部署只读副本(如 PostgreSQL Streaming Replica、Redis Cluster Slave),待同步延迟稳定后再提升某个副本为主节点;或者直接保留在 Swarm 的独立节点甚至裸金属上,通过 K8s External Service 实现跨编排访问。Kubernetes 的数据持久化方案如 CSI、StatefulSet 和 Swarm 的 Volume Plugin 差异较大,提前在测试环境演练 PVC 创建、备份和恢复流程。
关键选型和对比:Swarm 与 Kubernetes 的差异不仅是命令不同,更是设计理念的变化。Swarm 偏向于声明式极简,K8s 偏向于控制器模式与事件驱动的复杂编排。团队需为此重新培训,建立新的 CI/CD 规范(从 docker stack deploy 迁移到 Helm 或 Kustomize)。关于两者技术差异和选型的深入分析,请参考 Docker 与 Kubernetes 对比。
# 阶段一验证:同一份镜像在 Swarm 和 K8s 上启动
docker stack deploy -c docker-compose.yml myapp # Swarm
kubectl apply -f k8s-deployment.yaml # K8s(使用同一镜像)
渐进式迁移的核心原则是 始终保留回滚路径。通过在边缘负载均衡器(如 Cloudflare、AWS ALB、自研 Gateway)上控制流量比例,任何阶段出现不可用都能秒级回退。迁移完成后,Swarm 节点逐步下线,监控和日志体系统一接入 K8s 的 Prometheus / Grafana / ELK 栈,形成一致的 可观测性平台。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。