Docker Compose 多服务编排指南:从开发到生产的完整实践
分类:后端与运维 | 标签:Docker, Compose, 微服务, DevOps, 容器化
发布时间:2026-04-18 | 作者:优易云科技运维团队
引言
在我们的日常工作中,Docker Compose 是不可或缺的编排工具。从本地开发环境搭建、CI/CD 流水线中的集成测试,到小型生产环境的部署,Compose 几乎覆盖了容器化全生命周期的每一个阶段。过去两年,我们团队维护了 20+ 个 Compose 编排配置,涵盖了从简单的 Web 应用到包含 15 个微服务的复杂物联网平台。本文将系统性地分享我们在实际项目中积累的 Compose 最佳实践。
Compose 文件结构最佳实践
一个组织良好的 Compose 文件是可维护性的基础。我们推荐使用 YAML 锚点和多文件覆盖模式来管理不同环境的配置差异。
基础配置文件 + 环境覆盖文件
# docker-compose.yml — 基础配置(所有环境共享)
version: "3.9"
x-common-env: &common-env
TZ: Asia/Shanghai
NODE_ENV: production
LOG_LEVEL: info
x-logging: &default-logging
driver: json-file
options:
max-size: "50m"
max-file: "5"
tag: "{{.Name}}/{{.ID}}"
services:
# --- API 网关 ---
gateway:
image: youyiyun/gateway:${IMAGE_TAG:-latest}
container_name: yy-gateway
restart: unless-stopped
environment:
<<: *common-env
PORT: 8080
UPSTREAM_URL: http://backend:3000
ports:
- "${GATEWAY_PORT:-80}:8080"
networks:
- frontend
- backend
depends_on:
backend:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
logging: *default-logging
# --- 后端服务 ---
backend:
image: youyiyun/backend:${IMAGE_TAG:-latest}
container_name: yy-backend
restart: unless-stopped
environment:
<<: *common-env
DATABASE_URL: postgresql://app:${DB_PASSWORD}@postgres:5432/youyiyun
REDIS_URL: redis://redis:6379/0
MQTT_BROKER: mqtt://emqx:1883
networks:
- backend
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
emqx:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
logging: *default-logging
# --- PostgreSQL 数据库 ---
postgres:
image: postgres:16-alpine
container_name: yy-postgres
restart: unless-stopped
environment:
POSTGRES_DB: youyiyun
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d youyiyun"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
logging: *default-logging
# --- Redis 缓存 ---
redis:
image: redis:7-alpine
container_name: yy-redis
restart: unless-stopped
command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
logging: *default-logging
# --- EMQX MQTT Broker ---
emqx:
image: emqx/emqx:5.7.0
container_name: yy-emqx
restart: unless-stopped
environment:
EMQX_LISTENERS__TCP__DEFAULT__BIND: "0.0.0.0:1883"
EMQX_LISTENERS__WS__DEFAULT__BIND: "0.0.0.0:8083"
EMQX_LOADED_PLUGINS: "emqx_auth_http,emqx_management"
volumes:
- emqx_data:/opt/emqx/data
- emqx_log:/opt/emqx/log
ports:
- "1883:1883"
- "8083:8083"
- "18083:18083"
networks:
- backend
healthcheck:
test: ["CMD", "emqx", "ping"]
interval: 15s
timeout: 10s
retries: 3
start_period: 30s
logging: *default-logging
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # 内部网络,不暴露到宿主机
volumes:
postgres_data:
driver: local
redis_data:
driver: local
emqx_data:
driver: local
emqx_log:
driver: local
# docker-compose.override.yml — 开发环境覆盖(自动合并)
version: "3.9"
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
environment:
NODE_ENV: development
LOG_LEVEL: debug
volumes:
- ./backend/src:/app/src # 热重载
- ./backend/package.json:/app/package.json
command: npm run dev
postgres:
ports:
- "5432:5432" # 开发时暴露端口方便本地工具连接
# docker-compose.prod.yml — 生产环境覆盖
version: "3.9"
services:
gateway:
ports:
- "443:8443"
environment:
SSL_CERT: /etc/ssl/certs/server.crt
SSL_KEY: /etc/ssl/private/server.key
volumes:
- /etc/ssl/certs:/etc/ssl/certs:ro
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
reservations:
memory: 256M
cpus: "0.5"
backend:
deploy:
resources:
limits:
memory: 2G
cpus: "2.0"
reservations:
memory: 1G
cpus: "1.0"
replicas: 2 # 生产环境 2 实例
postgres:
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} # 必须通过环境变量传入
deploy:
resources:
limits:
memory: 4G
cpus: "2.0"
# 使用方式:
# 开发:docker compose up -d (自动合并 override)
# 生产:docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
多阶段构建优化镜像体积
在生产环境中,镜像体积直接影响部署速度和存储成本。我们在 Node.js 和 Go 项目中全面采用多阶段构建,将镜像体积控制在合理范围内。
# --- Node.js 项目多阶段构建 Dockerfile ---
# ========== 阶段1:依赖安装 ==========
FROM node:20-alpine AS deps
WORKDIR /app
# 先复制依赖描述文件,利用 Docker 层缓存
COPY package.json package-lock.json ./
# 只安装生产依赖
RUN npm ci --omit=dev &&
npm cache clean --force
# ========== 阶段2:构建 ==========
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# 构建产物
RUN npm run build &&
# 清理不需要的文件
rm -rf node_modules/.cache
# ========== 阶段3:运行 ==========
FROM node:20-alpine AS runner
# 安全:使用非 root 用户
RUN addgroup --system --gid 1001 appgroup &&
adduser --system --uid 1001 appuser
WORKDIR /app
# 只复制必要的文件
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=deps --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3000
# 切换到非 root 用户
USER appuser
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3
CMD wget -qO- http://localhost:3000/api/health || exit 1
CMD ["node", "dist/server.js"]
# --- Go 项目多阶段构建 Dockerfile ---
# ========== 阶段1:构建 ==========
FROM golang:1.22-alpine AS builder
WORKDIR /build
# 利用 Go module 缓存
COPY go.mod go.sum ./
RUN go mod download
# 编译
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64
go build -ldflags="-w -s -X main.version=${VERSION:-unknown}"
-o /build/server ./cmd/server
# ========== 阶段2:运行(scratch 基础镜像,仅 ~15MB)==========
FROM scratch
# CA 证书(HTTPS 请求需要)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# 时区数据
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
ENV TZ=Asia/Shanghai
COPY --from=builder /build/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]

