滚动更新与零停机部署:deploy.sh 的设计

云原生与交付30 阅读约 5 分钟

很多人的「部署脚本」就是一句 docker compose up -d。它能跑,但有两个隐患:更新瞬间可能有停机、新版本起不来时没人兜底。我的 deploy.sh 围绕「零停机 + 失败自愈」多做了几步。

部署脚本要做的事

flowchart TD
    A["接收 service + image_tag"] --> B["记录当前 tag (供回滚)"]
    B --> C["拉取新镜像 tag"]
    C --> D["compose up -d 滚动替换该服务"]
    D --> E{"健康检查通过?"}
    E --> |"是"| F["清理旧镜像 → 完成"]
    E --> |"否"| G["回滚到旧 tag → 报警"]

核心逻辑大致是:

#!/usr/bin/env bash
set -euo pipefail

SERVICE="$1"          # 要部署的服务,如 api
NEW_TAG="$2"          # 目标镜像 tag(commit sha)

# 1. 记下当前 tag,失败时好回滚
OLD_TAG="$(current_running_tag "$SERVICE")"

# 2. 拉新镜像(失败就停,不动现有服务)
export IMAGE_TAG="$NEW_TAG"
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull "$SERVICE"

# 3. 滚动替换:只重建目标服务,依赖不动
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d \
  --no-deps "$SERVICE"

# 4. 健康检查
if ! wait_healthy "$SERVICE" 60; then
  echo "健康检查失败,回滚到 $OLD_TAG"
  IMAGE_TAG="$OLD_TAG" docker compose ... up -d --no-deps "$SERVICE"
  exit 1
fi

# 5. 成功,清理悬空镜像
docker image prune -f

关键动作拆解

① 先拉镜像,再替换。 pull 单独一步且失败即停。这样网络问题/镜像不存在时,现有服务原封不动,不会出现「拉到一半把旧的停了、新的又起不来」的中间态。

--no-deps 只动目标服务。 部署 API 时不该顺手重启 MongoDB、Redis。--no-deps 限定只重建指定服务,避免无谓地动数据库这类有状态依赖。配合容器自身的滚动替换,新容器健康后旧容器才退出,请求几乎无感切换。

③ 健康检查是零停机的灵魂。 「容器起来了」不等于「服务能用了」——进程在跑但还没连上数据库、还没加载完,照样 502。所以要轮询服务的健康端点,真正 ready 了才算部署成功

wait_healthy() {
  local svc="$1" timeout="$2" elapsed=0
  until curl -fsS "http://localhost:3000/health" >/dev/null 2>&1; do
    sleep 3; elapsed=$((elapsed + 3))
    [ "$elapsed" -ge "$timeout" ] && return 1
  done
}

④ 失败自动回滚。 健康检查没过,立刻用记录的 OLD_TAG 把服务拉回旧版本。因为旧镜像还在本地,回滚是秒级的。部署可以失败,但不能让站点挂着没人管。

set -euo pipefail 打底。 任何一步出错立即终止、未定义变量报错、管道中任意环节失败都算失败。部署脚本最怕「错了还继续往下跑」,这一行是底线。

为什么值得多写这几十行

  • 更新无感:用户在你部署时刷新页面,不会撞上 502;
  • 坏版本进不了生产:起不来的版本被健康检查挡下并自动回滚,不会「发布完才发现挂了」;
  • 可重复、可信任:每次部署走同样的流程,CI 调它、手动也能调它,行为一致。

小结

零停机部署的脚本要点:先 pull 后替换(保护现有服务)、--no-deps 只动目标、健康检查确认真正 ready、失败自动回滚到旧 tag、set -euo pipefail 兜底。多写这几十行,换来的是「敢在白天部署」的底气。

相关文章

评论 (7)

V

Vincent

小项目生产用 compose 还是直接上 k8s?感觉 k8s 太重了

林深时见鹿

学到了,谢谢补充

E

Echo

零停机这块细节真不少,受教了

K

kkk

请问多个 server_name 复用同一张证书会有坑吗?

G

Gavin

收到,这个点很值得补充,记下了 🙏

梦回

PM2 的开机自启我之前漏了 save,难怪重启就没了

半夏

生产环境我也是这么配的,稳