切换语言
切换主题

Docker Compose 生产部署三要素:健康检查、重启策略与资源限制

凌晨三点,服务器告警短信把我从床上炸醒。

打开电脑一看,容器状态显示 running,绿色的小圆点看着挺健康。但访问服务?502 Bad Gateway。

数据库容器还没启动完成,应用容器就已经急吼吼地去连了。连接失败,服务挂掉。容器还在”运行”,但服务早已暴毙。

这就是我第一次把 Docker Compose 部署到生产环境时的真实经历。

跑得起来和跑得稳,完全是两码事。你的 docker-compose up 能一键启动,不代表半夜内存爆炸、进程崩溃时它还能站着。

Docker Compose 生产部署三要素——健康检查、重启策略、资源限制——就是用来填这个坑的。这篇文章分享我在踩坑过程中总结的配置方法,附带可直接复制的 YAML 模板,帮你把容器从”能跑”变成”跑得稳”。

1. 健康检查 (healthcheck):判断容器是否真正可用

docker ps 显示的 running 状态,只是告诉你的容器进程还在。但它能不能正常提供服务?不知道。

健康检查就是让 Docker 定期”体检”你的容器:发个 HTTP 请求、连一下数据库、或者跑个脚本,看服务是不是真活着。

健康检查怎么工作?

Docker 会按你设定的间隔,往容器里发送检查命令。命令返回 0 就是健康,非 0 就是不健康。连续失败几次后,容器会被标记为 unhealthy

关键在于:健康检查失败不会自动触发重启。它只是把状态亮出来,告诉你”这哥们有问题”。要让它自动恢复,得配合 depends_on 的条件启动和重启策略。

四个参数你得搞清楚

healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
  interval: 30s      # 每隔多久检查一次
  timeout: 10s       # 单次检查超时时间
  retries: 3         # 连续失败几次标记为 unhealthy
  start_period: 60s  # 启动宽限期,这段时间失败不计入 retries

start_period 这个参数我曾经忽略过,结果就是:应用启动慢,数据库连接要 40 秒,但健康检查 10 秒就开始了。连续失败 3 次,直接被标记为 unhealthy。加上 start_period: 60s 后,给应用留够了初始化时间。

常用健康检查命令

Web 服务

healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 30s

PostgreSQL

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 10s
  timeout: 5s
  retries: 5

Redis

healthcheck:
  test: ["CMD", "redis-cli", "ping"]
  interval: 10s
  timeout: 3s
  retries: 3

配合 depends_on 实现依赖启动

这个才是健康检查最实用的地方:让等服务依赖真正就绪后再启动。

services:
  app:
    depends_on:
      db:
        condition: service_healthy  # 等数据库健康检查通过
      redis:
        condition: service_healthy  # 等 Redis 健康检查通过

以前我用 depends_on: [db, redis],结果应用容器启动了,数据库还在初始化。连不上,直接报错退出。改成 condition: service_healthy 后,应用会乖乖等到数据库能响应 pg_isready 才启动。世界清静了。

2. 重启策略 (restart):让容器具备自愈能力

容器挂了,谁来扶它起来?

手动 docker restart?凌晨三点告警的时候你试试。

重启策略就是让 Docker 守护进程帮你干这事儿。容器退出后,Docker 自动判断要不要重启。

四种策略对比

策略行为适用场景
no挂了就挂了,不重启临时测试、CI/CD
always不管怎么退出都重启核心服务
on-failure只有异常退出才重启任务型容器
unless-stopped始终重启,除非手动停止生产首选

生产环境首选:unless-stopped

restart: unless-stopped

为什么推荐 unless-stopped 而不是 always

区别在于:手动 docker stop 后的行为

  • always:手动停止后,系统重启或 Docker 服务重启,容器又会自动启动
  • unless-stopped:手动停止后,就真的停着,不会自己活过来

想象一下:你手动停了一个容器做维护,结果服务器重启后它自己又跑起来了。可能你只想说一句:搞什么鬼。

on-failure 的重试限制

on-failure 可以设置重试次数:

restart: on-failure:5  # 最多重启 5 次

如果容器连续 5 次启动失败,Docker 就放弃了。适合那些可能因为外部原因(数据库连不上、配置错误)导致反复崩溃的场景——防止无限重启循环。

怎么选?简单说

  • 核心服务(Web、API、数据库):unless-stopped
  • 后台任务、定时脚本:on-failure
  • 开发调试、临时运行:no

