24.3 SOP、Runbook 与变更治理
一份只有命令的文档,会让操作更快,却不一定更安全:
它没有回答:
- 这是哪种故障或变更?
- 当前事实符合文档前提吗?
- 操作者有权改变哪个目标?
- 影响哪些用户和数据?
- 哪一步必须停?
- 命令超时后是失败、成功还是未知?
- 如何证明结果,而不是只看 exit code?
- 应当 rollback 还是 roll forward?
- 哪些证据可以保留,哪些 secret 不能导出?
可执行文档必须同时是动作合同、停止合同和证据合同。
24.3.1 日常操作、故障处置与恢复演练
四类文档解决不同不确定性
| 文档 | 环境 | 不确定性 | 主要形态 |
|---|---|---|---|
| SOP | 日常、重复 | 输入和结果基本已知 | 线性步骤 |
| Incident Runbook | 已有症状 | 原因和状态未知 | 分支与决策点 |
| Recovery Drill | 受控演练 | 恢复路径能否工作未知 | 假设、计时、验收 |
| Change Plan | 一次变更 | 版本、目标、窗口特定 | 精确 artifact 与批准 |
同一个动作可以出现在不同文档中,但语境不同。
例如 credential rotation:
不能把 routine SOP 原样用于凭据泄露事故,因为 rollback 到旧 secret 在事故中 不是安全选择。
SOP:稳定输入下的可重复流程
一份合格 SOP 至少包含:
SOP 中最重要的句子往往是:
而不是最长的命令。
Runbook:从症状出发,而不是假设根因
告警是:
Runbook 不应开头就执行 failover。合理顺序:
若先看到 replica lag 就 promote,可能:
- 切错根因;
- 扩大 write gap;
- 引入 split-brain 风险;
- 让原本健康的 write path 中断;
- 丢失调查证据。
Runbook 应提供 decision tree:
它不能穷举所有根因,但必须约束高风险捷径。
Recovery Drill:证明“能恢复”
backup SOP 和 restore drill 不同:
演练要在开始前固定 success criteria。若恢复后才决定“只要能连上就算成功”, 结果会向已有事实偏移。
本章的 SOP-BACKUP-RESTORE 要求:
- source cluster 和 repository 明确;
- recovery target 固定;
- destination 无 production listener;
- 验证 query 与预期先冻结;
- WAL 缺失、identity 模糊或 isolation 失败立即停止;
- 时间只记为沙箱 observation,不称 production RTO。
Drill 不应依赖临场英雄主义
有效演练测试:
- 文档是否完整;
- 权限是否可用;
- secret 是否能安全获得;
- 工具版本是否兼容;
- 依赖是否存在;
- 验收是否机器可判;
- 证据是否完整;
- 交接是否能继续。
只让原作者凭记忆完成,证明的是个人能力,不是组织恢复能力。可在后续轮次让 另一位合格 operator 按文档执行,原作者只观察和记录歧义。
Change Plan:把抽象 SOP 绑定到一次动作
SOP 可以写:
Change Plan 必须写:
“按切换 SOP 执行”不能替代这些单次事实。
版本和环境必须受支持
文档应写:
命令、输出字段和行为会随版本改变。升级计划必须同时检查 SOP 与 validator。
日常、故障和演练的证据粒度不同
| 类型 | 关键证据 |
|---|---|
| routine | target、输入版本、执行结果、postcondition |
| incident | timeline、user impact、decision、unknown outcome |
| drill | hypothesis、predefined success、timing、exception |
| change | request/approve/execute identity、artifact hash、closeout |
不能因为 incident 需要更多证据,就把所有 routine 日志永久保存;也不能因 routine 简洁,在安全事故中只留一个 shell exit code。
24.3.2 申请、评审、执行、验证与回退
完整生命周期
本章使用十一阶段:
“批准”不是生命周期的起点,也不是结束。
1. Request:先说明目的,不要从命令开始
请求至少回答:
反例:
合格:
2. Classify:按实际影响,不按团队习惯
本章风险级别:
L0:只读
- 查询统计视图;
- 渲染配置 diff;
- 验证已有 evidence;
- 不改变数据库、pool、topology、credential 或 route。
L0 仍要绑定目标,避免拿错误环境的证据做结论。
L1:有界、可逆、低影响
- 增加 dashboard;
- 发布 disabled alert;
- sandbox 中增加窄权限 NOLOGIN role。
需要 preview、verification 和 rollback。
L2:material state
- credential rotation;
- online schema expansion;
- pool limit;
- role/permission;
- 生产参数 reload;
- 业务路由变化。
需要独立批准、停止线、完整证据。
L3:destructive/topology/recovery
- failover;
- restore/promotion;
- data deletion;
- cluster reset;
- 大范围不可逆迁移。
需要最强 target binding、双人控制、延迟确认或等价门禁。
风险由 blast radius、可逆性、未知结果与数据后果共同决定,不是“SQL 只有 一行所以低风险”。
3. Freeze preconditions:审批的是事实快照
preflight 可以包括:
每个事实要有:
- source;
- captured_at;
- freshness limit;
- expected value;
- stop behavior。
批准后到执行前可能发生变化。执行器必须重新采集关键事实:
4. Preview and test:使用相同 artifact
好的 pipeline:
不要测试一套手写 SQL、执行另一套临时编辑内容。
preview 可能包括:
- Pigsty inventory projection 和 diff;
- SQL parse/plan;
- lock/rewrite analysis;
- restore dry-run facts;
- target list;
- expected state transition;
- synthetic data replay;
- rollback simulation。
preview 不能证明生产一定成功,但能拒绝明显不符合合同的输入。
5. Approve:批准目标、动作、窗口,不是空白授权
批准记录要绑定:
“同意处理”或聊天表情不能作为 L3 授权。
6. Execute:每一步都应可观察
执行器:
- 使用个人 durable identity;
- 不共享 root/DBA 密码;
- 不把 secret 放在 shell 参数;
- 记录 phase start/end;
- 每一阶段检查 stop condition;
- 不并行执行计划外动作;
- 超时后先分类结果。
脚本的 set -e 只能在命令非零时停止。它不能判断:
- command 返回 0 但目标错误;
- client timeout 后 server 已完成;
- failover command 成功但路由未刷新;
ALTER TABLE成功但 application 不兼容;- backup 成功但不可恢复。
7. Observe:动作期间看用户与组件
观察至少两条线:
只看执行命令输出,会错过用户影响;只看 SLI,又无法及时识别安全停止线。
8. Verify:状态正确,而不是命令完成
postcondition 应与 request success 一一对应。
planned switchover:
schema release:
credential rotation:
9. Rollback or roll forward:不是所有状态都能倒回
rollback 适合:
- application feature flag;
- additive configuration;
- reversible grant;
- 在 semantic cutover 前保留旧结构。
roll forward 更适合:
- timeline 已推进的 failover;
- 已泄露 credential;
- 已有新版本写入的数据格式;
- 外部系统已消费的事件;
- commit outcome unknown。
PostgreSQL DDL 的边界
许多 PostgreSQL DDL 可以在 transaction 中回滚,但不能据此声称发布总可逆:
CREATE DATABASE等命令不能在 transaction block 内运行;CREATE INDEX CONCURRENTLY不能在 transaction block 内运行,失败还可能 留下INVALIDindex;- data backfill 已被新应用读取后,SQL rollback 不能撤回外部影响;
- table rewrite/WAL/replica lag 已经发生;
- application 与 schema 的兼容窗口可能关闭。
具体行为见 PostgreSQL CREATE INDEX 和第 11 章的安全模式变更。
回退计划要说明“在哪个决策点之前可回退”,而不是只写 ROLLBACK。
10. Retain evidence:保存证明,不保存秘密
每次变化至少形成 manifest:
不要保存:
- cleartext password;
- SCRAM verifier;
- private key;
- 完整 credential URI;
- 无边界 bind values;
- 不必要的客户行。
11. Close and review:关闭是一个结论
关单条件:
- service postcondition 通过;
- component postcondition 通过;
- unknown outcomes 为零或有明确 owner/deadline;
- 临时权限、route、pool/config 已恢复或正式纳管;
- evidence 完整;
- follow-up 有 owner 与 due date;
- budget/incident 状态更新。
“维护窗口结束”不能自动关闭失败的变更。
一个 change record
本章要求字段:
真实系统还要验证三个 identity 不同且当时有效。
24.3.3 高风险动作的双人或延迟确认
双人控制解决什么
高风险动作容易同时发生三类错误:
独立批准人应使用独立证据检查意图与目标;执行器负责按批准 artifact 操作。 两人同时复制同一个错误命令,不构成有效独立控制。
NIST SP 800-53 Rev. 5.1 在 access restrictions for change 中描述 dual authorization:两名合格个人批准 和实施选定高风险变化,并对变化负责。组织还可轮换职责,降低串通风险。
本章将它转化为:
独立不只意味着用户名不同
无效:
- 同一人使用两个账号;
- 两人共享一个 password;
- approver 没有理解 PostgreSQL/Pigsty 风险;
- approver 只看 requester 的截图;
- 执行 artifact 在批准后被修改;
- 两人都受同一个未验证假设影响。
有效独立性包括:
- durable workforce identity;
- 不同职责;
- 足够专业能力;
- 能访问 source-of-truth;
- artifact hash;
- 能拒绝;
- 拒绝不会被绕过;
- 事后记录。
延迟确认解决冲动和误目标
destructive action 可要求 cooldown:
延迟让:
- 执行者有时间发现环境错误;
- 依赖 team 有时间反馈;
- 自动备份/导出有时间完成;
- 用户影响窗口再次确认。
它不适用于所有 incident。真正紧急时可以缩短 normal wait,但不能跳过 target、 authority、stop line 和 evidence。
confirmation token 必须绑定语义
差:
较好:
脚本还应从运行时重新解析 target identity,而不是完全信任环境变量字符串。
break-glass 不是无规则
break-glass 允许:
- 跳过正常排期;
- 获取时限高权身份;
- 执行恢复服务所需的最小动作。
它不允许:
- 不记录目标;
- 无止境保留高权;
- 关闭审计;
- 将 credential 发进聊天;
- 跳过事后 review;
- 使用后不轮换;
- 将未知结果当成功。
本章合同:
emergency change 仍要选择最小动作
事故中的压力会放大 scope:
Runbook 应给出干预阶梯:
每一级需要新的证据和 authority。不能因为已经进入 SEV-1,就自动获得所有 destructive action 的授权。
自动化能做什么
适合机器执行:
- 解析 inventory;
- 验证 mode/owner;
- 比较 target identity;
- 采集 leader/timeline/LSN;
- 计算 lock、WAL、budget;
- 检查 artifact hash;
- 拒绝非空 evidence directory;
- 执行 bounded probe;
- 核对 postcondition;
- 生成 manifest;
- 扫描 secret material。
仍需组织决定:
- 用户损失是否可接受;
- recovery point 是否符合业务;
- 是否暂停业务;
- 谁拥有 change authority;
- 合规/隐私边界;
- 何时允许例外。
自动化可以证明 guard 成立,不能发明授权。
guard 不是一串容易伪造的环境变量
高风险 runner 应结合:
只检查 NONPRODUCTION=true 不够;操作者可以错误设置。第 19–23 章的实验
runner 还会核对 host、cluster、topology 和 fixture identity。
超时与 unknown outcome
高风险 API/SQL 超时后:
例子:
COMMITtimeout:查 idempotency token;- switchover timeout:查 Patroni leader/timeline;
- revoke timeout:从新 session 测认证与 role attributes;
- DDL timeout:查 catalog/lock/index validity;
- backup timeout:查 repository manifest,不凭 client exit 猜。
重试本身是一项 change,需要可幂等证明。
高风险 review 的三次停止机会
批准人不能提前放弃后两次停止。机器 guard 也不能因为 approval 存在而忽略 live drift。
本章的 SOP 与变更政策
四个 SOP 覆盖:
每份都有 precondition、stop、verification、rollback/roll-forward 和 evidence。 validator 会拒绝:
- backup exit 0 作为全部恢复验证;
- L2/L3 同人自批自执行;
- break-glass 跳过 target/evidence;
- 沙箱运行被标为 production proof。
文档演练清单
- 文档类型是否正确,还是把 incident 写成线性 SOP?
- scope 和版本是否明确?
- trigger 与 authority 是否明确?
- exact target 能否由 live evidence 验证?
- precondition 是否带 freshness?
- 每个高风险 phase 前是否有 stop condition?
- 命令 timeout 后是否先核对 durable state?
- success 是否由 postcondition 定义?
- rollback 与 roll-forward 的决策点是否明确?
- external side effect 是否考虑?
- requester/approver/executor 是否真正独立?
- break-glass 是否有时限、审查和轮换?
- evidence 是否 secret-free?
- 另一个合格 operator 能否只凭文档完成?
- 文档、脚本、目标版本变化后是否重新演练?
下一节会把 SLO 与这些 Runbook 连接起来:一个告警若不能把值班人带到安全的 第一动作,就不应成为 page。
上一节:SLI、SLO 与错误预算 · 返回本章目录 · 下一节:观察与告警契约 · 查看全书目录 · 查看索引中心