Files
pikasTech-unidesk/.agents/skills/unidesk-cicd/references/incident-recovery.md
T
2026-07-18 17:52:32 +02:00

15 KiB
Raw Blame History

CI/CD 生产事故恢复

适用边界

  • 本流程适用于 PaC、Tekton、GitOps、Argo、git-mirror 或 runtime rollout 连续失败,以及多个 consumer 出现共同失败指纹的生产事故。
  • 本流程不扩大写入授权:
    • consumer 私有配置可以在原任务范围内修复;
    • CI/CD 公共服务面默认只读;
    • 修改公共服务面必须取得用户明确授权。
  • 节点、consumer、PipelineRun、仓库、分支和提交均为变化项,必须从 owning YAML 与受控 CLI 解析,禁止复制本案例取值。

公共服务面与 consumer 私有面

  • 公共服务面包括:
    • 共享 PaC controller/release 与 admission
    • Gitea webhook bridge、durable inbox 和共享 source authority
    • 共享 git-mirror read/write Deployment、Service、PVC 和仓库 ref
    • 共享 Tekton/Argo bootstrap、renderer、模板和 native helper
    • 公共 edge、FRPC、Caddy、Secret 和 RBAC。
  • consumer 私有面只包括:
    • owning YAML 中由 consumer id 唯一定位的独立配置片段;
    • 独立 lane、namespace、Pipeline、source artifact 和 runtime 参数;
    • consumer 自己仓库内由 owning YAML 声明的 .tekton 制品。
  • 任一对象存在多个 consumer owner、共享 repositoryRef、共享 PVC、共享 Deployment 或共享网络入口时,按公共服务面处理。

最短定位路径

按以下顺序执行,前三步足以完成运行面分层,不再从裸 Kubernetes 大对象开始试探:

  1. 读取节点摘要:

    bun scripts/cli.ts cicd status --node <NODE>
    
  2. 一次计算选中 consumer 的回退窗口与同 source authority 共同指纹:

    bun scripts/cli.ts platform-infra pipelines-as-code diagnose-regression \
      --target <NODE> \
      --consumer <CONSUMER>
    
    • ok 表示只读采集是否成功;
    • incident 表示最新终态是否处于失败连续段;
    • window.lastKnownGoodwindow.firstFailed 给出 source commit 边界;
    • commonFailure 只在共享 repositoryRef 的 lanes 内聚类;
    • state=read-failed 时先处理读取可见性,不得按健康解释;
    • 窗口不足时只执行输出中的有界 expand-window
  3. 仅在需要精确 TaskRun 或日志证据时,对首次失败执行输出中的 debug-first-failure

    bun scripts/cli.ts platform-infra pipelines-as-code debug-step \
      --target <NODE> \
      --consumer <CONSUMER> \
      --id <PIPELINERUN>
    
  4. 使用命令输出的 sourceRange.compareUrl 比较最后成功 source commit 与首次失败 source commit

    • 只检查两者之间的提交;
    • 优先寻找与共同失败指纹直接相关的最近变更;
    • 记录致因文件、提交、影响 consumer 和可回退性;
    • 不在此阶段设计前向重构。

回退决策

  • 满足以下条件时直接选择回退:
    • 失败从某个提交后稳定出现;
    • 多条 PipelineRun 具有相同首个断点;
    • 变更与断点存在直接因果关系;
    • 回退不造成不可逆数据或外部状态损坏。
  • 回退 PR 只包含致因变更的精确反向语义:
    • 不夹带前向修复;
    • 不夹带重构;
    • 不夹带其他 consumer 或公共服务清理;
    • 不夹带 unrelated PR。
  • 致因位于公共服务面但用户没有明确授权时:
    • 停止写入;
    • 提供致因提交和精确回退候选;
    • 说明受影响 consumer 与预计恢复证据;
    • 等待用户授权公共服务面回退。

