Ready-Set-BANG:三段式发布流程实战指南

发布时间:2026/9/2 19:47:18
Ready-Set-BANG:三段式发布流程实战指南 发布流程是最适合用固定节奏去约束的一类任务。如果把一次发布拆成 Ready、Set、BANG 三个阶段Ready 指的是环境准备Set 指的是构建与配置BANG 指的是启动和验证。这个命名常被用作项目发布脚本或自动化部署工具的内部代号它把一个容易失控的过程拆成了三段可检查、可回放、可重试的动作。对于后端开发、运维和 DevOps 工程师来说掌握这种三段式发布流程等于拿到了一套不依赖具体 CI 平台、能够手工落地的最小上线方案。这篇文章会围绕这个思路给出一个可运行的示例项目以及对应的脚本、配置、验证方法和排错路径。这类流程的真正价值不在于脚本本身而在于每一段都有明确的退出条件。Ready 没过就停止Set 没产出就不允许启动BANG 启动后必须完成健康检查、冒烟验证和基础压测失败则自动回滚。用这种方式发布就算命令最终还是人工敲的也比“先上服务器再说”要可控得多。1. 先理解 Ready、Set、BANG 三个关键词代表什么1.1 为什么发布不能只靠“运行启动命令”很多小型项目发布时确实只做两件事把新包传上去然后重启服务。这种做法的隐患不在重启动作本身而在重启之前和重启之后。重启之前可能需要确认端口是否被占用、磁盘是否足够、依赖命令是否安装、配置目录是否存在。这些问题只要有一个不满足服务启动后就会在几分钟内报错。更麻烦的是如果旧进程还没退出新进程又尝试监听同一个端口启动就会直接失败。重启之后至少要确认三件事进程是否活着、健康检查接口是否返回正常、核心功能是否可用。如果只看ps结果就认为发布成功忽略了业务接口 500那么用户看到的第一个错误就会比开发人员看到的更早。Ready、Set、BANG 这套命名就是把“发布前的检查、发布中的构建、发布后的验证”拆开。每个阶段可以单独执行也可以由编排脚本按顺序调用。这样设计有一个明显好处排错时不需要重跑整个过程哪个阶段失败就只修哪个阶段。1.2 三个阶段的职责边界可以用一张表明确阶段划分阶段目标关键动作失败处理Ready确认环境满足发布条件检查端口、目录、依赖命令、磁盘、用户权限中止发布输出检查报告Set生成可启动的制品和配置构建产物、版本号、配置渲染、备份当前版本中止发布不动当前服务BANG启动新版本并验证可用性启动服务、健康检查、冒烟测试、轻量压测自动回滚到上一个版本三个阶段之间不要互相跳步。比如不能在 Ready 还没完成时就先执行 Set因为 Set 阶段会生成新的版本目录和配置文件如果磁盘或权限有问题这些产物可能只生成了一半后续启动就会基于不完整的目录运行。1.3 什么情况下适合这个流程这套流程比较适合单体服务、少量节点的内部系统以及还没有引入 Kubernetes 或云原生编排平台的团队。它不需要额外安装侧车容器、服务网格或配置中心只要一台 Linux 服务器、一个 systemd 单元文件、一组 shell 脚本就能跑起来。如果项目已经是多服务微服务架构节点超过几十个或者已经有完善的 CI/CD 与发布平台那这类手写脚本可以降级为平台里的一个执行步骤而不是替代平台。脚本的价值在于讲清楚发布链路而不是推翻现有平台。学习环境适合最先跑通这套流程因为能在本地虚拟机或单台 Linux 服务器上完整模拟发布、验证、回滚。生产环境使用前还要补上权限控制、审计日志、监控告警和更严格的回滚验证。2. 准备一个最小示例目标结构与环境清单2.1 示例项目结构为了让发布流程从概念变成可复现的脚本需要先准备一个最简单的 HTTP 服务。示例服务使用 Node.js 原生http模块不依赖第三方包这样便于在任何一台装有 Node.js 的机器上运行。demo-server/ ├── src/ │ └── server.js ├── deploy/ │ ├── config.sh │ ├── ready.sh │ ├── set.sh │ ├── bang.sh │ ├── rollback.sh │ └── conf/ │ ├── app.tpl.yaml │ └── app.env.tpl └── package.jsonserver.js只提供两个接口/health用于健康检查/api/hello用于冒烟验证。真实项目中会有更多业务接口但验证链路可以沿用同样的思路。const http require(http); const server http.createServer((req, res) { if (req.url /health) { res.writeHead(200, { Content-Type: application/json }); res.end(JSON.stringify({ status: ok })); return; } if (req.url /api/hello) { res.writeHead(200, { Content-Type: application/json }); res.end(JSON.stringify({ message: hello })); return; } res.writeHead(404, { Content-Type: application/json }); res.end(JSON.stringify({ error: not found })); }); server.listen(8080, 0.0.0.0);package.json保持最小化{ name: demo-server, version: 1.0.0, private: true, scripts: { start: node src/server.js } }这个服务不依赖npm install在发布脚本里也不需要执行构建命令。接下来的重点是发布脚本如何准备环境、生成配置、启动和验证服务。2.2 环境要求和工具版本下表是本地跑通示例所需的工具版本并不苛刻工具用途建议版本Linux 服务器发布目标环境Ubuntu 20.04 或 CentOS 7Node.js运行示例服务18 或更高版本curl健康检查与冒烟请求7.68 及以上jq解析 JSON 响应1.6 及以上systemd管理服务进程随操作系统自带如果是学习环境不要求立刻引入 systemd也可以先直接用nohup node server.js启动。但生产环境建议使用 systemd因为它能处理进程守护、崩溃重启、日志收集和启动依赖关系。2.3 公共配置应该集中存放发布脚本里会有很多重复变量例如服务名、安装目录、端口、用户、日志目录。这些变量最好集中放在config.sh中避免在多个脚本里写死。#!/usr/bin/env bash set -Eeuo pipefail ENV_NAME${ENV_NAME:-test} APP_NAMEdemo-server APP_DIR/opt/${APP_NAME} RUN_USERwww-data PORT8080 LOG_DIR/var/log/${APP_NAME} BACKUP_DIR/data/backup/${APP_NAME} HEALTH_URLhttp://127.0.0.1:${PORT}/health HELLO_URLhttp://127.0.0.1:${PORT}/api/hello RELEASE_DIR${APP_DIR}/releases CURRENT_LINK${APP_DIR}/current注意set -Eeuo pipefail的含义-E让ERR陷阱在函数和子 shell 中也能生效。-e遇到非零退出码时立即退出脚本。-u使用未定义变量时报错。-o pipefail管道中任意命令失败整条管道返回失败。配合-e使用时要小心有些命令的返回码本身就是非零例如grep在找不到匹配时返回 1。因此在检查端口时不能直接让脚本因为grep退出。3. Ready 阶段把“应该没问题”变成检查清单3.1 用 ready.sh 做前置检查Ready 阶段的目标是在所有操作开始前确认目标服务器满足发布条件。以下脚本实现了一个适合学习环境的最小版本。#!/usr/bin/env bash set -Eeuo pipefail source $(dirname $0)/config.sh failed0 check_cmd() { if ! command -v $1 /dev/null 21; then echo [FAIL] command not found: $1 failed1 else echo [OK] command exists: $1 fi } echo Ready Phase echo env$ENV_NAME app$APP_NAME port$PORT for cmd in node curl jq; do check_cmd $cmd done echo --- check running user --- if [ $(id -u) -eq 0 ]; then echo [WARN] running as root; production should use $RUN_USER fi echo --- check port $PORT --- if ss -ltn 2/dev/null | grep -q :$PORT ; then echo [FAIL] port $PORT is already in use failed1 else echo [OK] port $PORT is free fi echo --- check app directory --- if [ ! -d ${APP_DIR} ]; then echo [WARN] app dir ${APP_DIR} does not exist, creating mkdir -p ${APP_DIR} fi df -h ${APP_DIR} | tail -1 echo --- check release dir --- mkdir -p ${RELEASE_DIR} mkdir -p ${LOG_DIR} mkdir -p ${BACKUP_DIR} if [ $failed -ne 0 ]; then echo Ready phase failed. Stop deployment. exit 1 fi echo Ready phase passed.这段脚本的关键点有三个。第一命令检查放在最前面。node不存在时后续启动必定失败jq不存在时冒烟测试里的 JSON 解析无法完成。第二端口检查要使用ss -ltn而不是老的netstat。ss在大多数现代 Linux 系统上默认可用输出也更稳定。如果只监听 IPv4ss -ltn会显示0.0.0.0:8080如果监听 IPv6则可能显示[::]:8080grep :$PORT 可以覆盖这两种格式。第三Ready 阶段发现正式目录不存在时不应直接失败而是创建目录。因为首次发布时目录本来就不存在这是预期场景。但命令缺失、端口被占用则必须失败。3.2 Ready 阶段的检查点可以把 Ready 阶段想象成体检表逐项检查后才允许继续操作系统和用户权限是否正确磁盘空间是否足够端口是否被占用依赖命令是否存在日志目录、发布目录、备份目录是否可写配置文件模板是否存在这些检查在生产环境要更严格。比如磁盘空间不能只看df还要判断剩余比例端口被占用时应进一步确认是哪个进程占用避免误杀其他服务。3.3 Ready 阶段的常见坑第一个坑是使用set -e后端口检查中的grep在没有匹配时返回 1导致脚本直接退出。解决方式是把grep放在if条件中或者临时关闭set -e。上面的写法把grep放在if条件中不会触发致命退出。第二个坑是只检查目录存在不检查目录权限。发布账号如果对APP_DIR没有写权限后续的mkdir、ln -s都会失败但 Ready 阶段却显示通过。更稳妥的做法是使用test -w或创建临时文件来验证写入权限。第三个坑是忽略端口可能被本机其他服务复用。如果 8080 已被监控系统或日志采集器占用发布过程可能直接把旧进程覆盖。因此端口检查是 Ready 阶段最关键的保全措施之一。4. Set 阶段构建、配置渲染和回滚点4.1 用版本目录承载每次发布产物Set 阶段的核心目标不是执行服务器端代码构建而是把要发布的内容整理成可启动的目录。为了避免覆盖旧版本按版本号建立目录是推荐做法。#!/usr/bin/env bash set -Eeuo pipefail source $(dirname $0)/config.sh VERSION${BUILD_ID:-$(date %Y%m%d%H%M%S)} TARGET_RELEASE${RELEASE_DIR}/${VERSION} echo Set Phase echo version$VERSION if [ -d $TARGET_RELEASE ]; then echo [FAIL] release dir already exists: $TARGET_RELEASE exit 1 fi mkdir -p $TARGET_RELEASE cp -r ${APP_DIR}/src $TARGET_RELEASE/ cp ${APP_DIR}/package.json $TARGET_RELEASE/ echo $VERSION $TARGET_RELEASE/VERSION这里出现了BUILD_ID环境变量。引入它的原因是要让脚本可以被 CI 系统调用。Jenkins、GitLab CI 等平台都会为每次构建生成唯一 ID发布时把它作为版本号能保证版本可追踪。手工执行时则自动使用时间戳版本号。4.2 配置文件模板不要写死环境信息如果配置里写死了测试环境地址发布到生产时就会连错数据库。推荐使用模板文件把变化项留给环境变量填充。以下是app.env.tpl模板ENV_NAME${ENV_NAME} PORT${PORT} LOG_DIR${LOG_DIR}以下是app.tpl.yaml模板app: name: ${APP_NAME} env: ${ENV_NAME} server: port: ${PORT} log: dir: ${LOG_DIR}渲染配置时先把变量导出再使用envsubst。envsubst来自 GNU gettext大部分 Linux 发行版默认包含。export PORT ENV_NAME LOG_DIR APP_NAME envsubst ${APP_DIR}/deploy/conf/app.tpl.yaml $TARGET_RELEASE/app.yaml envsubst ${APP_DIR}/deploy/conf/app.env.tpl $TARGET_RELEASE/app.env不要使用sed直接替换 YAML 或 JSON 中的变量。YAML 和 JSON 对缩进、转义和特殊字符敏感sed做字符串替换很容易破坏格式。envsubst只匹配${VAR}结构不解析 YAML相当于只做了纯文本替换仍然安全。如果模板中包含密钥不应以明文写入配置目录。更推荐的方式是启动时从环境变量或密钥管理工具读取。示例项目没有涉及数据库密码但生产环境必须考虑这一点。4.3 发布目录指针与备份新版本内容准备完成后需要让运行中的服务指向新版本目录。这里使用符号链接current表示当前版本。if [ -L $CURRENT_LINK ]; then BACKUP_NAMEcurrent.bak.$(date %s) cp -a $CURRENT_LINK ${BACKUP_DIR}/${BACKUP_NAME} echo [OK] backup current to ${BACKUP_DIR}/${BACKUP_NAME} fi ln -sfn $TARGET_RELEASE $CURRENT_LINK echo [OK] current link - $TARGET_RELEASE备份时使用cp -a把当前链接指向的整个目录复制到备份目录而不是只保存符号链接。如果只保存链接回滚时可能发现旧目录已经被清理导致无备份可回滚。4.4 Set 阶段的常见坑第一个坑是版本目录存在时继续覆盖。如果 CI 再次执行同一次构建BUILD_ID相同目录可能已经存在。脚本应该检测到存在后直接失败或跳过而不是覆盖避免产物不一致。第二个坑是备份只备份配置不备份数据。发布前如果涉及数据库结构变更应用代码会从旧版本切换到新版本但数据库 Schema 已经不可逆。这类数据库迁移必须单独设计迁移脚本和回滚计划不能依赖目录级备份。第三个坑是配置文件里的行尾符或编码问题。Windows 下创建的模板文件可能带 CRLF渲染出来的 YAML 可能在启动时解析失败。在 Linux 上执行前建议用file命令检查模板编码或用sed -i s/\r$//清除回车符。5. BANG 阶段启动、健康检查、冒烟验证和压测5.1 用 systemd 管理服务启动Set 阶段只生成了目录和配置还没有启动进程。BANG 阶段要完成启动动作并且让进程保持在可控状态。生产环境推荐使用 systemd 单元文件。创建/etc/systemd/system/demo-server.service[Unit] DescriptionDemo Server Afternetwork.target [Service] Userwww-data WorkingDirectory/opt/demo-server/current EnvironmentFile/opt/demo-server/current/app.env ExecStart/usr/bin/node src/server.js Restarton-failure RestartSec3 StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.target关键参数说明参数含义注意点User服务运行用户不要用 root建议使用低权限账号WorkingDirectory进程工作目录必须指向 current 链接目录EnvironmentFile环境变量文件文件权限需要控制好ExecStart启动命令路径要写成绝对路径Restart崩溃重启策略on-failure是常用策略RestartSec重启间隔设置过短会频繁拉起然后执行sudo cp /opt/demo-server/deploy/systemd/demo-server.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable demo-server.service sudo systemctl restart demo-server.servicedaemon-reload不能省略。修改了单元文件后systemd 不会自动重新读取必须先执行。5.2 健康检查必须等待服务进入可用状态服务启动和进程就绪之间有一段时间差。有的服务启动很快几百毫秒内就能监听端口有的服务需要加载模型、初始化连接池或预热缓存可能要几十秒。因此 BANG 阶段不能只restart一下就算完成。以下脚本实现轮询健康检查#!/usr/bin/env bash set -Eeuo pipefail source $(dirname $0)/config.sh echo BANG Phase echo health url: $HEALTH_URL for i in $(seq 1 30); do if curl -fsS --max-time 3 $HEALTH_URL /dev/null 21; then echo health check ok after ${i}s break fi if [ $i -eq 30 ]; then echo health check failed after 30s ${APP_DIR}/deploy/rollback.sh exit 1 fi sleep 1 done这里有一个重要细节健康检查不只判断进程是否存活而是向/health接口发起真实 HTTP 请求。curl -fsS中-f表示遇到 4xx/5xx 时返回错误。-s表示静默模式不输出进度条。-S表示显示错误信息。如果接口返回 200但 JSON 内容不是{status:ok}仅靠curl还不够。因此健康检查之后还要检查响应体的内容。5.3 冒烟测试验证核心业务路径健康检查只能证明服务在线不能证明业务可用。一个常见的例子是服务启动后依赖的数据库连接失败但/health接口因为不走数据库而返回 200。因此需要冒烟测试请求真实业务路径。BODY$(curl -fsS --max-time 3 $HELLO_URL) || { echo smoke test request failed ${APP_DIR}/deploy/rollback.sh exit 1 } echo smoke response: $BODY if ! printf %s $BODY | jq -e .message hello /dev/null 21; then echo smoke test failed: unexpected response ${APP_DIR}/deploy/rollback.sh exit 1 fi echo smoke test okjq -e .message hello在表达式结果为true时返回 0表达式结果为false或解析失败时返回非零。这样可以精确判定字段值是否符合预期而不是只看请求有没有 200。冒烟测试的用例应该覆盖最核心的链路。如果服务是订单系统至少验证创建订单接口能返回业务成功如果是支付回调系统至少验证签名校验链路。示例里的/api/hello只是占位真实项目中要换成具体业务场景。5.4 轻量压测判断“能不能扛住初期流量”冒烟测试通过后可以用一个轻量压测快速观察服务在并发下的表现。这里推荐ab工具如果没有安装可以通过系统包管理器安装。ab -n 1000 -c 20 $HELLO_URL含义如下-n 1000总共请求 1000 次。-c 20并发数为 20。$HELLO_URL压测接口地址。压测后关注三个指标Failed requests是否为 0。Requests per second是否满足预期。Time per request是否在可接受范围内。压测输出示例Complete requests: 1000 Failed requests: 0 Requests per second: 523.45 [#/sec] (mean) Time per request: 38.207 [ms] (mean)需要注意这种压测只是冒烟级验证不能代表真实容量。真实流量是带有业务参数、登录态、数据库读写和网络延迟的与ab打静态接口完全不同。它更适合作为发布后“服务没有明显性能回退”的参考。5.5 失败时自动回滚BANG 阶段如果健康检查、冒烟测试或压测失败需要回滚。rollback.sh示例#!/usr/bin/env bash set -Eeuo pipefail source $(dirname $0)/config.sh echo Rollback LATEST_BACKUP$(ls -1t ${BACKUP_DIR}/current.bak.* 2/dev/null | head -1) if [ -z $LATEST_BACKUP ]; then echo no backup found, cannot rollback exit 1 fi echo restore from $LATEST_BACKUP ln -sfn $LATEST_BACKUP $CURRENT_LINK sudo systemctl daemon-reload sudo systemctl restart ${APP_NAME}.service sleep 3 if curl -fsS --max-time 3 $HEALTH_URL /dev/null 21; then echo rollback health ok else echo rollback health failed, need manual intervention exit 1 fi回滚时直接用备份目录替换current符号链接并重启服务。回滚后也必须重新做一次健康检查否则可能出现“回滚动作成功但服务仍然起不来”的假象。5.6 BANG 阶段的常见坑第一个坑是健康检查只等了 3 秒就放弃。服务在重启瞬间可能还在初始化超过等待时间后才正常。轮询 30 次、每次等待 1 秒是相对稳妥的起点实际项目要根据服务启动时间调整。第二个坑是压测打到了生产数据库。ab并发 20 可能只读接口还好但如果冒烟用例创建了脏数据应用又连的是生产库发布验证就会污染数据。生产环境做验证时最好使用独立测试账号、只读副本或可回滚的数据隔离方案。第三个坑是回滚到旧版本后发现旧版本无法使用新配置。比如新版本本来依赖一个新的环境变量回滚后旧版本代码遇到这个变量会报错。因此回滚验证不能只确认进程启动还要确认旧版本的核心逻辑也能正常工作。6. 发布流程出问题时从现象倒推根因6.1 现象Ready 阶段端口检查误报如果脚本里写了这样的命令ss -ltn | grep :$PORT 在端口未被占用时grep退出码为 1在set -e环境下脚本会在这一行退出。这不是端口真的有冲突而是脚本写法的问题。检查方式单独执行ss -ltn | grep :$PORT 观察是否有输出。 处理建议把检查放在if条件中或者用if ss -ltn 2/dev/null | grep -q :$PORT ; then。预防方式在 Ready 脚本里为这类非零返回命令加显式条件判断并输出真实状态。6.2 现象Set 阶段生成 YAML 后服务启动失败如果使用了sed替换模板中的变量很容易出现 YAML 缩进被打乱、特殊字符被转义、字符串包含空格后引号缺失等问题。启动时 Node 读取 YAML 可能会报语法错误。检查方式查看生成的app.yaml内容重点看缩进和引号。处理建议改用envsubst或yq这类专用工具渲染配置。envsubst处理环境变量更安全yq可以按 YAML 语义修改字段。预防方式生产环境尽量使用配置中心或环境变量注入避免在发布脚本里手工拼接 YAML。6.3 现象健康检查循环超时但服务看起来正常有时curl已经能在浏览器访问但脚本里循环 30 秒仍然失败。常见原因有服务监听在 IPv6但curl请求解析到 IPv4。/health路径大小写或携带了额外路径前缀例如/api/health。systemd 启动时没有真正启用服务restart报错但脚本没有感知。防火墙只放行公网端口本机127.0.0.1请求被限制。检查方式在服务器上手动执行curl -v http://127.0.0.1:8080/health观察实际响应。执行journalctl -u demo-server.service -n 50查看服务日志。处理建议根据具体原因修正监听地址、修正健康检查 URL 或关闭对应限制。生产环境还应检查systemctl status demo-server.service是否处于active (running)。6.4 现象回滚后服务仍然不健康回滚成功不代表可用性恢复。数据库 Schema 已经变更、旧代码不兼容新数据、配置文件被新版本覆盖都可能导致回滚后依旧 500。检查方式回滚后查看应用日志重点看数据库连接错误、配置解析错误、模块加载错误。处理建议先判断旧版本能否在当前数据库结构下运行。如果数据库迁移不可逆需要执行专门的数据回滚脚本。回滚前应保存旧版本的完整配置和数据库 Schema 备份。预防方式发布前明确每次变更的可回滚性。不可回滚的变更需要额外设计补偿机制不能只靠代码目录回滚。6.5 统一排错清单问题现象常见原因检查命令处理建议Ready 阶段直接退出grep返回码触发set -eset e后执行脚本在if条件中调用grep端口检查误报端口被其他服务占用ss -ltnp确认占用进程调整端口服务启动失败配置文件格式错误journalctl -u demo-server.service -n 50检查模板渲染结果健康检查失败健康检查路径错误curl -v http://127.0.0.1:8080/health修正 URL 或监听地址冒烟测试失败接口返回不符合预期手动请求业务接口查看 JSON修正断言条件或检查服务依赖压测失败并发下出现大量错误ab -n 100 -c 20单独验证查看日志确认连接池、数据库等资源回滚后仍失败数据库或配置不兼容查看应用日志中的 ERROR设计数据回滚和配置回滚7. 生产环境建议把三段流程扩展到团队级发布7.1 学习环境与生产环境的差异本地跑通脚本只是第一步。生产环境的发布约束比学习环境多得多下面这张表可以帮助快速识别差异维度学习环境生产环境运行用户当前用户或 root专用低权限账号服务管理直接node server.jssystemd / supervisor配置管理本地模板替换配置中心或密钥管理日志终端输出集中式日志平台监控无Prometheus、告警、健康检查回滚手工执行脚本自动化触发有限时要求审计无记录操作人、时间、版本数据库变更忽略必须包含迁移计划生产环境不只要考虑“能不能发布”还要考虑“发布后如果影响线上流量如何在最短时间内止损”。因此回滚脚本、监控告警和值班人的响应机制最好提前演练。7.2 可复用的发布前检查清单每次发布前可以把以下清单作为人工检查项代码分支和版本号是否明确本次变更包含哪些接口、配置、数据库脚本依赖的服务和中间件是否已经准备好配置模板中的环境变量是否都在目标环境可用端口和资源是否被其他任务占用备份目录是否有足够空间回滚脚本是否可用监控面板能否看到新版本指标是否有已经验证过的回滚方案是否通知了相关团队和负责人这些项不一定全部自动化但至少要有人确认。每一项后面可以填写负责人发布会议或发布通知里直接对齐结果。7.3 接入 CI/CD 时如何映射三个步骤示例脚本已经是独立 shell 脚本因此很容易接入 Jenkins、GitLab CI 或 GitHub Actions。大致映射关系如下CI 阶段调用脚本成功条件readydeploy/ready.sh退出码为 0setdeploy/set.sh生成 release 目录和配置bangdeploy/bang.sh健康检查、冒烟、压测全部通过在 GitLab CI 中一个简化示例stages: - ready - set - bang ready: stage: ready script: - bash deploy/ready.sh set: stage: set script: - bash deploy/set.sh needs: [ready] bang: stage: bang script: - bash deploy/bang.sh needs: [set]接入 CI 后脚本依然可以单独在服务器上运行。不要把唯一入口绑死在 CI 平台否则 CI 不可用时运维人员无法手工发布。7.4 发布时长、节奏与人为检查三段式发布不等于全自动化。小规模项目可以在 10 到 20 分钟内完成一次发布但重点是发布过程的可控性。建议团队约定固定发布窗口避免凌晨三点临时上线。每次发布最好指定两个角色执行人和复核人。执行人运行脚本复核人盯监控和日志。BANG 阶段如果出现业务量异常复核人有权发起回滚而不必等执行人确认。对于初学者建议先用一台开发虚拟机完整跑一遍 Ready、Set、BANG 三个脚本故意制造一次健康检查失败观察回滚是否生效。把这条链路练熟之后再考虑引入平台级发布系统。8. 最后要给团队的实践建议Ready、Set、BANG 这类命名的好处是每个阶段都有明确动词准备好环境、设置好产物、然后才触发启动。它强迫发布流程先回答“环境是否允许”和“产物是否正确”再回答“服务是否能跑”。在落地时不要一开始就追求把全部检查自动化。先在服务器上手动执行ready.sh观察输出再执行set.sh确认current链接指向新版本最后执行bang.sh确认服务健康。跑通一次后再把三个阶段串进 CI。实际项目里最容易出问题的往往不是脚本本身而是环境差异。同一套脚本在测试环境通过到生产环境却可能因为端口占用、权限不足、配置模板缺少变量而失败。所以每个阶段都要保留清晰的日志输出失败时能直接定位到具体检查项。如果团队要在一个现有系统上引入这套流程建议第一次发布不切换流量只做演练。演练时故意让健康检查失败一次验证回滚能否恢复。演练通过后再进入真实发布。这样做的目的只有一个让发布从“祈祷不出错”变成“出错也能回来”。