在编排多容器应用时,简单的depends_on只保证依赖容器先启动,并不保证其内部服务已经就绪。例如,应用容器依赖数据库,数据库容器可能已经启动但尚未监听端口,应用连接就会报错。判断是否属于这类问题时,可以查看应用日志中是否出现connection refused或ETIMEDOUT,并且数据库容器日志显示尚未准备好。此时需要从“启动顺序”升级到“就绪检查”,用健康检查机制来精确控制依赖关系。
改配置之前,先别急着给所有服务都加上 healthcheck。需要确认当前 compose 文件版本支持 condition 字段。通常 docker-compose 版本在 2.1 以上才能使用 depends_on 的 condition: service_healthy。如果不确定版本,可以先运行 docker-compose config 检查语法是否被接受;如果报错提示 condition 不被支持,就需要升级 docker-compose,或者改用 docker compose v2 插件。
用 healthcheck 把启动顺序变成就绪检查
在docker-compose.yml中,可以给依赖服务定义healthcheck指令,然后修改depends_on的condition为service_healthy。例如,对数据库服务添加test: ["CMD", "pg_isready", "-U", "postgres"],并设置interval、timeout、retries等参数。应用服务则写成depends_on: db: condition: service_healthy。这样应用容器会在数据库通过健康检查后才启动。注意需要docker-compose版本在2.1以上,并且不同版本对condition的支持有差异。
完整的配置可以写成这样:
services:
db:
image: postgres:16
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
app:
build: .
depends_on:
db:
condition: service_healthy
这个示例里 start_period 字段给数据库留出了启动豁免期,启动初期的检查失败不会计入 retries,适合数据库首次初始化较慢的情况。镜像不同,检查命令也要跟着换。例如 MySQL 可以用 mysqladmin ping,Redis 可以用 redis-cli ping。但精简镜像不一定包含这些工具,可以先进容器手动执行一次检查命令,确认返回码为 0 再写进配置;否则健康检查会一直失败,应用容器也就一直不会启动。
检查命令和参数里的坑
健康检查命令的写法需要谨慎。很多基础镜像不包含curl或wget,如果使用它们作为检查命令,会因命令不存在而健康检查失败。更稳妥的方式是使用语言自身能力,例如bash的/dev/tcp,或者安装必要的工具。另外,如果健康检查的interval设得过短(比如1秒),会在服务启动过程中频繁触发失败,导致retries耗尽;如果timeout设得过长,则依赖方需要等待更久。推荐interval为5-10秒,timeout为3-5秒,retries为3-5次。
参数设置的逻辑要结合实际启动时间:interval 设短会增加检测频率,但服务启动慢时频繁失败反而容易误判;timeout 设太长会让依赖方等待更久。一般可以先按 10 秒间隔、3 秒超时、3 次重试起步,再观察服务日志中的失败输出。如果服务启动确实很慢,优先延长 start_period,而不是把 retries 盲目调大。
start_period 和循环依赖要单独处理
依赖服务如果是第三方镜像,且镜像本身没有提供 HEALTHCHECK 指令,可以在 compose 里覆盖 healthcheck,但前提是镜像里存在可执行的检查命令。另一个容易踩到的问题是循环依赖:当服务 A 的 depends_on 写了 B 的 service_healthy,而 B 的 depends_on 又写了 A 的 service_healthy,compose 会直接报错。循环依赖需要重新设计,比如把两个服务合并,或者拆成多个 compose 文件分阶段启动。
除了依赖 compose 的 condition,还可以准备一个等待脚本,在容器的 entrypoint 前面循环检查依赖服务的端口或 HTTP 接口,成功后再执行原命令。等待脚本方式的优点是不依赖 compose 版本,也能在 Kubernetes 的容器启动流程里借用同样的思路。但脚本必须设置超时和最大重试次数,否则会无限阻塞;脚本内要用 exec 执行原命令,避免留下僵尸进程。
改完后看这几个信号
配置完成后,先在测试环境跑一遍,不要直接改线上。观察 docker-compose ps 里依赖服务是否显示 healthy,确认通过后再启动应用容器;再对比应用容器启动时间,是否明显晚于依赖服务健康的时间。如果应用仍然报连接错误,先查健康检查的具体输出。docker inspect 的 Health 字段里有检查次数、失败次数和最近一次输出,能看出检查失败的大致原因。注意健康检查日志不一定直接输出到 docker-compose logs,需要结合容器应用日志一起判断。
回滚边界要提前想好。如果新配置导致依赖服务反复重启,可以先恢复原 compose 里的 depends_on 写法,回到只保证启动顺序的状态,同时保留 healthcheck 作为观察手段。等确认检查命令和参数都稳定后,再把 condition 加回来。整体思路是从“能启动”逐步过渡到“健康后再启动”,每一步都以容器日志和健康状态为准。
如果调整后仍然偶发连接失败,需要回到网络层面排查,比如依赖服务监听的是 127.0.0.1 还是 0.0.0.0、容器间网络是否受限、DNS 解析是否正常。depends_on 加健康检查解决的只是启动时序问题,不负责网络连通性。服务自身的超时重试、连接池大小这些参数,也需要配合应用容错设计,不能只靠编排层兜底。