切换语言
切换主题

Docker Compose 生产部署:健康检查、重启策略与日志管理

凌晨三点,服务器告警短信一条接一条地弹到手机上。打开终端一看,磁盘空间 99%——容器日志占了 50GB。

这还不是最糟糕的。去年有个项目,API 容器状态显示 “running”,但数据库连接早就断了,请求全部返回 500。排查了整整三个小时才定位到问题。据 Last9 的研究数据,容器”假死”这种事,平均每次要浪费 3.2 小时排查时间。

说实话,很多团队第一次在生产环境部署 Docker Compose 时,都只是简单配个端口映射和卷挂载,就把容器往线上扔了。健康检查没配置,日志轮转也没加,重启策略更是随便写个 restart: always。结果就是:容器看起来在跑,其实早就挂了;日志文件疯狂增长,最后把磁盘撑爆;崩溃的服务无限重启,把 CPU 和内存都吃光。

这篇文章,我会把生产环境的三个核心配置——健康检查、重启策略、日志管理——掰开揉碎讲清楚。不光有配置示例,还有常见服务的健康检查命令、故障排查步骤,以及一份可以直接复制使用的完整 docker-compose.yml 模板。

健康检查 — 让容器真正活着

容器状态显示 running,不代表应用真的在工作。数据库连不上、端口没监听、进程卡死——这些情况 Docker 可不知道。健康检查就是给容器装个”心跳监测器”,定期检查应用到底是不是还能正常响应。

配置语法

在 docker-compose.yml 里,healthcheck 配置长这样:

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 10s      # 每隔 10 秒检查一次
  timeout: 5s        # 每次检查最多等 5 秒
  retries: 5         # 连续失败 5 次才标记为 unhealthy
  start_period: 30s  # 容器启动后给 30 秒预热时间

这几个参数得搭配好。timeout 不能比 interval 大,不然检查还没结束下一次就开始了。start_period 不能省——数据库这类服务启动慢,如果预热时间太短,健康检查会误判容器挂了。

常见服务的健康检查命令

不同服务,检查方式不一样。这里列几个常用的:

PostgreSQL

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

pg_isready 是 PostgreSQL 自带的检查工具,专门用来判断数据库是否准备好接受连接。

MySQL / MariaDB

healthcheck:
  test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
  interval: 10s
  timeout: 5s
  retries: 5
  start_period: 30s

注意密码用 $$ 转义,不然 YAML 会把 $ 当成变量引用。

Redis

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

Redis 的 ping 命令返回 PONG,用 grep 过滤一下确保结果正确。

Web Server(HTTP 检查)

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

-f 参数让 curl 在 HTTP 状态码非 2xx 时返回非零退出码,触发健康检查失败。

坑点提醒:Alpine 这类精简镜像里可能没有 curl。要么安装它(apk add curl),要么改用 wget:

test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1"]

启动顺序控制

数据库还没准备好,API 容器就启动了,结果连接失败、报错、崩溃——这种事我见过太多次。用 depends_on 配合 condition: service_healthy 能解决:

services:
  postgres:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  api:
    build: ./api
    depends_on:
      postgres:
        condition: service_healthy  # 等 postgres 健康检查通过才启动

这样一来,Docker Compose 会等 postgres 的健康检查返回 healthy 后,才启动 api 容器。不会再出现”数据库还没就绪,API 就去连”的尴尬。

重启策略 — 失败后优雅恢复

容器崩溃了怎么办?让它自动重启看起来是个好主意。但问题是:如果崩溃的根本原因没解决,重启只会无限循环,浪费 CPU 和内存,还掩盖了真实故障。

配置语法

重启策略在 deploy 块里配置:

deploy:
  restart_policy:
    condition: on-failure   # 只在失败时重启
    delay: 5s               # 重启前等 5 秒
    max_attempts: 3         # 最多尝试重启 3 次
    window: 120s            # 120 秒内重启成功才算真正恢复

condition 有三个选项:

  • none:不重启,容器挂了就挂着
  • on-failure:只有容器非正常退出(退出码非 0)才重启
  • any:不管什么情况都重启

生产环境推荐

生产环境建议用 on-failure,而不是 always

为啥?restart: always 会让容器不管什么情况都重启。应用代码有 bug 导致崩溃?重启。数据库连不上导致进程退出?重启。配置文件写错了启动失败?还是重启。结果就是崩溃循环,日志疯狂刷屏,CPU 被反复消耗。

on-failure 加上 max_attempts 就不一样了——最多重启 3 次,如果还是失败就停下来。运维人员能看到容器最终挂掉了,去排查真正的问题。

参数调优

delay 是重启间隔。太短的话,容器可能还没完全清理干净就又启动了;太长又会延长恢复时间。一般 5-10 秒比较合适。

