目录
[1. 先确认基础服务是否真的异常](#1. 先确认基础服务是否真的异常)
[2. 再看控制器日志](#2. 再看控制器日志)
[3. 对比关键 commitId](#3. 对比关键 commitId)
[1. 控制器只跟踪自己的自动提交链路](#1. 控制器只跟踪自己的自动提交链路)
[2. 人工手动提交插入了新的版本上下文](#2. 人工手动提交插入了新的版本上下文)
[3. 控制器没有感知这次手动提交](#3. 控制器没有感知这次手动提交)
[1. 先确认控制器当前检查的是哪个 commitId](#1. 先确认控制器当前检查的是哪个 commitId)
[2. 再确认 ready 属于哪个 commitId](#2. 再确认 ready 属于哪个 commitId)
[3. 排查是否存在手动提交、补提交或旁路操作](#3. 排查是否存在手动提交、补提交或旁路操作)
[4. 必要时重新对齐链路](#4. 必要时重新对齐链路)
[1. 状态必须和版本绑定](#1. 状态必须和版本绑定)
[2. 自动链路和人工链路要严格区分](#2. 自动链路和人工链路要严格区分)
[3. 日志要能帮助还原上下文](#3. 日志要能帮助还原上下文)
[4. 排查时先对齐对象,再判断状态](#4. 排查时先对齐对象,再判断状态)
一次部署阻塞的根因分析:自动提交与手动提交链路混用的代价
这次问题看起来不大,但很典型,也很值得记录。
在云产品部署过程中,控制器会分批提交产品终态,每一次提交都会生成一个 commitId,随后控制器会按这个 commitId 去检查底层依赖服务是否 ready,比如 dockerRegistry。按设计,只要依赖服务 ready,部署流程就应该继续推进。
但实际情况是:控制器一直卡在第一次提交的任务上,日志显示该任务长期处于 not done,而我们去看 dockerRegistry,它明明已经是 ready。表面上看像是基础服务问题,实际上并不是。
这类问题最容易误判的地方就在于:状态看起来对了,但对象不对。
一、故障现象
故障表现非常明确:
- 控制器持续检查第一次提交对应的任务;
- 该任务状态一直是
not done; dockerRegistry在现网已经是ready;- 部署流程因此卡住,无法继续往下走。
如果只看结果,很容易先怀疑是 dockerRegistry 没起来,或者基础服务健康检查有问题。
但实际排查后发现,问题不在服务本身,而在控制器检查的 commitId和服务 ready 对应的 commitId不是同一个。
二、排查过程
1. 先确认基础服务是否真的异常
第一步,我们先从最常见的方向排查:dockerRegistry 是否真的没 ready。
检查结果很直接:dockerRegistry 是 ready 的,底层服务没有异常。
这一步先排除了"服务本身故障"这个方向。
2. 再看控制器日志
既然服务没问题,就继续看控制器日志。
日志里一个关键现象是:控制器一直在检查第一次提交,对应任务状态始终是 not done。
这时候问题就变得更像是"状态错位",而不是"服务故障"。
3. 对比关键 commitId
为了确认是不是上下文不一致,我们把几个关键值拉出来做了对比:
- 所有提交的
commitId - 最新提交的
commitId - 控制器正在检查的
commitId dockerRegistry变成ready时对应的commitId
这一对比,问题就清楚了。
中间有人为手动提交了一次,产生了一个新的 commitId。
而 dockerRegistry 正是在这个新的 commitId 中变成 ready 的。
但控制器仍然在检查旧的 commitId,也就是第一次提交对应的任务。
换句话说:
- 新任务已经 ready;
- 旧任务仍然 not done;
- 控制器只认旧任务;
- 所以流程一直卡住。
三、根因分析
这次故障的根因,可以归结为一句话:
提交上下文错位,导致状态判断失真。
拆开来看,有三个关键点:
1. 控制器只跟踪自己的自动提交链路
控制器是按自动提交生成的 commitId 来推进状态的,它并不会自动去识别别的提交来源。
2. 人工手动提交插入了新的版本上下文
中间的人为手动提交,生成了新的 commitId,也改变了实际 ready 的归属对象。
3. 控制器没有感知这次手动提交
因此它一直在检查旧任务,旧任务没有变化,自然就持续显示 not done。
本质上不是控制器"看错了状态",而是它一直在看旧版本的状态 。
而我们看到的 dockerRegistry ready,属于另一个版本的事实。
四、解决思路
这类问题的解决,核心不是"让服务 ready",而是先把状态所属的上下文对齐。
1. 先确认控制器当前检查的是哪个 commitId
这是第一优先级。
如果控制器盯着旧提交,后续所有状态判断都没有意义。
2. 再确认 ready 属于哪个 commitId
dockerRegistry ready 不能只看当前环境状态,必须确认它对应的是哪一次提交。
3. 排查是否存在手动提交、补提交或旁路操作
只要存在人工介入,就要明确它是否进入了控制器的观察链路。
如果没有进入,那它就不能和自动链路混为一谈。
4. 必要时重新对齐链路
如果旧任务已经卡死,而真正生效的是新提交,就需要明确以哪条链路为准,避免控制器继续盯着旧任务不放。
五、这次踩坑的几个教训
1. 状态必须和版本绑定
在分批提交、异步推进的系统里,ready 和 not done 不是独立结论,必须绑定 commitId 才有意义。
没有版本上下文的状态,不能直接拿来判断问题。
2. 自动链路和人工链路要严格区分
手动提交、临时补提交、人工修复,这些动作都会改变状态来源。
如果和控制器的自动状态机混在一起,就很容易出现"环境已经变了,但控制器还在看旧任务"的问题。
3. 日志要能帮助还原上下文
控制器日志如果只记录结果,不记录当前检查的 commitId、提交来源、状态归属版本,那么排查时就只能靠猜。
对于这种问题,日志的核心价值不是告诉你"发生了什么",而是告诉你"这个状态属于谁"。
4. 排查时先对齐对象,再判断状态
先确认控制器在看谁,再确认当前 ready 属于谁,最后判断两者是否一致。
只要对象没对齐,后面的状态分析往往都是无效的。
六、最后总结
这次故障的表面现象是"dockerRegistry 已经 ready,但控制器一直卡住",本质上却是一个很典型的工程问题:状态判断脱离了提交上下文 。
在复杂系统里,状态不是孤立存在的,它一定属于某个版本、某条链路、某个 commitId。如果只看结果,不看归属,就很容易把不同版本的事实混在一起,最后得出错误结论。
这件事给我的最大提醒是:
复杂系统里最危险的,不是没有状态,而是状态看起来正确,却对应了错误的对象。
以后再遇到类似问题,我会优先检查三件事:
- 当前控制器在看哪个
commitId; - 当前环境状态属于哪个
commitId; - 中间有没有人手动改过链路。
只要这三件事对齐,很多"看起来卡死"的问题,其实都能很快定位。