Docker Compose 原生并不支持复杂的滚动更新策略,生产环境若要求零停机,通常需要在服务前加装负载均衡器,并通过脚本控制多副本的启停顺序,或者直接采用 Docker Swarm 模式。
先说结论:单纯使用 docker-compose up -d 无法实现真正的零停机,必须结合负载均衡和多副本机制。
- 适合:中小规模单机部署,有反向代理前置的场景
- 先准备:配置健康检查接口,确保新容器就绪再接管流量
- 验收:更新后观察负载均衡器后端状态,确认无 502 报错
命令速用版
# 常规更新会导致短暂停机(旧容器停止后新容器才启动)
docker-compose up -d `--no-deps` `--build` app
# 推荐思路:先扩容,再更新,最后缩容
docker-compose up -d `--scale` app=2
# 等待健康检查通过
docker-compose up -d `--no-deps` `--build` app
# 确认新容器正常后,再缩容回 1
docker-compose up -d `--scale` app=1为什么会这样
Docker Compose 的设计初衷更多是面向开发和单机部署,其默认行为是在更新服务时先停止旧容器,再启动新容器。如果端口被旧容器占用,新容器无法启动,这中间必然存在服务不可用的时间窗口。公开资料中没有看到可靠的量化数据说明这个窗口具体多长,但取决于容器启动速度和业务初始化逻辑。
要实现零停机,核心在于“新旧共存”。需要在旧容器停止前,让新容器已经启动并通过健康检查,同时由前端的负载均衡器(如 Nginx、Traefik)将流量逐步切换到新容器。Docker 官方文档也指出,对于生产环境的高可用部署,推荐使用 Docker Swarm 或 Kubernetes 等编排工具,因为它们原生支持滚动更新策略。
分步处理
1. 前置负载均衡:确保你的 Compose 服务前有一层反向代理。不要直接暴露业务容器端口给公网,而是让 Nginx 代理到容器。
2. 配置健康检查:在 docker-compose.yml 中为服务添加 healthcheck,确保容器完全启动就绪。
services:
app:
image: myapp:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 5s
timeout: 3s
retries: 33. 执行扩容更新:不要直接替换。先将副本数增加到 2,此时旧版本仍在运行。然后触发新镜像的更新,新容器会加入集群。
4. 验证新实例:检查新容器的健康状态是否为 healthy。可以使用 docker inspect 查看 State.Health.Status。
5. 缩容旧实例:确认新容器稳定运行后,将副本数缩容回预期数量,旧容器会被自动停止移除。
怎么验证是否生效
1. 持续请求测试:在更新期间,使用脚本或工具对服务接口发起持续请求,观察是否有连接拒绝或 502 错误。
while true; do curl -s -o /dev/null -w "%{http_code}\n" http://your-domain.com; sleep 0.5; done2. 日志检查:查看负载均衡器的错误日志,确认更新期间没有大量的 upstream 连接失败记录。
3. 容器状态:使用 docker ps 确认更新过程中,始终至少有一个容器处于 Up 状态。
常见坑
1. 数据库迁移:如果更新涉及数据库结构变更,必须确保新旧代码兼容数据库,或者在更新前单独执行迁移脚本,否则新容器启动会报错,旧容器停止后服务中断。
2. 会话保持:如果应用依赖本地 Session,多副本共存会导致用户登录状态丢失。需要将会话存储到 Redis 等外部中间件,或配置负载均衡器的 Sticky Session。
3. 资源竞争:滚动更新期间容器数量翻倍,需确保服务器内存和 CPU 足够支撑临时扩容,否则可能触发 OOM 导致所有容器崩溃。
4. 端口冲突:如果在 Compose 中绑定了宿主机固定端口(如 80:80),扩容多副本时会冲突。建议使用 Docker 网络内部通信,只在负载均衡层暴露端口。
参考来源
- Docker 官方文档 - Docker Compose 概述:https://docs.docker.com/compose/
- Docker 官方文档 - Swarm 模式滚动更新:https://docs.docker.com/engine/swarm/swarm-tutorial/rolling-update/