window 这个参数容易被忽略。它定义的是:重启后多长时间内不再次失败,才算重启成功。比如设置 window: 120s,容器重启后如果 120 秒内又挂了,max_attempts 计数不会重置。这样能避免”刚重启成功一秒钟又崩溃”的误判。

健康检查与重启策略的配合

健康检查和重启策略不是独立工作的,它们会协同配合:

  1. 健康检查连续失败 retries 次 → 容器被标记为 unhealthy
  2. 如果配置了 restart_policy,Docker 会尝试重启容器
  3. 重启后健康检查重新开始计数
  4. 如果重启后健康检查通过,容器恢复正常;如果重启后还是失败,继续尝试重启直到 max_attempts 用完

这个链路让故障有了”自动恢复”的能力,同时又限制了无限重启的风险。

日志管理 — 防止磁盘被撑爆

开头提到的凌晨三点告警——磁盘 99%,日志占了 50GB——这事儿我经历过不止一次。Docker 默认的日志驱动 json-file 不会自动清理旧日志,文件会无限增长。如果不配置日志轮转,早晚把磁盘撑爆。

日志轮转配置

在 docker-compose.yml 里加上 logging 配置:

logging:
  driver: "json-file"
  options:
    max-size: "10m"      # 单个日志文件最大 10MB
    max-file: "3"        # 最多保留 3 个日志文件
    compress: "true"     # 压缩旧日志节省空间

这样配置后,容器日志最多占用 30MB(10MB × 3)。超过 10MB 时,Docker 会创建新文件;超过 3 个文件时,最旧的会被删除或压缩。

日志文件存放在 /var/lib/docker/containers/<container-id>/<container-id>-json.log。用 du 命令可以查看实际占用:

du -sh /var/lib/docker/containers/*/*-json.log

驱动选择

Docker 支持多种日志驱动:json-file、syslog、fluentd、journald、local 等。对于大多数场景,json-file 或 local 都够用。

Docker 官方文档提到,local 驱动比 json-file 更高效,自带日志轮转,不需要手动配置 max-size/max-file。如果你日志量很大(比如每天几十 GB),可以考虑用 local:

logging:
  driver: "local"

不过 local 驱动有个缺点:不能用 docker logs 直接查看日志内容。需要在配置里加上 mode: "non-blocking" 才能兼容。

集中式日志收集(可选)

单机部署用 json-file 或 local 就行。但如果你有几十台服务器、几百个容器,日志分散在各处就不好管理了。这时候可以考虑集中式日志方案:

  • Fluentd:轻量级日志收集,适合小规模集群
  • ELK Stack(Elasticsearch + Logstash + Kibana):功能强大,但部署成本高
  • Loki + Grafana:云原生方案,与 Prometheus 生态集成好

这些方案的配置比较复杂,不在本文范围内。简单提一下配置 Fluentd 的思路:

logging:
  driver: "fluentd"
  options:
    fluentd-address: "localhost:24224"
    tag: "docker.{{.Name}}"

Fluentd 会把日志转发到指定的地址,你可以在另一台服务器上统一收集和分析。

完整配置模板

把健康检查、重启策略、日志管理组合起来,就是一个生产级的 docker-compose.yml。下面是一个完整示例,包含 PostgreSQL 数据库、Redis 缓存、和 API 服务:

version: '3.8'

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 5s
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      DATABASE_URL: postgres://myuser:mypassword@postgres:5432/mydb
      REDIS_URL: redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
    deploy:
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 3
        window: 120s
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"
        compress: "true"

volumes:
  postgres_data:

配置要点说明

启动顺序:api 容器的 depends_on 同时等待 postgres 和 redis 的健康检查通过。数据库和缓存都准备好了,API 才启动,避免启动时的连接错误。

日志大小差异:postgres 和 redis 的日志量通常不大,10MB × 3 足够;API 服务日志可能更多,设置 50MB × 5。根据实际日志量调整,别一刀切。

重启延迟差异:postgres 启动慢,重启后需要时间恢复,delay 设置 5 秒;API 启动快,delay 设置 10 秒给健康检查更多缓冲。

启动预热时间:postgres start_period: 30s,给数据库足够的初始化时间;redis start_period: 5s,Redis 启动本来就快;API start_period: 10s,应用启动通常几秒就够了。

这个模板可以直接复制使用,只需要把环境变量和镜像换成你自己的。如果你的项目还有其他服务(比如 MongoDB、MinIO),按照同样的模式加上健康检查、重启策略和日志配置就行。

常见陷阱与排查

配置写完了,部署上去,问题可能还是会出现。这里列几个常见坑和排查步骤。

健康检查一直失败

症状:容器状态一直是 unhealthy,但应用看起来能正常工作。

排查步骤

  1. 先检查健康检查命令用的工具是否存在:

    docker exec <container> which curl
    docker exec <container> which pg_isready

    Alpine 镜像里经常没有 curl,需要手动安装或者改用 wget。

  2. 手动运行健康检查命令,看输出:

    docker exec <container> curl -f http://localhost:8080/health

    如果返回错误,可能是健康检查端点本身有问题。

  3. 查看健康检查的详细状态:

    docker inspect --format='{{json .State.Health}}' <container> | jq

    能看到最近几次检查的结果、失败原因、时间戳。

容器反复重启

症状:容器启动几秒后又挂掉,日志里全是重启记录。

排查步骤

  1. 查看容器退出原因:

    docker inspect --format='{{.State.ExitCode}}' <container>
    docker inspect --format='{{.State.Error}}' <container>

    退出码能告诉你大概是什么问题(1 = 一般错误,137 = 被 OOM 杀掉,139 = 段错误)。

  2. 查看重启次数:

    docker inspect --format='{{.RestartCount}}' <container>

    如果次数很大,检查 max_attempts 是否生效。

  3. 查看容器日志找具体错误:

    docker logs --tail 100 <container>

日志磁盘满

症状:磁盘空间告警,发现 /var/lib/docker/containers 目录占用很大。

排查步骤

  1. 找出占用最大的日志文件:

    du -sh /var/lib/docker/containers/*/*-json.log | sort -rh | head -5
  2. 检查日志轮转配置是否生效:

    docker inspect --format='{{.HostConfig.LogConfig}}' <container>

    如果输出显示 Config: {},说明没配置日志轮转。

  3. 手动清理日志(临时方案):

    truncate -s 0 /var/lib/docker/containers/<id>/<id>-json.log

    这是临时方案,长期还是要加上日志轮转配置。