恢复验收

  • 回退 PR 合并后,只观察该合并产生的新正常自动事件:
    • webhook/Gitea delivery 已提交;
    • PaC 外层 PipelineRun 成功;
    • Tekton terminal roles 成功;
    • GitOps revision 与 source commit 对齐;
    • Argo Synced/Healthy
    • runtime ready 且 /health 成功。
  • 不得通过人工 mirror sync、直接 Gitea push、人工 PipelineRun、Argo sync、bootstrap、apply 或运行面热补补齐验收。
  • 回退事件恢复健康后,才允许从最新健康基线创建独立前向修复 PR。

Tekton 大对象与 Kine/SQLite 控制面退化

现象分层

  • 先区分自动链传播延迟与节点控制面事故:
    • PipelineRun 已成功并生成 GitOps commit、Argo 仍停在旧 revision 但 node、API、lease 和 runtime workload 健康时,按传播延迟处理;
    • 只读记录 PipelineRun completion、GitOps commit、Argo reconcile/operation、 Deployment rollout 和 runtime ready 时间,等待自动链自行收敛;
    • host 无法解析 *.svc.cluster.local 只说明查询平面错误, 不能证明集群内 git mirror 或 Argo repo-server 不可用;
    • node 进入 NotReady、Service endpoint 消失,并同时出现 API handler timeout、 node lease 延迟、kubelet housekeeping timeout 或持续 Kine Slow SQL 时, 才按节点控制面事故处理。
  • 不得只凭 Pod Pending、Argo Synced/Healthy 或单条 Slow SQL 下结论:
    • Synced/Healthy 必须同时核对实际 revision,避免把旧 revision 健康误判为新交付完成;
    • 周期性 compaction 或孤立 Slow SQL 只记录为余压;
    • 同窗口出现 lease、API、kubelet 和业务 endpoint 退化,才构成同类故障段。

根因判定

  • 对疑似大型 PipelineRun,在目标 k8s 内计算短摘要:
    • 对象总字节数;
    • spec 字节数;
    • status.pipelineSpec 字节数;
    • Pipeline task、matrix TaskRun 和普通 TaskRun 数量;
    • 最大字段及其 renderer/source 路径。
  • Tekton 会把解析后的 Pipeline spec 持久化到 status.pipelineSpec
    • renderer 为每个服务重复展开相同大型 inline taskSpec.steps[].script 时, 对象会在 specstatus 双份放大;
    • 运行期间的状态更新会重复写入大型对象,进一步放大 Kine/SQLite 单写者压力;
    • TaskRun results 只有在字节证据成立时才可判为主因,禁止按字段名称猜测。
  • 将对象证据与同窗口控制面证据关联:
    • Kine compaction 耗时和 Slow SQL
    • API handler timeout 与 stale resource-version
    • node/peer lease 写入延迟;
    • kubelet housekeeping 和 pod lifecycle 延迟;
    • k3s cgroup task、内存、swap 与数据库/WAL 压力。
  • 对象大小、耗时和留存阈值必须由 owning YAML 控制:
    • 状态与 OTel 只输出 blocking=false warning
    • 禁止把可见性阈值升级成阻断 PipelineRun、GitOps、Argo 或业务 /health 的门禁。

快速恢复

按以下顺序恢复用户入口,禁止跳过取证后直接重启:

  1. 保存最小证据:
    • 执行 cicd status --node <NODE>
    • 对选中 consumer 执行 pipelines-as-code status 和 id-specific history
    • 记录 node condition、受影响 Service endpoint、异常 Pod owner、 Kine/API/lease/kubelet 的有界日志计数;
    • 所有 Kubernetes 大对象在目标侧计算大小和摘要,不把完整 JSON 拉回本机。
  2. 判断是否仍可由 Kubernetes 自行调和:
    • API 与 kubelet 可用时,先等待 Deployment/StatefulSet controller 创建替代 Pod
    • 只处理已被驱逐、终态、卡在删除态且已不属于 Service endpoint 的旧 Pod
    • 删除前确认 owner、替代副本和 endpoint,禁止 broad selector、全 namespace 强删或触碰有状态单例。
  3. 只在节点控制面已退化且 Pod 级调和无效时重启 k3s:
    • 该动作属于 $unidesk-daddev P2 紧急恢复,必须有用户授权;

    • 单节点目标通过 host 受控 route 执行:

      trans <NODE>:/root/unidesk systemctl restart k3s
      trans <NODE>:k3s kubectl get nodes
      
    • 禁止使用 Compose 替代 YAML 选中的 k8s 运行面;

    • API 恢复后立即复查 node Ready、controller 调和和业务 endpoint healthy 后不再扰动 backend-core 或其他已恢复 workload。

  4. 控制面稳定后释放终态 Tekton 压力:
    • 先对 owning cleanup 入口执行 dry-run
    • 再以 --confirm --wait 有界删除符合 YAML retention 的终态 PipelineRun
    • 必须保留 active run、最新成功证据、业务 PVC、Secret、runtime workload 和 GitOps desired state。
  5. 恢复只以用户原入口结束:
    • node Ready
    • workload ready 且无持续重启;
    • Service endpoint 存在;
    • backend/provider/public /health 返回成功;
    • 恢复动作、根因假设和待持久化修复进入 issue 与 TaskTree;遗留 MDTODO 按 $unidesk-tasktree 迁移。

