Docker Compose 生产环境实践:从开发到部署的完整指南

深入 Docker Compose 生产级实践,涵盖高级语法、健康检查、日志收集、Secrets 与 CI/CD 集成。

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本地磁盘开发/小流量
syslogrsyslog取决于配置传统运维体系
fluentdFluentd 节点统一日志平台
lokiGrafana Loki云原生可观测
awslogsCloudWatchAWS 基础设施

使用 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 SwarmKubernetes
学习曲线低(主要用 docker CLI)
节点规模< 100 节点数千节点
存储编排基本支持CSI 生态丰富
服务发现内置 DNSCoreDNS + 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_modulesvendor__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.jsonrequirements.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 中缓存 aptnpmpip 等包管理器的本地缓存,能显著减少离线场景下的构建耗时。

关于多阶段构建的详细原理和各语言模板,请参考 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 栈,形成一致的 可观测性平台

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「docker」更多文章

  1. 容器运行时深度解析:dockerd 到 containerd 再到 runc 的完整调用链
  2. Harbor 私有镜像仓库深度实践:企业级容器镜像管理
  3. Docker 镜像大小优化与层缓存策略深度指南