快速排查命令清单

遇到问题时,这几个命令能帮你快速定位:

# 查看所有容器健康状态
docker ps --format "table {{.Names}}\t{{.Status}}"

# 查看某个容器的健康检查历史
docker inspect --format='{{json .State.Health}}' <container>

# 查看容器退出码和重启次数
docker inspect --format='ExitCode: {{.State.ExitCode}}, RestartCount: {{.RestartCount}}' <container>

# 查看日志文件大小
du -sh /var/lib/docker/containers/*/*-json.log | sort -rh

# 查看容器最近 100 条日志
docker logs --tail 100 <container>

总结

生产环境部署 Docker Compose,这三个配置不是可选的,而是必须的:健康检查让容器不只是”看起来活着”,重启策略让故障有自动恢复的机会同时限制无限循环,日志管理防止磁盘被撑爆。

3.2小时
容器假死平均排查时间

核心配置清单

  • 健康检查:test + interval + timeout + retries + start_period
  • 重启策略:condition: on-failure + max_attempts: 3
  • 日志轮转:max-size: 10m + max-file: 3 + compress: true

三步行动计划

  1. 检查你现有的 docker-compose.yml,看看有没有这三个配置。没有的话,至少加上健康检查和日志轮转。
  2. 用上面的完整模板部署一个测试服务,观察健康检查是否生效、日志是否在轮转。
  3. 把排查命令记下来。下次凌晨三点收到告警时,能快速定位问题。

别让你的容器裸跑在生产线上。配置好这三个”保护罩”,出问题时至少能自动恢复、能快速排查、不会把磁盘撑爆。

常见问题

健康检查的 interval 和 timeout 怎么设置才合理?
interval 建议 10-30 秒,timeout 建议 3-10 秒。关键是 timeout 不能大于 interval,否则下次检查开始时上一次还没结束。数据库类服务建议设置较长的 start_period(30-60 秒),给初始化预留时间。
重启策略用 restart: always 还是 on-failure 更好?
生产环境推荐 on-failure 配合 max_attempts。always 会在任何情况下重启,包括配置错误、代码 bug 等,导致崩溃循环。on-failure 只在异常退出时重启,配合 max_attempts 限制次数,让运维能发现问题。
日志文件多大、保留几个合适?
一般服务建议 max-size: 10m + max-file: 3,总共 30MB。日志量大的服务(如 API)可以设 50m × 5。关键是根据实际日志量调整,同时开启 compress: true 节省空间。
容器健康检查一直失败但应用正常运行怎么办?
先确认健康检查命令用的工具是否存在(如 curl、pg_isready),Alpine 镜像常缺工具。再手动运行检查命令看输出,最后用 docker inspect 查看健康检查历史定位具体失败原因。
depends_on 的 condition: service_healthy 有什么用?
确保依赖服务的健康检查通过后才启动当前容器。比简单的 depends_on 更可靠,避免数据库还没准备好 API 就尝试连接导致启动失败。需要依赖服务配置 healthcheck 才能生效。
如何快速查看容器日志占用了多少磁盘空间?
使用命令:du -sh /var/lib/docker/containers/*/*-json.log | sort -rh。如果发现单个容器日志特别大,检查该容器的日志轮转配置是否生效。

11 分钟阅读 · 发布于: 2026年4月12日 · 修改于: 2026年4月12日

评论

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

相关文章