系统实践

一次生产部署为什么不能只看 HTTP 200

Git提交、CI、Cloudflare Pages、域名、HTTPS、页面响应和人工验收是不同的证据层。结合高源外一次真实发布,讲清“上线成功”到底需要证明什么。

· 约 12 分钟阅读 · 近 30 天 少于 10 次浏览

一条生产发布路径经过版本、构建、部署、连接、页面检查和人工验收等多个关卡

前段时间,我在“高源外”发布了一篇文章:《当科学课不再只是“知道答案”》。

第一次发布以后,GitHub Actions 运行成功,Cloudflare Pages 完成部署,正式网址可以打开,服务器返回 HTTP 200。从通常的技术指标看,这次上线已经成功了。

但事情没有结束。

我重新读了一遍文章,又对正文做了调整,第二次发布。后来我看到文章末尾的“后记”,觉得它和正文的收束重复,于是又让 Codex 删除这一段,重新发布一次。

第三次发布后,我关心的不只是页面还能不能打开。我还要确认:后记真的消失了吗?那句不再需要的文字还有没有留在生成页面里?前面已经确认保留的 AI 段落、新结尾和三条图片说明是否仍然存在?手机和电脑上还能不能正常阅读?

这次很普通的内容修改,让我重新意识到:

HTTP 200 只能证明一个请求得到了成功响应,不能证明一次生产交付已经完整。

“部署成功”其实包含很多句话

我们常把“代码已经提交”“CI绿了”“平台显示部署成功”“网站能打开”合并成一句话:上线了。

但这些状态回答的是不同问题。

  • Git 提交回答:这次到底保存了哪一版改动?
  • 推送回答:远程仓库是否已经收到这次提交?
  • CI 回答:这版代码完成了哪些自动检查?
  • Cloudflare Pages 回答:哪个构建产物被部署到了哪个项目?
  • 域名回答:用户访问的地址是否指向目标服务?
  • HTTPS 回答:浏览器是否通过安全连接访问了目标站点?
  • HTTP 200 回答:这个请求是否收到了成功状态码?
  • 内容检查回答:返回的是不是我们想发布的页面?
  • 人工验收回答:页面是否符合这次修改的真实意图?

任何一层失败,都可能让最终结果与预期不同。反过来,某一层成功,也不能替其他层作证。

从Git提交、CI、Pages部署、域名和HTTPS、HTTP 200、内容核对到人工验收的七层证据链
HTTP 200只是链路中的一站,后面还有内容核对和人工验收。

第一层:Git 提交证明“是哪一版”

那篇科学教育文章在仓库里留下了三次清楚的提交:第一次发布、第二次修改、第三次删除后记。

高源外本地仓库中科学教育文章的三次真实Git提交记录
本地仓库真实提交记录:首次发布、文章优化和删除后记是三个可复核版本。

提交的价值不只是“代码保存了”。它把一次改动固定成了可以识别的版本:哪些文件改变了,改变发生在什么时候,部署的提交是否就是刚刚验收的那一版。

如果本地改完了文件,却没有进入提交,那么 CI 根本看不到这些变化。如果提交存在,但推送的是另一条分支,生产环境也不会自动得到它。

因此,“我已经改了”与“生产流水线得到这次修改”是两件事。

一个提交能够证明仓库里有一组确定的变化,但它不能证明:

  • 测试已经通过;
  • 构建能够完成;
  • 远程仓库已经收到;
  • 生产平台已经使用;
  • 页面内容符合人的最终意图。

第二层:CI 证明“完成了哪些检查”

高源外的生产工作流在 main 分支收到推送后,会安装锁定版本的依赖,运行单元测试,执行 Astro 检查和正式构建,然后再把生成的静态产物交给 Cloudflare Pages。

CI 成功很重要。它至少说明,同一份提交在一套明确的运行环境里走完了工作流中列出的步骤。

但“CI通过”的边界也必须说清楚。

如果工作流只运行单元测试,它就不能证明浏览器里的页面布局正常;如果测试没有检查某段文章内容,它也不会知道一段不该出现的文字仍然存在;如果构建时使用了错误的环境配置,构建成功仍可能得到错误目标的产物。

CI 不是一个抽象的质量印章。它只能证明工作流实际执行并通过的那些检查。

所以看见绿色勾号以后,我还需要知道:

  • 它对应哪个提交?
  • 实际运行了哪些命令?
  • 哪些检查根本没有包含在工作流里?
  • 生成的产物是否就是后面部署的那一份?

第三层:Cloudflare Pages 证明“产物被部署了”

在高源外的工作流里,正式构建完成后,Cloudflare 的部署步骤会把 dist 目录发布到指定 Pages 项目,并携带当前 Git 提交的标识。

当这个步骤成功时,可以证明平台接受了这次构建产物,并完成了相应部署。

但平台日志里的“Success”仍然不是最终用户页面的全部事实。

它不能单独证明自定义域名已经正确指向这个项目,不能证明用户所在地的 DNS 和 CDN 已经得到最新状态,也不能证明访问某个具体路径时返回的是正确内容。

如果一个账号里有多个项目或环境,甚至还需要确认:部署成功的是不是我们真正想发布的那个项目和分支。

第四层:域名和 HTTPS 是两道不同的门

