Docker 运维入门实战:不停机更新,改了代码不用停掉整个站
关键词:docker compose up、滚动更新、健康检查、回滚、镜像标签
上一篇我们给 my-app 加上了日志和健康检查:出问题先看日志,服务健不健康看 docker compose ps 的状态。今天把这套东西用起来——改了代码之后,怎么更新这几个容器,而不用把整个站停掉再重起。
先说一个实话:单机、每个服务只有一个副本时,不存在真正的"零中断"。目标是把中断压到一次只影响一个服务、几秒钟,并且每一步都能验证、能回滚,而不是 docker compose down 一把梭,把网关、网页、接口全停了再慢慢起。
本文接着前面的结构讲:gateway(Nginx 网关)、web(静态网页)、api(8000 端口的 Python 接口服务),并且假设上一篇的健康检查已经配好——没有它,下面的"等健康了再继续"就无从谈起。
一、更新前先分清:改了什么,就动什么
my-app 里三类改动,对应三种更新范围,别一上来就全量重建:
- 只改了
api的代码:只重建、只重启api,web和gateway不动。 - 只改了
web的静态文件:只重建、只重启web。 - 改了
gateway的 Nginx 配置:只重启gateway,而且要先验证配置。
全量 docker compose up -d --build 在服务少的时候也能跑,但它会把所有容器按依赖顺序重建一遍,中断面最大,还容易把一个没改动的服务也卷进一次本可避免的重启。更新的原则是:动得越少,炸得越小。
二、给镜像打个版本号,别只用 latest
回滚的前提是旧镜像还在。先给要更新的服务打个明确的标签,再构建:
# 假设 api 这次改动记为 v1.1,旧版本 v1.0 的镜像仍在本地
docker compose build api
docker tag my-app-api:latest my-app-api:v1.1
更稳妥的做法是在 docker-compose.yml 里把镜像名写死成带版本的:
services:
api:
build: ./api
image: my-app-api:v1.1
这样 docker compose ps 里一眼就能看出每个服务跑的是哪个版本,出问题要回滚时,也不用猜"刚才跑的到底是哪一份代码"。latest 永远指向"最新一次构建",它不是一个版本,而是一个会变的指针——排查和回滚都别依赖它。
三、一次只更新一个服务:构建、重启、等健康
以 api 改了一个接口逻辑为例,完整顺序是:
docker compose build api
docker compose up -d --no-deps api
--no-deps 是关键:它只重建 api,不连带重启 gateway 和 web。Compose 会停掉旧的 api 容器、起一个新的,然后健康检查开始探活。这时不要急着动下一个服务,先等它健康:
docker compose ps api # STATUS 一列应出现 (healthy)
docker compose logs --tail 50 api # 看启动日志有没有报错
等 (healthy) 出现,再从网关侧验证一次接口真的通了,而不是只看容器在跑:
curl http://localhost/api/health || curl https://你的域名/api/接口路径
这一步的意义在上一篇已经讲过:进程在跑不等于接口能用。健康检查通过 + 网关侧请求有正常响应,这次更新才算落袋。web 的更新同理:docker compose build web,再 docker compose up -d --no-deps web,等健康、再验证页面。
四、网关配置改了:先验证,再重启
如果这次改的是 gateway 的 Nginx 配置(比如加了一个转发规则),重启之前先在容器里验证配置语法,配错了直接停下,别让一个坏配置把入口换掉:
docker compose exec gateway nginx -t
docker compose up -d --no-deps gateway
docker compose logs --tail 50 gateway
nginx -t 通过再重启,是网关更新里成本最低的一道保险。重启后同样从外侧请求一次,确认网页和接口都还能通过网关访问——网关是所有流量的入口,它一坏,所有服务看起来都坏了。
五、更新顺序:后端先行,网关最后
多个服务都要更新时,按这个顺序走,一次一个:
api:先更新后端,验证健康、验证接口。web:再更新网页,验证页面。gateway:只有它的配置变了才动它,而且放在最后。
理由很简单:网关转发到后端,后端先就位,网关再怎么重启,转发目标都是健康的。反过来先重启网关、后端还是旧的甚至正在重启,网关那几秒的请求就全落空了。顺序本身也是一种中断控制。
六、出问题了怎么回滚:别现想,现成的步骤
更新后验证没通过(健康检查变 (unhealthy)、日志里有报错、网关侧请求失败),先别急着再改代码,先回滚到上一个能用的版本,把服务恢复了再慢慢查:
# 把 api 的镜像标签改回 v1.0(或直接用旧镜像名),再重建重启
docker compose up -d --no-deps api
docker compose ps api
docker compose logs --tail 50 api
回滚时同样只动出问题的那个服务,同样等健康、同样从网关侧验证。回滚不是"认输",它是更新流程的一部分:能快速回滚的更新,才敢放心更新。如果旧镜像已经被删了、旧代码也没打标签,回滚就只能靠"重新构建一份旧代码",那一步的成本和风险都高得多——所以第二步的版本号不是形式,是回滚的入场券。
七、三个新手最容易踩的坑
1. 用 docker compose down 做更新
down 会停掉并删除所有容器,包括网关和没改动的服务,整个站先全停,再一个一个起。这是部署,不是更新。日常更新用 up -d --no-deps <服务名>,只动该动的。
2. 不等健康就更新下一个服务
上一个服务还在 (health: starting),下一个已经重启了,出了问题根本分不清是谁的锅。一次一个、等健康、再验证、再继续,这四步别省。
3. 更新完只看容器在跑
docker compose ps 显示 Up 只是容器在跑。更新的验收标准是三条:健康状态 (healthy)、服务日志无报错、从网关侧真实请求一次有正常响应。三条缺一,都不算更新完成。
结语
从单容器、Compose 编排、数据卷、反向代理和 HTTPS、日志与健康检查,到今天的滚动更新,这条线已经是一套能对外服务、出了问题能定位、改了代码能安全更新的部署了。下一篇可以往"别让一个服务拖垮整台机器"走一步:给每个容器加上内存、CPU 限制和重启策略,让单个服务的异常有边界。
- 点赞
- 收藏
- 关注作者
评论(0)