一个坑:重启策略只管”容器退出后要不要重启”。它不管”服务是不是真的可用”。要结合起来,还得靠健康检查。

3. 资源限制 (deploy.resources):防止容器失控

你有没有遇到过这种情况:一个容器内存泄漏,吃掉了服务器所有内存,其他容器全被 OOM Killer 干掉。

我遇到过。那感觉,啧。

资源限制就是给每个容器设个”天花板”:超过这个限度,就把它干掉,保护其他服务。

limits vs reservations

deploy:
  resources:
    limits:
      cpus: '1.0'      # 最多用 1 个 CPU
      memory: 512M     # 最多用 512MB 内存
    reservations:
      cpus: '0.5'      # 至少保留 0.5 个 CPU
      memory: 256M     # 至少保留 256MB 内存
  • limits:硬限制,超过就杀进程(OOM)
  • reservations:软限制,告诉调度器”这个容器至少需要这么多资源”

说得直白点:limits 是”不能超过”,reservations 是”至少保证”。

CPU 限制怎么设置?

cpus: '1.0'   # 最多用满 1 个 CPU 核心
cpus: '0.5'   # 最多用 50% 的 CPU
cpus: '2.0'   # 最多用 2 个核心

CPU 限制是软限制,容器超过后会被限速,不会被杀。所以宁可设高点。

内存限制怎么设置?

memory: 512M    # 512MB
memory: 2G      # 2GB

内存限制是硬限制。超过,容器直接被 OOM Killer 干掉,没商量。

我的经验值

  • Node.js 应用:至少 512M,生产环境建议 1G
  • Python 应用:256M - 512M
  • PostgreSQL:根据连接数和数据量,1G - 4G
  • Redis:256M - 512M,如果做缓存可能要更大

一个实战配置

services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.25'
          memory: 256M

这样设置:应用最多用 1 个 CPU 和 1GB 内存,但 Docker 会保证至少给它 0.25 个 CPU 和 256MB 内存。

注意deploy 配置主要用于 Docker Swarm。单机部署用 docker-compose up 时,资源限制生效但需要 Docker Compose V2 或使用 docker-compose --compatibility 参数。更通用的单机写法是用 mem_limitcpus(已废弃)或直接用 deploy(Compose V2.20+ 支持)。

4. 完整配置模板:可直接复制的生产级 YAML

三要素单独用都有用,但组合起来才是生产级的配置。下面是一个 Web 服务 + PostgreSQL + Redis 的完整示例,可以直接复制改造。

完整示例

version: '3.8'

services:
  # Web 应用
  app:
    image: myapp:latest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.25'
          memory: 256M
    logging:
      driver: json-file
      options:
        max-size: "10m"   # 单个日志文件最大 10MB
        max-file: "3"     # 最多保留 3 个日志文件

  # PostgreSQL 数据库
  db:
    image: postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: apppassword
      POSTGRES_DB: appdb
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 512M
    volumes:
      - pgdata:/var/lib/postgresql/data

  # Redis 缓存
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          memory: 128M

volumes:
  pgdata:

配置分离技巧

开发环境和生产环境的配置通常不同。用两个文件分开管理:

# compose.yaml - 开发环境
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    restart: "no"  # 开发时不想自动重启
# compose.production.yaml - 生产环境覆盖
version: '3.8'
services:
  app:
    image: myapp:v1.2.3  # 生产用构建好的镜像
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 1G

启动生产环境:

docker-compose -f compose.yaml -f compose.production.yaml up -d

两个文件会合并,compose.production.yaml 的配置覆盖 compose.yaml

日志管理:防止磁盘撑爆

Docker 默认的日志驱动是 json-file,日志会无限增长。不加限制的话,几个月后磁盘就被日志吃满了。

logging:
  driver: json-file
  options:
    max-size: "10m"   # 单文件最大 10MB
    max-file: "3"     # 最多 3 个文件,总共最多 30MB

每个容器最多 30MB 日志,自动轮转。这配置我都加到每个服务里了,省得以后还要去手动清理。

总结

说了这么多,三要素的核心逻辑就是:

健康检查发现问题 → 重启策略自动恢复 → 资源限制防止崩溃蔓延

这套组合拳下来,你的 Docker Compose 应用就能:

  1. 启动时等依赖服务真正就绪,而不是盲目冲上去
  2. 崩溃后自己爬起来,半夜不用你起床
  3. 一个容器失控不会拖垮整台服务器