镜像体积优化数据对比
在我们的实际项目中,多阶段构建带来的体积缩减效果显著:
- Node.js 后端服务:单阶段 1.2GB → 多阶段 280MB(减少 77%)
- Go 微服务:单阶段 850MB → 多阶段 15MB(减少 98%)
- Python 数据处理服务:单阶段 1.5GB → 多阶段 420MB(减少 72%)
健康检查配置详解
健康检查是生产环境稳定运行的保障。没有健康检查的容器,Docker 无法判断服务是否真正可用,这会导致错误的依赖判断和负载均衡问题。
# 不同服务的健康检查策略
# PostgreSQL:使用 pg_isready
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d youyiyun -q"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s # 数据库初始化需要较长时间
# Redis:使用 redis-cli ping
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 10s
# Node.js 后端:HTTP 健康检查
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
# Go 服务:使用内置的健康端点
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/healthz"]
interval: 15s
timeout: 3s
retries: 3
start_period: 5s # Go 编译的二进制启动很快
# Nginx:检查进程是否存在
healthcheck:
test: ["CMD-SHELL", "pidof nginx || exit 1"]
interval: 30s
timeout: 3s
retries: 2
健康检查参数选择指南
在我们的经验中,以下参数配置适用于大多数场景:
- interval(检查间隔):数据库类 10s,应用类 30s,静态资源服务 60s
- timeout(超时时间):通常设为 3-5s,超过此时间视为失败
- retries(重试次数):3 次是合理的默认值,避免单次网络抖动导致服务重启
- start_period(启动宽限期):根据服务启动时间设置。Node.js 应用通常 20-30s,Go 服务 5-10s,数据库 30-60s
网络模式选择
Docker 提供了多种网络模式,选择不当会导致性能瓶颈或安全问题。
Bridge 网络(默认)
适用于大多数场景。每个容器通过 veth pair 连接到 Docker 网桥,容器间通过服务名通信。我们推荐为前端服务和后端服务分别创建网络,后端网络标记为 internal,增强安全性。
Host 网络模式
容器直接使用宿主机网络栈,无 NAT 开销,性能最佳。适用于对网络延迟极度敏感的场景(如 MQTT Broker、时序数据库)。但需要注意端口冲突和隔离性降低的问题。
# EMQX 使用 host 网络模式以获得最佳性能
emqx:
image: emqx/emqx:5.7.0
network_mode: host
# 端口直接映射到宿主机,无需 ports 配置
# 注意:host 模式下容器间不能通过服务名通信
Overlay 网络(Swarm 模式)
用于多节点集群的跨主机容器通信。我们在需要 Docker Swarm 部署的生产环境中使用 Overlay 网络。配合 Swarm 的负载均衡,可以实现透明的服务发现和跨节点路由。
# Docker Swarm 部署配置
networks:
overlay-net:
driver: overlay
attachable: true # 允许独立容器连接
driver_opts:
encrypted: true # 启用加密通信
数据持久化:Volumes vs Bind Mounts
这是初学者最容易混淆的概念。我们总结了选择规则:
Named Volumes(命名卷)
适用场景:数据库数据、应用状态文件、缓存数据。Docker 完全管理存储位置和权限,可移植性好。
volumes:
postgres_data: # Docker 自动创建和管理
redis_data:
emqx_data:
Bind Mounts(绑定挂载)
适用场景:开发时的代码热重载、配置文件注入、日志输出目录。直接映射宿主机路径,需要注意权限问题。
# 开发环境:代码热重载
volumes:
- ./backend/src:/app/src
# 配置文件注入(只读)
volumes:
- ./config/nginx.conf:/etc/nginx/nginx.conf:ro
- ./config/ssl:/etc/nginx/ssl:ro
# 注意:生产环境避免使用 bind mount 存储数据
# 因为文件权限和 SELinux/AppArmor 策略可能导致问题
tmpfs(临时文件系统)
适用于完全不需要持久化的临时数据(如 Redis 缓存、会话存储),数据存储在内存中,容器停止后自动清除。
滚动更新与零停机部署
在生产环境中,我们需要在不中断用户访问的情况下更新服务。Docker Compose 配合 deploy 配置可以实现简单的滚动更新。
# 滚动更新配置
services:
backend:
image: youyiyun/backend:${IMAGE_TAG}
deploy:
replicas: 3
update_config:
parallelism: 1 # 每次更新 1 个容器
delay: 15s # 每个容器更新间隔
order: start-first # 先启动新容器再停止旧容器(零停机)
failure_action: rollback # 更新失败自动回滚
rollback_config:
parallelism: 1
order: stop-first # 回滚时先停新容器
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
# 滚动更新命令
# 1. 拉取新镜像
docker compose pull backend
# 2. 滚动更新(Swarm 模式)
docker compose up -d --no-deps --build backend
# 3. 或使用 deploy 命令
docker compose deploy --with-registry-auth
# 查看更新状态
docker compose ps
docker compose logs -f backend
Nginx 负载均衡配合
# nginx.conf 配置示例
upstream backend_servers {
least_conn; # 最少连接负载均衡
# Docker Compose 使用服务名解析
server backend_1:3000 max_fails=3 fail_timeout=30s;
server backend_2:3000 max_fails=3 fail_timeout=30s;
server backend_3:3000 max_fails=3 fail_timeout=30s;
# 健康检查(需要 nginx_plus 或第三方模块)
# health_check interval=10s fails=3 passes=2;
}
server {
listen 443 ssl http2;
server_name api.youyiyun.com;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# 零停机:先检查后端是否存活
location / {
proxy_pass http://backend_servers;
proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 3;
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_read_timeout 300s;
proxy_send_timeout 300s;
}
}
日志管理与监控
容器日志管理是生产运维的关键环节。默认的 json-file 驱动如果不加限制,日志文件会无限增长,最终耗尽磁盘空间。
# 全局 Docker daemon 日志配置 /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5",
"compress": "true"
},
"storage-driver": "overlay2"
}
# 单服务覆盖日志配置
services:
backend:
logging:
driver: json-file
options:
max-size: "100m"
max-file: "10"
tag: "youyiyun-backend/{{.Name}}"
env: "NODE_ENV,LOG_LEVEL"
labels: "service"
# 高级:使用 Loki 收集日志
services:
loki:
image: grafana/loki:2.9.0
volumes:
- loki_data:/loki
networks:
- monitoring
promtail:
image: grafana/promtail:2.9.0
volumes:
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- ./config/promtail.yml:/etc/promtail/config.yml:ro
command: -config.file=/etc/promtail/config.yml
networks:
- monitoring
depends_on:
- loki
# promtail.yml 配置
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
filters:
- name: label
values: ["logging=promtail"]
relabel_configs:
- source_labels: ['__meta_docker_container_name']
target_label: 'container'
生产环境安全加固
容器安全是生产环境的底线。以下是我们在生产环境中实施的安全加固措施。
# 1. 非 root 用户运行
# Dockerfile 中已添加 appuser
USER appuser
# 2. 只读文件系统(只读根文件系统)
services:
backend:
read_only: true
tmpfs:
- /tmp
- /app/logs # 日志写入临时目录
# 3. 资源限制(防止资源耗尽)
services:
backend:
deploy:
resources:
limits:
memory: 2G
cpus: "2.0"
reservations:
memory: 512M
cpus: "0.5"
memswap_limit: 2G # 禁止使用 swap
pids_limit: 100 # 限制进程数量
# 4. 安全选项
services:
backend:
security_opt:
- no-new-privileges:true # 禁止提权
- apparmor:docker-default # 启用 AppArmor 配置
cap_drop:
- ALL # 丢弃所有能力
cap_add:
- NET_BIND_SERVICE # 仅保留绑定 <1024 端口的能力
# 5. 敏感信息管理
# ❌ 不要在 docker-compose.yml 中硬编码密码
# ✅ 使用 Docker secrets(Swarm 模式)或 .env 文件
# .env 文件(加入 .gitignore)
DB_PASSWORD=your_secure_password_here
REDIS_PASSWORD=your_redis_password
JWT_SECRET=your_jwt_secret
SMTP_PASSWORD=your_smtp_password
# docker-compose.yml 中引用
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
# 6. 网络隔离
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # 后端网络不暴露到宿主机
monitoring:
driver: bridge
internal: true
# 7. 镜像安全扫描(CI/CD 集成)
# 使用 Trivy 扫描镜像漏洞
# trivy image --severity HIGH,CRITICAL youyiyun/backend:latest
# 8. Docker daemon 安全配置 /etc/docker/daemon.json
{
"userns-remap": "default", # 用户命名空间重映射
"live-restore": true, # 容器不中断 daemon 重启
"userland-proxy": false, # 减少攻击面
"no-new-privileges": true # 全局禁止提权
}
总结
Docker Compose 是一个非常强大的工具,但”能用”和”用好”之间存在巨大的差距。通过合理的文件组织、多阶段构建优化、完善的健康检查、恰当的网络和数据持久化策略、安全的滚动更新机制以及全面的安全加固,我们可以构建出稳定、高效且安全的容器化部署方案。这些实践经验在我们的 20+ 个项目中反复验证,希望能为你的容器化之旅提供有价值的参考。