--- id: deploy-and-rollback type: sop group: B status: published version: 1 updated: 2026-11-06 token_estimate: 2526 --- # 如何做一次站点部署与回滚 > **用途**:把改好的文件推上线,出问题时能退回去。核心不是「怎么推」,是**先能退,再往前**。 > > **覆盖**:静态站点、单机部署、有可写备份位或版本库的场景。 > > **产出**:一次可追溯的发布记录,加一个验证过的回退点。 > > 拿到就能跑:第二步「建回退锚点」不能跳。跳过它,后面每一步都在赌。 --- ## 触发的变异问法 - 「怎么把这个改动推上线」 - 「部署完发现不对,怎么退回去」 - 「推上去了但页面没变」 - 「怎么确认线上真的是新版本」 - 「改了 CSS 结果整站样式乱了」 - 「发布前要检查什么」 - 「怎么回滚到上一个版本」 - 「线上和本地不一致,以哪个为准」 - 「紧急修复,能直接改线上吗」 --- ## 什么时候不要用这份文件 - **改的是服务器配置**(反向代理、证书、数据库参数)—— 这些不在文件层面,回退方式是改回配置再重载,不走这套。 - **要删线上文件** —— 删除不可逆,需要单独的确认流程,不能用「部署」的名义捎带。 - **含数据库迁移** —— 文件能回滚,数据不能。有迁移时必须先有数据快照。 - **既没有备份位也没有版本库** —— 无法建立回退点。先建一个,再谈部署。 --- ## 执行步骤 ### 第一步 · 先探测三处 推之前先看清现状。**三处 = 本地源 / 镜像(或备份仓)/ 线上。** 对每个要动的文件,取三样:**大小、md5、修改时间**。 三处一致 → 说明没人动过,可以推。 线上与本地不同 → 正常,这正是要推的原因。 **线上已经是新的** → 停。有人先推过了,先查清是谁、推了什么,再决定。 ⛔ 不要跳过这步直接推。「线上是什么」不能靠记忆,只能靠取回来比对。 **输出**:一张三处的 md5 对照表,以及一句结论:线上当前是旧版 / 已是新版 / 文件缺失。 ### 第二步 · 建回退锚点(不能跳) 把**改动前**的线上文件复制到备份位,并当场核对 md5。 **唯一有效的判据是:备份件的 md5 等于改动前那一版的 md5。** 不是时间,不是文件名。 这一步最容易错,两种典型错法: - **按时间取最新备份** —— 如果那个位置被后来覆盖过,「最新的备份」可能正是新内容,等于把回退点删了、留下废物。 - **备份名不带唯一后缀** —— 重跑一次就互相覆盖,两次操作后只剩一份,还是错的那份。 如果备份丢了,还有一条路:**从版本库历史里取改动前的那一版**。这比任何备份都可靠,因为它不由操作者决定。 **输出**:备份路径 + 备份件 md5 + 「等于改动前」的核对结果。 ### 第三步 · 确认没人在同时改 自动化任务、定时脚本、另一个人的部署,都可能与这次撞车。 查三样:有没有相关进程在跑、有没有锁文件、目标目录最近有没有别的写入。 有任务在跑 → 等它结束。**并发写入同一个索引文件,最后谁赢取决于毫秒,输的一方内容静默消失。** **输出**:一句确认——「无并发」或「已等 X 结束」。 ### 第四步 · 推上去 两条规矩: 1. **先传到临时位,再落位。** 直接覆盖会让权限和属主跟着源文件走,可能变成不可读或不可执行。落位时显式设成目标该有的属主与权限。 2. **落位后立刻双侧比对 md5。** 不比就等于没验证——传输截断、编码变化、传错文件,都只有 md5 能发现。 **输出**:落位后的 md5 清单,与本地逐条相同。 ### 第五步 · 线上实测(不能跳) **必须从线上取回来验证,不能看本地文件、不能看编辑器预览。** 预览层会留旧件,它显示的「成功」不作数。 请求要带两样东西: - **完整的浏览器 User-Agent** —— 裸程序 UA 常被安全规则拦掉,返回的不是你要的内容。 - **`Cache-Control: no-cache`** —— 否则可能读到缓存,看到的是上一次的结果。 验四样,缺一不可: | 验什么 | 判据 | |---|---| | 状态码 | 200;403/404/无响应都要当失败处理 | | 字节数 | 与本地文件一致(差几个字节通常是换行被改) | | md5 | 与本地一致(最硬的一条) | | 关键文字 | 这次改的那句话,在取回的内容里能搜到 | **输出**:四样实测值,全部对上才算部署完成。 ### 第六步 · 收尾三件 1. **补镜像**:如果镜像不等于线上,补上。别信「同步脚本说一致」——很多脚本只比文件名不比内容。 2. **重建派生件**:索引、地图、清单这类从文件派生的东西,部署后必须重建。漏了就是「内容在,但找不到」。 3. **提交版本库**:把这次改动记进去,下次回退才有依据。 **输出**:三处一致(本地 / 镜像 / 线上)+ 一条版本记录。 --- ## 出问题怎么办 按现象查,不要从头重来。 | 现象 | 最可能的原因 | 处理 | |---|---|---| | 双侧 md5 不一致 | 传的是旧文件 / 落位失败 | 核对源文件 md5,重传 | | 200 但内容还是旧的 | 缓存 | 加 `no-cache` 重取;仍旧则查中间层缓存 | | 无响应 / 连接被断 | UA 被安全规则拦了 | 换完整浏览器 UA 再试 | | 403 / 404 | 路径写错,或落位后属主不对 | 查实际落位路径与权限 | | 内容对,但页面样式不对 | 组件样式没重新合并 | 重新合并样式表再推一次 | | 改了一个页面,别的页也变了 | 这页是从别处派生的 | 重跑派生,别手改结果 | | 回滚后还是不对 | **备份件本身是新的** | 用版本库历史重建,不要相信备份目录 | 最后一条是最贵的:**回滚失败通常不是回滚动作错了,是回退点本身就选错了。** --- ## 输出规范 ### 人读 ``` 发布:<改了哪些文件> 回退点:<备份路径> · md5 已核等于改动前 推前状态:线上旧版 / 已是新版 / 缺失 实测:状态码 200 · 字节数一致 · md5 一致 · 关键文字命中 收尾:镜像已补 / 派生已重建 / 已提交版本库 未做:<这次没覆盖什么> ``` ### 机器读 ``` files: [{path, md5_before, md5_after, bytes}] anchor: {path, md5, verified_equal_before: true|false} precheck: {online_state: old|new|missing, concurrent: false} verify: {status, bytes_match, md5_match, probe_text_found} derived: {sitemap: rebuilt|skipped, index: rebuilt|skipped} vcs: {committed: true|false, ref} ``` `anchor.verified_equal_before` 为 false 时,后面全部不算通过——**没有可靠的回退点,这次发布不成立**。 ### 一句话版 `已发布 N 个文件,回退点已核,线上四样全对。` 或 `发布未完成:卡在<哪一步>。` ⛔ 不要说「大概好了」。四样里缺一样就是未完成。 --- ## 常见错误 - **跳过探测直接推** —— 线上可能已经是新版,或者根本没这个文件,推完才发现方向错了。 - **按时间挑备份** —— 最常见的致命错误,会把新内容当成回退点。 - **落位后不比 md5** —— 传输问题、编码变化只有 md5 能发现,肉眼看不出来。 - **用编辑器预览确认** —— 预览层留旧件,显示的成功不作数。 - **请求不带 UA** —— 拿到的是拦截页而不是目标文件,还以为是内容坏了。 - **只推文件不重建派生** —— 页面在,索引里没有,等于没发布。 - **紧急时「直接改线上」** —— 改完没有本地记录,下次部署会覆盖掉,而且无从回退。 --- ## 最后核对 2026-11-06。命令与工具因环境而异,但六步的顺序和四样实测判据通用。与所执行系统的既定标准冲突时,以那个系统的标准为准。