Skip to content

无停服更新技术方案

基于反向代理、蓝绿槽位、健康检查、原子切流和快速回滚的无停服发布教程,适合 1Panel、Docker Compose、Caddy / Nginx 部署场景。

基于反向代理、蓝绿槽位、健康检查、原子切流和快速回滚的无停服发布教程,适合 1Panel、Docker Compose、Caddy / Nginx 部署场景。

无停服更新的目标不是“永远不会出错”,而是让发布过程具备可验证、可切换、可回滚的工程结构。 用户请求始终由一个健康版本承接,新版本只有在通过检查后才进入生产流量。

目标

做法

结果

不中断访问

旧版本继续服务,新版本在空闲槽位启动

用户请求不会打到未启动完成的实例

降低发布风险

先内部健康检查,再公网 smoke test

问题在切流前暴露

快速回滚

旧槽位不立刻停止

新版本异常时可以切回旧 upstream

便于审计

记录镜像 tag、commit、active 槽位、验证结果

出问题时能定位是哪次发布引入

核心思路

core-ideatext

无停服更新不是“发布速度快”,而是把风险拆成 4 个可控动作:

1. 老版本继续对外服务
2. 新版本先部署到空闲环境
3. 新版本健康检查通过后,再切换流量
4. 老版本保留一段时间,发现问题可以快速切回

传统发布经常是“停旧服务、替换文件、启动新服务”。这会带来窗口期: 新服务启动慢、健康检查失败、静态资源不完整、数据库迁移异常,都会直接影响用户。 蓝绿发布把这些动作移到空闲槽位完成,最后只做一次反向代理切换。

推荐架构

architecturetext

用户请求

域名 / CDN / DNS

反向代理层(Caddy / Nginx / Traefik)

当前 active upstream
  ├─ app-blue   当前生产版本
  └─ app-green  空闲发布槽位

发布时:
1. 如果 blue 正在对外服务,就把新版本部署到 green
2. green 健康检查通过后,把反向代理 upstream 指向 green
3. blue 暂时保留,作为回滚版本

组件

职责

关键要求

反向代理

接收公网请求,并转发到 active 槽位

支持 reload,不应重启导致断流

blue 槽位

一个完整应用实例

能独立提供服务

green 槽位

另一个完整应用实例

与 blue 共享同一套外部依赖,但端口或容器名隔离

健康检查

判断实例是否可接流量

不能只检查进程存在,要检查依赖是否可用

回滚入口

把 active upstream 指回旧槽位

必须简单、明确、可重复执行

发布前检查

prechecktext

发布前检查:

1. 确认当前分支、commit、镜像 tag 或构建产物版本
2. 确认本地没有未提交的关键变更
3. 确认私钥、ssh.md、.env、证书等敏感文件没有进入 Git
4. 确认数据库迁移是否向后兼容
5. 确认新版本有 /health 或 /ready 健康检查
6. 确认反向代理可以无中断 reload
7. 确认有明确回滚路径

标准发布流程

release-flowtext

标准蓝绿发布流程:

1. 识别当前 active 槽位
2. 选择 idle 槽位作为新版本部署目标
3. 拉取或构建新镜像
4. 启动 idle 槽位容器
5. 等待 idle 槽位 /ready 返回 200
6. 对 idle 槽位做内部 smoke test
7. 更新反向代理 upstream 到 idle 槽位
8. reload 反向代理
9. 通过公网域名验证 /ready、/health 和核心页面
10. 保留旧槽位一段时间,用于快速回滚

判断发布是否成功,不能只看容器是否 running。必须从公网域名验证真实路径, 因为用户访问链路还包括 DNS、CDN、TLS、反向代理、静态资源和后端接口。

Docker Compose 示例

下面是简化示例。真实生产环境还会包含网络、卷、环境变量、日志、资源限制和反向代理配置。

docker-compose.bluegreen.ymlyaml

