系统学习 故障排查学习路径:从方法论到系统、网络、数据库与 K8s

incident-helm-pending

一次 Helm 升级被中断后 release 卡在 pending-upgrade、回滚又遭遇并发操作的复盘(匿名化合成案例)。

现象

某业务集群的发布流水线执行 Helm 升级后,新版本 Pod 一直未就绪。流水线在自身超时时间到达时强行结束任务,Helm 客户端没有机会完成收尾。随后 helm list --all 显示 release 处于 pending-upgrade,再次执行 upgrade 或 rollback 都报 another operation (install/upgrade/rollback) is in progress。旧版本仍在服务,但该 release 的所有发布操作被卡住。

时间线

影响面

旧版本 Pod 保持可用,没有发生整站故障。影响集中在交付侧:该 release 约半小时无法继续升级,流水线自动重试和人工操作互相阻塞;同一命名空间的后续变更被迫排队。失败的 Pod 占用少量资源,节点容量略紧但没有触发驱逐。由于本次变更不包含数据库结构变更,回滚不涉及数据恢复;如果涉及不可逆迁移,处理顺序必须完全不同。

误导线索

看到 pending-upgrade 和“another operation in progress”,第一反应是有一个陈旧的 Helm 锁,或者 release Secret 损坏。实际上 Helm 没有需要人工删除的独立锁对象,release 当前 revision 的状态记录就是并发保护的依据。第一次 rollback 失败并不是状态无法恢复,而是此时自动重试真的在运行,两个操作发生了重叠。

第二个误判是把问题归咎于 chart 或 Helm 本身。检查 Pod 日志后可以看到明确的配置键错误,升级之所以没有正常变成 failed,是因为流水线在 Helm 完成收尾前杀掉了客户端。推翻“锁坏了”的证据是:自动重试停止后,同一个 rollback 命令成功;helm history 也清楚显示上一版处于 deployed,release 数据没有丢失。

根因

直接触发因素是新版本配置键错误,Pod 无法就绪。更关键的是发布链路的超时边界设置错误:流水线硬超时早于 Helm 的 --wait 完成,取消任务时直接终止了 Helm,使其没有机会执行失败回滚或写入最终状态。流水线还启用了自动重试,与人工回滚并发;同一个 release 没有互斥的 CI 并发组,最终形成了“状态锁住 + 操作竞争”。

如果升级命令使用了 --atomic,Helm 会尝试在失败时自动回滚,但前提是客户端能完整执行到失败处理阶段。外层进程被强杀时,任何应用层清理都可能来不及执行,因此不能只靠 --atomic 兜底。

修复

先停止所有自动化,包括队列中的重试、部署机器人和同一 release 的其他流水线,保证只有一个操作者:

helm list -n <ns> --all
helm history <release> -n <ns>
helm status <release> -n <ns>
kubectl get pods -n <ns> -l app=<app>
kubectl describe pod <pod> -n <ns>
kubectl logs <pod> -n <ns> --previous

从历史记录中选出明确为 deployed 的 revision,读取它的实际 values 和 manifest,确认回滚目标:

helm get values <release> -n <ns> --revision <last-deployed> --all
helm get manifest <release> -n <ns> --revision <last-deployed> > /tmp/last-deployed.yaml
helm rollback <release> <last-deployed> -n <ns> --wait --timeout 10m
helm history <release> -n <ns>

不要用删除 release Secret 的方式强行“解锁”。如果 rollback 仍提示有操作进行中,先找到并停止真正的并发任务;如果 rollback 因为 hook 失败,查看对应 Job 或 Pod 的日志,而不是反复重试。回滚完成后检查 Deployment 的可用副本和 Service 端点,再修复配置。重新发布前先本地渲染并与上一版对比:

helm get manifest <release> -n <ns> --revision <last-deployed> > /tmp/previous.yaml
helm template <release> <chart> -n <ns> -f <values-file> > /tmp/rendered.yaml
diff -u /tmp/previous.yaml /tmp/rendered.yaml

确认差异只包含预期变更后,使用 --atomic --wait 并让 Helm 超时小于流水线超时,再执行升级:

helm upgrade <release> <chart> -n <ns> -f <values-file> \
  --atomic --wait --timeout 10m

预防

相关命令

参考链接

读完自测

合上页面,先回答,再展开对照。

  1. Helm release 卡在 pending-upgrade 时,为什么不能直接删除 release Secret?
  2. 流水线的超时、自动重试和 --atomic 应该如何配合?
  3. 第一次 rollback 报“another operation in progress”时,第一步应该做什么?
答案
  1. Secret 是 release 历史和状态的一部分,直接删除会丢失 revision、values 和回滚依据,还可能让 Helm 误判为全新安装。应先停止并发操作,确认历史,再用 rollback 到已部署 revision 完成状态转换。
  2. 流水线硬超时应大于 Helm 的等待超时并留出回滚时间;不要在旧进程未退出时自动重试。--atomic 只有在 Helm 客户端能完成失败处理时才有效,强杀会绕过它。
  3. 不要继续点回滚或手工删 Secret。先找到并停止正在运行的 upgrade、rollback 或重试任务,确认同一 release 只有一个操作者,再检查 helm history 并执行回滚。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令