长期防复发

  • 从 renderer/source 消除重复内联:
    • 同构多服务构建优先使用 Tekton matrix,只在 PipelineRun 中保留一份 task 实现;
    • 跨 Pipeline 共享的稳定职责使用 reusable Task,并确保 Task reconciler 是正式 owner
    • 没有 Task reconciler 时不得以手工 apply 维持 TaskRef,改用 matrix 或 source 中的原生脚本文件;
    • 禁止用压缩字符串、编码载荷、第二 Pipeline 或 fallback 隐藏对象膨胀。
  • 为 source artifact 和 renderer 增加结构验证:
    • 统计对象总大小、specstatus.pipelineSpec
    • 检查重复 inline script、task 数量和 matrix 展开语义;
    • 保证每个服务仍有独立 TaskRun、结果收集和失败归属;
    • 结构检查只形成有界 warning,真正缺少渲染必需输入时才 fail-closed。
  • 保持 YAML-owned retention
    • 每个 consumer 声明终态 PipelineRun/TaskRun 留存策略;
    • cleanup 必须保护 active、latest success 和审计证据;
    • 定期清理降低对象 churn,但不能替代 renderer 减载。
  • 补齐非阻塞可观测性:
    • status/history 输出对象字节、Kine/WAL、compaction、API、lease、kubelet 和 node pressure 摘要;
    • CI/CD OTel span/event 记录 source commit、PipelineRun、GitOps commit、Argo revision、 runtime digest、阶段时间和对象压力 warning;
    • exporter 或 provenance 漂移只标记 evidence gap,禁止改变业务成功终态。
  • 长效修复必须由正常 PR 自动事件验收:
    • 新 PipelineRun 成功且对象显著减小;
    • GitOps commit、Argo revision、runtime digest 与 source identity 对齐;
    • PipelineRun 执行和后续 compaction 窗口内 node、API、lease、kubelet 与业务 endpoint 保持健康;
    • 不得用 k3s restart、Pod 删除、人工 PipelineRun、Argo sync 或 mirror flush 作为最终修复证据。

案例:删除工作目录引发 getcwd() 失败

  • 共同指纹:多个 PaC consumer 的后续 step 在业务逻辑执行前统一报 getcwd()
  • 致因:前一步删除 /workspace/source,后续步骤仍把该目录作为工作目录。
  • 正确动作:精确回退致因提交,先恢复自动链,再重做共享 Repository 多分支与 release 支持。
  • 错误动作:
    • 在故障基线上继续叠加共享架构改造;
    • 同时维护多个不完整 PR
    • 误合并无关 PR
    • 用人工补链制造“已恢复”证据。
  • 长期结论:
    • 先恢复最后已知健康状态;
    • 再做独立前向修复;
    • 每个 PR 必须单一职责、可独立验证、可独立回退。