services:
  app-blue:
    image: ghcr.io/example/app:2026-05-27-001
    container_name: app-blue
    environment:
      - APP_SLOT=blue
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/ready"]
      interval: 10s
      timeout: 3s
      retries: 30

  app-green:
    image: ghcr.io/example/app:2026-05-27-002
    container_name: app-green
    environment:
      - APP_SLOT=green
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/ready"]
      interval: 10s
      timeout: 3s
      retries: 30

反向代理切流

反向代理层只维护一个 active upstream 文件。切流时替换这个文件并 reload 代理, 不直接停止旧容器。

blue-active.confcaddyfile

# caddy-upstreams/app-active.conf
reverse_proxy app-blue:3000

green-active.confcaddyfile

# caddy-upstreams/app-active.conf
reverse_proxy app-green:3000

release-commands.shbash

# 1. 查看当前运行状态
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"

# 2. 查看当前 active upstream
cat caddy-upstreams/app-active.conf

# 3. 启动空闲槽位
docker compose up -d app-green

# 4. 等待健康检查
curl -fsS http://app-green:3000/ready

# 5. 切换 upstream 后 reload 反向代理
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile

# 6. 从公网域名验证
curl -fsS https://example.com/ready
curl -fsS https://example.com/health

前端静态资源热更新

如果只是改文案、样式、前端页面或 i18n 文案,不一定需要重建后端镜像。 可以把前端产物作为静态资源覆盖目录发布,再通过新槽位加载新资源。

frontend-only-hotfixtext

前端静态资源热更新策略:

1. 本地构建前端 dist
2. 打包 dist,避免带入 macOS ._* 文件
3. 上传到服务器临时目录
4. 解压到 data/public.new-时间戳
5. 校验文件完整性和权限
6. 把旧 data/public 原子移动为备份目录
7. 把 public.new 原子切换为 data/public
8. 让新槽位读取静态资源目录
9. 健康检查通过后切换流量
10. 保留旧 public 目录用于回滚

健康检查设计

health-checkstext

建议至少准备 3 层检查:

1. /health
   进程是否存活,依赖是否能初始化

2. /ready
   当前实例是否已经准备好接收真实流量

3. smoke test
   用公网域名访问核心页面或核心 API,确认反向代理、静态资源、鉴权和数据库都正常

/health 可以很轻,表示进程活着;/ready 应该更严格, 表示实例可以接流量。发布切流必须以 ready 为准。

回滚方案

rollbacktext

回滚流程:

1. 如果新槽位还没切流量:停止新槽位,继续保留旧版本
2. 如果已经切流量:把 upstream 改回旧槽位
3. reload 反向代理
4. 通过公网域名验证 /ready、/health 和核心业务
5. 如果是前端静态资源问题,把 data/public.bak 恢复为 data/public
6. 记录失败原因,禁止在未定位原因前重复发布同一版本

回滚方案要在发布前就存在,而不是线上出问题后临时想。最实用的做法是: 新版本切流后不要立刻停止旧槽位,至少保留到观察窗口结束。

高风险场景

danger-listtext

这些情况不适合直接蓝绿切换:

1. 数据库迁移不向后兼容
2. 新旧版本不能同时连接同一份数据
3. 消息队列消费者会重复消费或抢锁
4. 文件存储目录结构被破坏性变更
5. 新版本启动成功,但核心业务没有 smoke test
6. 反向代理 reload 会中断连接,且没有验证过

发布后观察

post-releasetext

发布后观察:

1. 公网 /ready 和 /health 状态码
2. 反向代理日志里的 4xx / 5xx
3. 新槽位容器最近 5-10 分钟错误日志
4. 核心接口延迟和首包时间
5. 前端 index.html 是否引用新 chunk
6. 控制台是否出现静态资源 404
7. 用户侧是否有登录、支付、API 调用等关键链路异常

建议每次发布都记录:发布时间、commit、镜像 tag、切换前 active 槽位、切换后 active 槽位、 健康检查结果、smoke test 结果和是否保留旧槽位。

下一步

统一 API 网关 · OpenAI Compatible