在多容器应用中,Web 服务、数据库、缓存、消息队列往往需要协同启动。很多人以为只要把服务写进 Docker Compose,容器就会严格按照配置顺序依次就绪。实际上,配置文件中的排列位置并不代表启动顺序,而“容器已经运行”也不等于“容器内的应用已经可以提供服务”。如果没有正确处理依赖关系,就可能出现数据库尚未接受连接、应用便启动失败的情况。🚀
一、认识 depends_on 的作用
Docker Compose 使用 depends_on 描述服务之间的依赖。例如,应用服务依赖数据库和 Redis 时,可以声明 app 依赖 db 与 redis。执行 docker compose up 后,Compose 会先创建依赖服务,再创建应用服务;停止项目时则按照相反的依赖顺序移除服务。
services:
app:
image: my-app
depends_on:
- db
- redis
db:
image: postgres
redis:
image: redis
需要特别注意:这种简写方式主要保证容器的启动先后关系,不会等待数据库完成初始化。只要 db 容器进入运行状态,Compose 就可能继续启动 app。关于这一行为,可参考 Docker 的启动与关闭顺序官方文档。
二、启动完成不代表服务就绪
数据库首次启动时可能需要创建数据目录、初始化系统表或执行初始化脚本;消息队列可能需要恢复持久化数据;Web 服务也可能需要加载配置。此时容器进程虽然存在,但端口或业务接口未必可用。⚠️ 如果应用只在启动阶段连接一次,连接失败后直接退出,整个 Compose 项目就会表现得不稳定。
因此,服务依赖控制应区分两个概念:started 表示容器已经启动,healthy 表示服务通过了健康检查。对于数据库、缓存和外部接口代理等关键依赖,更适合使用健康状态作为后续服务的启动条件。
三、使用 healthcheck 判断服务状态
healthcheck 可以定期在容器内执行检测命令,并依据命令退出状态判断服务是否健康。以 PostgreSQL 为例,可以通过 pg_isready 检查数据库是否能够响应连接请求,而不是只判断数据库进程是否存在。
services:
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10
start_period: 15s
- test:定义实际执行的检测命令。
- interval:设置相邻两次检查之间的时间间隔。
- timeout:限制单次检查允许执行的最长时间。
- retries:指定连续失败多少次后标记为不健康。
- start_period:为初始化较慢的服务预留启动缓冲期。
健康检查应该验证真正影响业务的能力。例如,数据库应检查连接可用性,Redis 可使用 redis-cli ping,HTTP 服务可访问轻量级健康接口。检测命令不宜执行复杂查询或高开销任务,否则可能给服务自身增加不必要的压力。🩺
四、通过 condition 精确控制启动条件
为依赖服务定义 healthcheck 后,可以使用 depends_on 的长格式,并将 condition 设置为 service_healthy。这样 Compose 会等待依赖项进入健康状态,再启动后续服务。
services:
app:
image: my-app
depends_on:
db:
condition: service_healthy
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10
常用的三种依赖条件
- service_started:依赖容器启动后即可继续,适合无需等待业务就绪的场景。
- service_healthy:依赖服务通过健康检查后再继续,适合数据库、缓存和消息队列。
- service_completed_successfully:依赖任务成功执行并正常退出后再继续,适合数据库迁移、配置生成和初始化作业。
五、处理数据库迁移等一次性任务
生产型项目常见的链路是“数据库就绪 → 执行迁移 → 启动应用”。可以把迁移操作拆分为独立服务,让 migrate 依赖健康的 db,再让 app 依赖成功完成的 migrate。这样既能明确执行顺序,也能避免多个应用实例同时运行迁移命令。🔄
db 健康
↓
migrate 成功完成
↓
app 启动并提供服务
一次性任务必须在成功时返回退出码 0,失败时返回非零退出码,否则 Compose 无法准确识别执行结果。迁移脚本还应尽量具备幂等性,避免项目重新创建或任务重试时产生重复数据。
六、不要把可靠性全部交给 Compose
depends_on 和 healthcheck 能改善本地开发、测试环境及单机部署的启动体验,但应用自身仍应实现连接重试、超时控制和合理的错误处理。因为服务在成功启动后仍可能暂时不可用,例如数据库重启、网络抖动或缓存实例故障。
- 为外部依赖设置有限次数或带退避策略的重试。
- 区分启动探测、存活探测和业务就绪状态。
- 避免仅使用固定时长的 sleep 等待服务。
- 通过 docker compose ps 查看容器状态。
- 使用 docker compose logs 服务名定位健康检查与启动错误。
固定等待时间看似简单,却无法适应不同机器性能和数据规模:等待过短仍会失败,等待过长又会拖慢启动。相比之下,基于实际服务能力的健康检查更加准确,也更容易排查问题。🔍
总结
Docker Compose 的启动顺序控制应围绕“依赖关系”和“服务就绪状态”设计:使用 depends_on 描述依赖,使用 healthcheck 判断真实可用性,使用 service_healthy 等条件控制后续服务,并将迁移任务拆成可验证的一次性步骤。同时,应用层仍需具备重试和容错能力。只有把容器编排与程序可靠性结合起来,才能获得稳定、清晰且便于维护的启动流程。✅