15 KiB
15 KiB
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 大对象开始试探:
-
读取节点摘要:
bun scripts/cli.ts cicd status --node <NODE> -
一次计算选中 consumer 的回退窗口与同 source authority 共同指纹:
bun scripts/cli.ts platform-infra pipelines-as-code diagnose-regression \ --target <NODE> \ --consumer <CONSUMER>ok表示只读采集是否成功;incident表示最新终态是否处于失败连续段;window.lastKnownGood与window.firstFailed给出 source commit 边界;commonFailure只在共享repositoryRef的 lanes 内聚类;state=read-failed时先处理读取可见性,不得按健康解释;- 窗口不足时只执行输出中的有界
expand-window。
-
仅在需要精确 TaskRun 或日志证据时,对首次失败执行输出中的
debug-first-failure:bun scripts/cli.ts platform-infra pipelines-as-code debug-step \ --target <NODE> \ --consumer <CONSUMER> \ --id <PIPELINERUN> -
使用命令输出的
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时, 对象会在spec与status双份放大; - 运行期间的状态更新会重复写入大型对象,进一步放大 Kine/SQLite 单写者压力;
- TaskRun results 只有在字节证据成立时才可判为主因,禁止按字段名称猜测。
- renderer 为每个服务重复展开相同大型 inline
- 将对象证据与同窗口控制面证据关联:
- 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=falsewarning; - 禁止把可见性阈值升级成阻断 PipelineRun、GitOps、Argo 或业务
/health的门禁。
- 状态与 OTel 只输出
快速恢复
按以下顺序恢复用户入口,禁止跳过取证后直接重启:
- 保存最小证据:
- 执行
cicd status --node <NODE>; - 对选中 consumer 执行
pipelines-as-code status和 id-specifichistory; - 记录 node condition、受影响 Service endpoint、异常 Pod owner、 Kine/API/lease/kubelet 的有界日志计数;
- 所有 Kubernetes 大对象在目标侧计算大小和摘要,不把完整 JSON 拉回本机。
- 执行
- 判断是否仍可由 Kubernetes 自行调和:
- API 与 kubelet 可用时,先等待 Deployment/StatefulSet controller 创建替代 Pod;
- 只处理已被驱逐、终态、卡在删除态且已不属于 Service endpoint 的旧 Pod;
- 删除前确认 owner、替代副本和 endpoint,禁止 broad selector、全 namespace 强删或触碰有状态单例。
- 只在节点控制面已退化且 Pod 级调和无效时重启 k3s:
-
该动作属于
$unidesk-daddevP2 紧急恢复,必须有用户授权; -
单节点目标通过 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。
-
- 控制面稳定后释放终态 Tekton 压力:
- 先对 owning cleanup 入口执行 dry-run;
- 再以
--confirm --wait有界删除符合 YAML retention 的终态 PipelineRun; - 必须保留 active run、最新成功证据、业务 PVC、Secret、runtime workload 和 GitOps desired state。
- 恢复只以用户原入口结束:
- node
Ready; - workload ready 且无持续重启;
- Service endpoint 存在;
- backend/provider/public
/health返回成功; - 恢复动作、根因假设和待持久化修复进入 issue 与 TaskTree;遗留 MDTODO 按
$unidesk-tasktree迁移。
- node
长期防复发
- 从 renderer/source 消除重复内联:
- 同构多服务构建优先使用 Tekton matrix,只在 PipelineRun 中保留一份 task 实现;
- 跨 Pipeline 共享的稳定职责使用 reusable Task,并确保 Task reconciler 是正式 owner;
- 没有 Task reconciler 时不得以手工 apply 维持 TaskRef,改用 matrix 或 source 中的原生脚本文件;
- 禁止用压缩字符串、编码载荷、第二 Pipeline 或 fallback 隐藏对象膨胀。
- 为 source artifact 和 renderer 增加结构验证:
- 统计对象总大小、
spec和status.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 或自动交付失败。
- SelfMedia PR
- 直接根因:
80ca187c中bootstrap_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 在
25000msYAML 预算内超时,阶段标记锁定在artifact-evidence; - 共享 collector 对同一 TaskRun 重复读取,并为成功 run 同步串行执行
kubectl logs; - NC01 上同一条有界日志读取实测需要
18s到38.5s,不能作为 status 同步必需项。
- status 在
- 公共面修复:
- status 只读取一次 TaskRun JSON,并在 task summary 与 artifact terminal record 间复用;
- status 使用
taskrun-status-only,不再同步读取 Pod 日志; - history collector 收敛为一次 selector 聚合,保留失败日志下钻;
- 目标侧输出无敏感值的
pac-status-progress,CLI 在 timeout observation 中显示remoteStage; - PipelineRun 成功、Argo
Synced/Healthy且 runtime ready 时,缺少同步日志证据降级为pac-artifact-log-evidence-deferred非阻断告警。
- 首次合并后的补充恢复:
- 首个修复 PR 合并后,canary 又在
remoteStage=argo间歇超过25000ms; - 给 PipelineRun 查询直接设置过短的单次上限会返回空列表并制造
pipeline-missingfalse negative,不能以“命令不再超时”冒充恢复; - 最终按 Repository CR label 在服务端筛选 PipelineRun,同时保留 consumer classifier 区分共享 repository lanes;
- PR
#2256把25000ms总预算按/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.7s与11.3s完成且保持ok=true。
- 首个修复 PR 合并后,canary 又在
- 验收结论:
- 原
status --target NC01 --consumer selfmedia-nc01 --json稳定在 YAML25000ms预算内返回合法 JSON; - 输出
ok=true、remoteStage=complete、PipelineRunSucceeded、ArgoSynced/Healthy、runtime1/1; - 全程未修改 SelfMedia 私有 YAML,未人工执行 PipelineRun、mirror、trigger、sync、flush、Argo sync 或 runtime patch。
- 原
- 长期结论:
- 先区分 delivery failure 与 observer failure;
- 修复共享 renderer 必须取得公共服务面授权;
- 先恢复最后已知正确语义,再删除重复、无界和同步日志读取;
- 总预算不得按假定调用数切成会制造 false negative 的局部超时,读取失败也不得伪装成对象不存在;
- status 负责快速结构化摘要,id-specific 入口负责慢日志证据。