# 让每次发布都能检查，也能恢复

原文: https://yishto.com/learn/delivery/cicd-monitoring-and-incidents/

作者: [Yishto](https://yishto.com/about/)

用搜索发布案例串起产物追踪、必要检查、用户健康、观测信号与事故处置，区分代码回滚和数据恢复。

首次发布: 2026-11-10
内容更新: 2026-11-10

资料服务发布了新的搜索功能，首页能打开，但查询包含空格时开始报错。构建完成、部署成功和用户能完成任务，是三个需要分别证明的事实。交付流程应把它们连接起来，并保留已经验证的版本，供故障时恢复。

案例里的 build-1 是最后稳定产物，build-2 是失败候选，build-3 通过教学检查。这些标识只用来说明状态，不对应真实流水线。目标是定义发布门槛、线上核验和故障记录，避免把一个绿色任务状态当成全部证据。

## 从变更到产物建立可追踪关系

CI 检查变更是否符合约束，通常包括安装依赖、编译和适合这次风险的测试。CD 把已验证产物交付到指定环境。持续交付可以保留人工批准步骤，持续部署通常强调自动进入环境，具体流程要根据组织规则写明。

GitHub Actions 工作流用 YAML 定义触发条件与 jobs，文件放在 `.github/workflows`。在设计中应区分普通分支验证与具有发布权限的任务。构建身份不应天然拥有生产密钥，来自不可信贡献的代码也不能随意接触受保护环境。

一次构建需要记录源代码提交、依赖锁、运行时、构建参数和产物标识。在条件允许时构建一次，再提升同一产物，减少环境间重新构建造成的差异。仍要单独管理环境配置，因为同一个产物配上不同配置会表现不同。

## 发布门槛要检查实际风险

新搜索功能涉及输入解析、数据库查询与空结果展示，所以需要正常查询、空白输入、无匹配结果和权限隔离检查。只检查代码覆盖率不能解释关键失败分支是否执行；只做首页 HTTP 200 检查也不能证明搜索成功。

资料站还可以检查内容链接、双语路由与页面元数据。把必要检查命名并固定下来，要求每项都给出结果，不能让漏掉的检查等同于通过。超时、未执行和失败应分别记录，方便区分程序缺陷与验证环境问题。

数据库迁移需要兼容计划。先增加兼容字段，再让新旧代码都能运行，之后才移除旧结构，是可评估的一种演进方式。是否适用取决于数据库与业务约束。撤回应用代码不能安全地撤回已写入的数据，也不能自动恢复删除的列；备份和恢复要另行验证。

## 把发布状态写成可检查规则

下面的离线模型只有两个必需检查和一个健康结果。缺少检查时保留旧版本；检查失败或健康核验失败时也保留旧版本。它没有向平台部署任何代码。

```python
stable = "build-1"
required = {"tests", "content"}
def release(candidate, checks, healthy):
    global stable
    if not required <= checks.keys():
        return stable
    if not all(checks[name] is True for name in required) or not healthy:
        return stable
    stable = candidate
    return stable
assert release("build-2", {}, True) == "build-1"
assert release("build-2", {"tests": True, "content": False}, True) == "build-1"
assert release("build-2", {"tests": True, "content": True}, False) == "build-1"
assert release("build-3", {"tests": True, "content": True}, True) == "build-3"
print(stable)
```

实际输出是 `build-3`。空字典没有通过门槛，build-2 的内容失败和健康失败也没有改变 stable；最后一次完整通过才更新稳定版本。这里特意检查必需字段，因为 Python 的 `all([])` 会返回 True，空集合不能被误当成所有检查完成。

真实系统的健康核验需要时间窗口和请求样本，不能只靠瞬时布尔值。还要处理多个发布并发、发布锁、版本推进权限和失败后的回滚动作。内存变量不会跨重启保存；稳定产物、执行记录与恢复点应存到可信持久系统。

## 用不同信号定位用户故障

指标描述聚合行为，例如请求数、失败比例和延迟分布。日志记录某个事件与错误原因。trace 用相关 span 表达一次请求跨组件的过程。它们可以通过请求 ID 和部署版本关联，但不应把用户正文、身份令牌或密钥直接写进观测数据。

搜索故障发生时，先看受影响比例与开始时间，再查看同一版本的错误分类，最后沿 trace 定位输入解析、数据访问或返回阶段。单个慢请求不能代表整个服务慢；平均延迟也可能掩盖少量严重超时。应选择与用户任务相符的分布指标和采样方式。

OpenTelemetry 提供采集、处理和导出信号的组件与约定，不自动成为完整监控后台。数据最终存到哪里、保留多久、哪些字段脱敏、采样会漏掉什么，都由应用和观测系统共同决定。trace 关联有帮助，也不意味着记录了所有因果关系。

## 告警需要指向可执行处置

SLO 将用户可观察的目标量化，例如一个窗口内成功完成搜索的比例。告警应关注持续的用户失败或目标消耗，并给出服务、环境、影响范围和调查入口。CPU 短暂升高可以用于分析，是否唤醒人要取决于它与用户影响的关系。

为不同故障写清处置：新版本错误率升高时停止扩大发布；依赖不可用时检查其状态与重试压力；请求积压时限制入站工作并优先恢复处理能力。没有负责人和动作的告警容易变成重复噪声。

故障期间先缓解影响，再寻找完整原因。指定协调者，避免多人同时修改同一配置。时间线只写已观察事实，明确记录假设、动作、执行者与结果。回滚后继续核验搜索任务和版本，并观察错误率，不能仅凭发布工具显示撤回成功宣布恢复。

发布与观测还应使用同一种版本标识。若页面写的是 build-3，日志只记录分支名，缓存又返回 build-2，就无法准确判断哪份代码产生了错误。将产物版本加入健康响应与日志上下文，避免记录敏感配置值。验证期间保留前后窗口，确认故障变化与发布时间的关系；时间重合只能提供调查线索，仍需用具体输入与错误路径验证原因。

## 练习：给搜索发布补齐恢复证据

设计一份发布记录，包含候选提交、产物、必要检查、环境配置摘要、放量方式、健康窗口、稳定版本与恢复条件。让测试数据覆盖带空格查询、空结果与访问拒绝，并模拟检查缺失、线上失败和迁移已经执行三种情况。

验收时，漏掉必需检查不能推进版本；验证记录能追溯到同一产物；故障后能够说明恢复的是代码、配置还是数据。再写一份简短事故记录：影响、时间线、缓解动作和可验证的后续改动。后续行动应有明确完成证据，例如新增一条能重现空格错误的测试，而不是只写“加强测试”。

## 资料来源与延伸阅读

- [Workflow syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax)
- [OpenTelemetry concepts](https://opentelemetry.io/docs/concepts/)
- [Site Reliability Engineering](https://sre.google/sre-book/table-of-contents/)

资料核验：2026-11-10。教学示例不代表线上实测。

## 相关文章

- [排错时先找证据，再改代码](https://yishto.com/learn/developer-workflow/debugging-with-evidence/)
- [AI 应用上线检查：超时、重试、权限与回滚](https://yishto.com/learn/production/shipping-checklist/)