案例:PaC status 公共 renderer 生成非法 JSON

  • 现象分层:
    • SelfMedia PR #82 合并后 14s 自动触发 PipelineRun
    • PipelineRun 对 source commit 8c03bcca554457315bc16bad01e5a96764276472 执行成功,耗时 256s
    • Argo 与 runtime 正常,pac-read-parse-failure 只发生在共享 status 观察器;
    • 因此这是公共观察面事故,不是 SelfMedia 私有 YAML 或自动交付失败。
  • 直接根因:
    • 80ca187cbootstrap_ready 的赋值原本正确;
    • 1f3283ff 在回退 admission 业务阻断时删除该赋值;
    • 2b385f08 恢复 consumer bootstrap fail-closed 引用,却没有恢复赋值;
    • shell 最终拼出非法 "ok":,,影响所有复用共享 renderer 的 PaC consumer。
  • 回退决策:
    • 不整体回退 2b385f08,因为整体回退会再次丢失正确的 bootstrap fail-closed 语义;
    • 只从最后已知正确实现恢复 bootstrap_ready 赋值,并实际执行三种 bootstrap fixture 后 JSON.parse 输出;
    • 这是对致因遗漏的最小语义回退,不是在错误基础上继续扩展 admission 设计。
  • 恢复期间暴露的第二断点:
    • status 在 25000ms YAML 预算内超时,阶段标记锁定在 artifact-evidence
    • 共享 collector 对同一 TaskRun 重复读取,并为成功 run 同步串行执行 kubectl logs
    • NC01 上同一条有界日志读取实测需要 18s38.5s,不能作为 status 同步必需项。
  • 公共面修复:
    • status 只读取一次 TaskRun JSON,并在 task summary 与 artifact terminal record 间复用;
    • status 使用 taskrun-status-only,不再同步读取 Pod 日志;
    • history collector 收敛为一次 selector 聚合,保留失败日志下钻;
    • 目标侧输出无敏感值的 pac-status-progressCLI 在 timeout observation 中显示 remoteStage
    • PipelineRun 成功、Argo Synced/Healthy 且 runtime ready 时,缺少同步日志证据降级为 pac-artifact-log-evidence-deferred 非阻断告警。
  • 首次合并后的补充恢复:
    • 首个修复 PR 合并后,canary 又在 remoteStage=argo 间歇超过 25000ms
    • 给 PipelineRun 查询直接设置过短的单次上限会返回空列表并制造 pipeline-missing false negative,不能以“命令不再超时”冒充恢复;
    • 最终按 Repository CR label 在服务端筛选 PipelineRun,同时保留 consumer classifier 区分共享 repository lanes
    • PR #225625000ms 总预算按 /8000 派生为 3s 单次上限,合并后再次把超时的 PipelineRun 查询改写为空数组并误报 pipeline-missing
    • 同时,admission provenance 为 default ServiceAccount 串行执行十次 kubectl auth can-i,累计占用总预算;
    • 恢复时把十次权限探测收敛为一次 kubectl auth can-i --list,并在目标侧聚合 PipelineRun/TaskRun mutation verbs
    • 精确回退 /8000 错误切分,单次读取上限恢复为 YAML 总预算本身,整次 status 仍由调用侧 25000ms 硬上限约束;
    • PipelineRun 读取失败不再改写为空数组,而是返回 pac-pipelinerun-read-failed,明确表示缺失尚未成立;
    • Argo 原始 JSON 仍只读一次并由 Argo 摘要与 runtime 共同复用;阶段事件继续输出 elapsed 与 step timeout
    • 最终补丁连续两轮 canary 分别在 15.7s11.3s 完成且保持 ok=true
  • 验收结论:
    • status --target NC01 --consumer selfmedia-nc01 --json 稳定在 YAML 25000ms 预算内返回合法 JSON
    • 输出 ok=trueremoteStage=complete、PipelineRun Succeeded、Argo Synced/Healthy、runtime 1/1
    • 全程未修改 SelfMedia 私有 YAML,未人工执行 PipelineRun、mirror、trigger、sync、flush、Argo sync 或 runtime patch。
  • 长期结论:
    • 先区分 delivery failure 与 observer failure
    • 修复共享 renderer 必须取得公共服务面授权;
    • 先恢复最后已知正确语义,再删除重复、无界和同步日志读取;
    • 总预算不得按假定调用数切成会制造 false negative 的局部超时,读取失败也不得伪装成对象不存在;
    • status 负责快速结构化摘要,id-specific 入口负责慢日志证据。