高源外使用 free-e.net 和 www.free-e.net。生产工作流里有单独的步骤检查并配置这两个自定义域名。

域名配置成功,主要说明平台侧已经接受了这个域名与项目的关系。真正访问时,还要看 DNS 是否把请求带到了正确位置。

HTTPS 又是另一层。浏览器出现安全连接,说明证书、域名和连接过程在这次访问中能够正常工作。它不证明后面的文章内容正确,也不证明应用的业务功能可用。

有时候我们看到浏览器地址栏的小锁,就下意识觉得“网站没问题”。实际上,小锁只回答了连接安全问题,并没有检查页面是不是旧版本、是不是错误页,或者文章是否缺了一段。

第五层:HTTP 200 到底证明了什么

HTTP 200 的含义很具体:服务器成功处理了这次 HTTP 请求,并用 200 OK 状态返回了响应。

这当然是重要证据,但它的范围没有我们想象得那么大。

一个页面返回 200,仍然可能是:

  • 自定义的错误页面也错误地返回了 200;
  • 首页正常,目标文章路径却不是预期内容;
  • 页面骨架存在,但图片或脚本加载失败;
  • CDN 仍然返回旧版本;
  • 标题正确,正文却漏了关键段落;
  • 内容都存在,但手机端出现横向溢出或遮挡。

因此,HTTP 200 只能进入证据链,不能成为证据链的终点。

第六层:内容检查证明“返回了什么”

删除后记后的那次发布,我在正式页面上检查了几个明确条件:

  • 页面响应为 HTTP 200;
  • “后记”模块已经不存在;
  • 被要求删除的那句话已经不存在;
  • 原来保留的 AI 段落仍然存在;
  • 新结尾仍然存在;
  • 三条图片说明仍然存在。

这比只看状态码多走了一步。它开始验证“这次修改想改变什么”和“生产页面实际上返回了什么”之间是否一致。

但文本存在也不是最后一层。某句话在 HTML 中存在,不代表它在页面上没有被样式隐藏;图片标签存在,不代表图片比例合适;目录链接存在,不代表点击和滚动体验正常。

内容断言适合确认可机器判断的要求,视觉和阅读体验仍需要浏览器检查。

最后一层:人工验收确认“这是不是我真正想要的”

第一次发布后,所有自动流程都可以成功,但我重新阅读文章后仍然可能改变判断。

这并不意味着部署系统出了故障。恰恰相反,技术流水线完成了它的职责:准确地把当时的版本交付到了线上。后来删除后记,是一次内容判断的变化,只有真正阅读文章的人才能作出。

自动测试可以检查标题有没有、图片数量对不对、页面是否溢出,却很难替我判断一段后记是不是重复,一次收束是不是太用力,或者一篇文章读起来是否仍然像我自己。

因此,人工验收不是自动化不够先进时的临时补丁。只要交付结果最终服务于人,它就是发布链路中独立的一层证据。

每一层证据,都有自己的边界

证据 能证明什么 不能单独证明什么
Git 提交 改动形成了可识别版本 测试、部署和页面正确
远程推送 远程仓库收到目标提交 CI已经完成
CI 通过 配置中的自动检查通过 未被测试覆盖的质量
构建成功 生成了符合构建规则的产物 产物已经上线
Pages 部署成功 平台接受并部署了产物 自定义域名和目标页面正确
域名可访问 请求能经域名到达服务 HTTPS、内容和功能正确
HTTPS 正常 本次安全连接成立 页面内容正确
HTTP 200 本次请求得到成功响应 返回的是目标版本和目标内容
内容断言通过 指定内容条件成立 整体视觉和阅读体验合格
人工验收 结果符合本次真实意图 后续运行永远不会出问题

这张表真正想表达的,不是发布一定要变得复杂,而是结论要和证据相匹配。

我现在使用的最小发布闭环

对高源外这样的静态内容站,一次普通文章发布并不需要庞大的运维体系,但至少需要下面这条链路:

  1. 检查本次改动范围,确认没有混入无关文件。
  2. 运行测试、类型检查和正式构建。
  3. 用浏览器检查桌面端和手机端。
  4. 提交并记录明确的版本。
  5. 推送后确认 CI 对应正确提交。
  6. 确认 Cloudflare Pages 部署成功。
  7. 通过正式域名访问目标路径。
  8. 检查 HTTP 状态、标题和关键内容。
  9. 由人完成最后一次阅读和确认。

如果中间任何一步失败,就把结论停在那一步,不用后面的成功替它补证据。

这套方法与我在《Node.js 生产部署:从手工 git pull 到可回滚发布》里强调的边界是一致的,也和数据库备份必须经过恢复验证是同一个道理:执行完成,只能证明执行完成;真正要交付的结果,需要针对结果本身验证。

写在最后

做生产系统以后,我越来越少说一句笼统的“已经好了”。

我更愿意把它拆开:代码已经提交,测试已经通过,构建已经完成,产物已经部署,正式域名可以访问,目标内容已经核对,最后由人验收。

这样说看起来慢一点,却能让每一个结论找到自己的依据。

HTTP 200 很重要。

但它只是告诉我们,门打开了。

至于门后是不是我们要交付的东西,还要继续往里看。