现在去检查一下你的 docker-compose.yml。缺了哪个配置?补上。

如果还没开始用 Docker Compose 部署生产环境,下次部署时把这三个配置带上。你会感谢自己的。

配置 Docker Compose 生产部署三要素

为 Docker Compose 添加健康检查、重启策略和资源限制,构建生产级稳定部署

⏱️ 预计耗时: 15 分钟

  1. 1

    步骤1: 添加健康检查配置

    为每个服务添加 healthcheck 配置:

    • test: 检查命令(curl、pg_isready、redis-cli ping 等)
    • interval: 检查间隔(推荐 10-30s)
    • timeout: 超时时间(推荐 5-10s)
    • retries: 失败次数(推荐 3-5 次)
    • start_period: 启动宽限期(根据应用启动时间设置 30-60s)
  2. 2

    步骤2: 配置依赖服务条件启动

    使用 depends_on 的 condition 参数:

    • 将 depends_on: [db] 改为 depends_on: db: condition: service_healthy
    • 确保依赖服务的健康检查已配置
    • 应用服务会等待依赖服务真正可用后再启动
  3. 3

    步骤3: 设置重启策略

    根据服务类型选择重启策略:

    • 核心服务(Web/API/数据库):restart: unless-stopped
    • 后台任务/定时脚本:restart: on-failure:5
    • 开发调试:restart: "no"
    • 避免使用 always(手动停止后会意外重启)
  4. 4

    步骤4: 配置资源限制

    在 deploy.resources 中设置 limits 和 reservations:

    • limits: 硬限制,超过会被 OOM Killer 干掉
    • reservations: 软限制,Docker 保证的最小资源
    • Node.js 应用:limits.memory 建议至少 512M
    • 数据库根据连接数和数据量设置 1G-4G
  5. 5

    步骤5: 添加日志轮转配置

    防止日志文件撑爆磁盘:

    • logging.driver: json-file(默认驱动)
    • logging.options.max-size: "10m"(单文件最大 10MB)
    • logging.options.max-file: "3"(保留 3 个文件)
    • 总日志上限 30MB,自动轮转

常见问题

健康检查失败后容器会自动重启吗?
不会。健康检查只是把容器标记为 unhealthy,不会自动触发重启。需要配合重启策略(如 unless-stopped)和 depends_on 的 service_healthy 条件使用。当容器进程崩溃退出时,重启策略才会介入。
unless-stopped 和 always 有什么区别?
关键区别在于手动停止后的行为:always 策略下,手动执行 docker stop 后,如果服务器或 Docker 服务重启,容器会自动启动;unless-stopped 策略下,手动停止后容器就保持停止状态,不会自己活过来。生产环境推荐 unless-stopped。
资源限制中的 limits 和 reservations 有什么区别?
limits 是硬限制,容器内存使用超过 limits.memory 会被 OOM Killer 杀掉;reservations 是软限制,告诉 Docker 调度器这个容器至少需要这么多资源,但容器可以超用。CPU 限制是软的(超过会被限速),内存限制是硬的(超过会被杀)。
start_period 参数有什么作用?
start_period 是启动宽限期。在这段时间内健康检查失败不会计入 retries。对于启动较慢的应用(如需要 40 秒连接数据库),设置 start_period: 60s 可以避免应用刚启动就被误判为 unhealthy。
如何在开发和生产环境使用不同的配置?
使用两个配置文件分离:

• compose.yaml:开发环境配置(build: ., restart: "no")
• compose.production.yaml:生产环境覆盖(image: xxx, restart: unless-stopped)
• 启动命令:docker-compose -f compose.yaml -f compose.production.yaml up -d
• 后者会覆盖前者的配置,实现配置分离
单机部署可以用 deploy.resources 吗?
可以。虽然 deploy 配置主要用于 Docker Swarm,但 Docker Compose V2.20+ 支持单机使用。如果使用旧版本,需要加 --compatibility 参数,或使用已废弃的 mem_limit/cpus 参数。建议升级到最新版 Docker Compose。

8 分钟阅读 · 发布于: 2026年4月24日 · 修改于: 2026年4月25日

相关文章

BetterLink

想持续收到这个主题的更新?

你可以直接关注作者更新、订阅 RSS,或者继续沿着系列入口往下读,避免下次又回到搜索结果重新找。

关注公众号

评论

使用 GitHub 账号登录后即可评论