Administrator
Administrator
发布于 2026-10-28 / 0 阅读
0
0

Docker 故障排查(14):Compose 中数据库已启动,应用却 connection refused:启动顺序不等于就绪

适用:使用 systemd 的 Linux 服务器、常规 rootful Docker Engine;Compose 示例使用 docker compose V2。本文为教学故障场景,错误文本和结果为示意,具体输出以现场为准。

一、遭遇情况:第一次部署失败,手动重启应用又好了

执行 docker compose up -d 后,数据库容器正在运行,应用却因 connection refused 退出。等半分钟再启动应用,就能连上。于是团队在启动脚本里加了 sleep 30。

本篇案例是数据库首次初始化比应用启动慢。固定等待时间在测试机上可能奏效,换一台慢磁盘服务器或增加初始化数据后,仍然可能失败。

二、原因分析:至少存在三种不同状态

容器主进程已启动;数据库已开始接受连接;应用使用指定账号、数据库和表可以完成请求。这三个阶段并不等价。

depends_on 的简单形式主要表达启动依赖,不能自动代表数据库已经达到应用要求的就绪状态。应通过明确的健康检查和依赖条件,让初次启动等待达到对应阶段。

数据库启动、就绪与应用可用的区别

三、排查思路:先证明是时间问题,不要先增加等待

在正确项目目录执行:

docker compose version
docker compose ps -a
docker compose logs --tail 100 --timestamps db
docker compose logs --tail 100 --timestamps app
docker compose config

实际服务不叫 app 时替换名称。核对应用失败时间是否早于数据库接受连接时间,并检查应用地址使用数据库服务名与容器内端口。

若日志是密码认证失败、数据库不存在或表不存在,不是单纯端口未就绪。等待更久不会自动修正错误密码;健康检查也不能代替数据迁移。

docker compose config 可能展开敏感环境变量,完整输出仅在受控终端查看,分享时先脱敏。它用于确认最终配置,不是可以随意公开的排查附件。

四、实践:一个完整、可独立运行的 Compose 项目

创建新的练习目录,不要覆盖生产 Compose 文件。本实验使用 PostgreSQL 16 的 Alpine 镜像,数据库不发布宿主机端口。check 是一次性连接检查任务,成功后退出 0 是预期行为。

在目录里建立 .env,填入你自己的练习密码:

LAB_DB_PASSWORD=ReplaceWithYourOwnLabPassword

不要提交真实 .env 到代码仓库。下面是完整的 compose.yaml:

name: dt14-readiness

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: labuser
      POSTGRES_DB: labdb
      POSTGRES_PASSWORD: ${LAB_DB_PASSWORD:?set LAB_DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""]
      interval: 3s
      timeout: 3s
      retries: 20
      start_period: 10s

  check:
    image: postgres:16-alpine
    environment:
      PGPASSWORD: ${LAB_DB_PASSWORD:?set LAB_DB_PASSWORD}
    depends_on:
      db:
        condition: service_healthy
    entrypoint: ["/bin/sh", "-ec"]
    command:
      - "psql -h db -U labuser -d labdb -v ON_ERROR_STOP=1 -c 'SELECT 1;'"

volumes:
  pgdata: {}

$$ 让 Compose 保留美元符号,交给容器内 shell 展开数据库环境变量。这里使用 TCP 回环地址检查数据库,避免把初始化时某些仅 Unix socket 可用阶段误当作外部连接已可用。

检查语法并启动:

docker compose config --quiet
docker compose up -d
docker compose ps -a
docker compose logs --tail 50 db check

预期 db 到达 healthy,check 输出查询结果 1 后退出 0。查看具体状态:

docker inspect "$(docker compose ps -q db)" --format '{{.State.Health.Status}}'
docker inspect "$(docker compose ps -a -q check)" --format 'Exit={{.State.ExitCode}}'

第二条使用 ps -a,因为检查任务已经结束。只看到它不在运行列表里,并不代表失败。

五、健康检查能保证什么,不能保证什么

pg_isready 主要判断 PostgreSQL 是否在接受连接,不是完整验证业务账号、数据库结构和查询权限。所以本实验另有 check 真正执行 SELECT 1;真实应用还需要根据业务验证迁移和必要对象。

start_period 为启动阶段失败计数提供宽限,不是强制固定睡眠。检查成功后,服务可以进入健康状态;参数需要结合初始化时间与失败判断要求设置。

如果把本例改为简单 depends_on: [db],首次初始化时 check 可能抢跑失败,但这个竞争条件不保证每次都复现。偶尔成功,不能证明部署定义没有时序问题。

六、持续运行还需要应用重连

service_healthy 解决 Compose 初次启动时的依赖等待,不会让数据库后来重启时,应用现有连接自动恢复。应用仍需有连接超时、有限重试、退避和连接池失效处理。

正常维护数据库时,必须按业务考虑连接恢复;不要把无限重启应用或固定 sleep 当成持续可用方案。

本实验结束可在专用目录执行 docker compose down 停止并删除项目容器和网络,默认保留命名卷。不要附加 -v 来解决启动故障,卷里可能有需要保留的数据。

七、验证与总结

确认依赖健康、应用真实连接成功,再观察数据库重启后的恢复能力。生产验收要按真实业务口径,而不只是 SELECT 1。

这类问题的关键是明确就绪条件,让初次等待和运行期重连各司其职。容器启动顺序可以控制,数据库初始化速度却不能靠一个固定秒数可靠推断。

参考资料


上一篇 · 系列目录 · 下一篇


评论