vSAN「虚拟对象」页面报"无法提取请求的数据"——一个孤儿 iSCSI 目标引发的血案
vSphere Client 里点开 vSAN → 虚拟对象(Virtual Objects),页面不给列表,只甩一句:
无法提取请求的数据。有关详细信息,请查看 vSphere Client 日志
按经验这通常意味着 vSAN 对象健康度出问题。但这次查下去发现集群健康全绿、没有对象降级、20 多台 VM 全部正常运行 —— 问题完全不在存储层。
真正的元凶是一个孤儿 iSCSI 目标,以及 vSAN UI 插件里一处没做判空的代码。
一、先排除”老套路”
这个报错在老环境里最常见的成因是:某台主机磁盘拥塞 → 触发组件重建 → 重建风暴 → Web UI 查询超时。
所以先按常规流程查一遍:
- 集群健康状态(cluster status)
- 各主机磁盘 / 对象健康度
- 是否有降级对象、重建中对象
结果:全绿。 三台主机都是 green,没有任何对象降级、没有重建、没有磁盘故障。
那就说明:数据面是好的,界面是坏的。 这是纯 UI 渲染问题。
二、去 vSphere Client 日志里捞 JS 堆栈
既然前端渲染失败,就直接去看前端日志:
1 | ls -lat /var/log/vmware/vsphere-ui/logs/ |
一下就捞到了关键堆栈:
1 | TypeError: Cannot read properties of undefined (reading 'vsanObjectUuid') |
注意这条日志的价值:它直接把”vSAN 虚拟对象列表”和”iSCSI Target”这两件看起来无关的事情绑在了一起。
调用链是:
1 | listVirtualObjects → listObjects → getVirtualObjects → buildIscsiTargets |
三、扒插件源码,确认是判空缺失
vSphere Client 的 vSAN 插件是一个前端 bundle,可以直接从 vCenter 本机拉下来:
1 | FQDN=$(hostname) |
再从报错信息给的精确字符偏移(main.js:1:815462)附近截取代码:
1 | d = open('/tmp/_main.js', encoding='utf-8', errors='replace').read() |
拿到崩溃点原文:
1 | buildIscsiTargetModel(v, d, O, _) { |
根因清楚了:插件假定每一个 iSCSI 目标都有 objectInformation。只要存在一个目标该字段为 undefined,整个”虚拟对象”列表的渲染就全线崩溃,前端只能显示那句含糊的错误。
而 vSAN 的 API 返回里,确实有一个目标不满足这个假设。
四、定位具体是哪个目标
用 vSAN API 列出所有 iSCSI 目标,逐个检查 objectInformation:
1 | vc_mos = vsanapiutils.GetVsanVcMos(vsanapiutils.GetVsanVcStub(si._stub, context=ctx), context=ctx) |
结果一目了然:
| 目标 alias | ioOwnerHost | objectInformation |
|---|---|---|
| target-a | host-1006 ✓ |
✓ healthy |
| target-b | host-1006 ✓ |
✓ healthy |
| 某个残留目标 | 空 ❌ | None ❌ |
| target-c | host-1006 ✓ |
✓ healthy |
就是它。 一个 I/O owner 为空、对象信息丢失的目标。
继续验证它是不是”孤儿”:
1 | GetIscsiTarget(cluster, '<alias>') |
列表里能列出来,按名字去取却取不到 —— 连它归属的主机也不认这个目标。典型的状态残留孤儿。
五、尝试删除:撞上死结
第一反应是把这个残留目标删掉。两条路都试了:
| 方式 | 结果 |
|---|---|
vCenter API RemoveIscsiTarget(cluster, alias) |
❌ Util: Cannot find target namespace dir for target <uuid> |
主机侧 esxcli vsan iscsi target remove -a <alias> |
❌ 完全相同的错误 |
(补充一个前置细节:如果目标下还挂着 LUN,会先报 Target <alias> is not empty,需要先 RemoveIscsiLUN(cluster, alias, lunId) 把 LUN 删掉。)
再去主机侧看这个目标的真实状态:
1 | esxcli vsan iscsi target list |
1 | Alias ... LUNs Is Compliant UUID I/O Owner UUID |
Is Compliant = false + I/O Owner UUID 为空 —— 问题目标连合规性都不满足了。
1 | esxcli vsan iscsi target get -a <alias> |
CMMDS 里的 owner 记录丢了,目标的”命名空间目录”也不存在了。
于是形成死结:想删,删除流程第一步就要去找这个命名空间目录,找不到直接报错退出;想修,普通管理接口没有任何入口。
六、找到官方工具:vitRecoveryTool
翻主机上的 vSAN 工具目录时发现了这个:
1 | ls /usr/lib/vmware/vsan/bin/ |
VIT = vSAN iSCSI Target。 vitRecoveryTool 正是 VMware 为”VIT 状态损坏”提供的官方恢复工具:
1 | python /usr/lib/vmware/vsan/bin/vitRecoveryTool.pyc --help |
1 | usage: vitRecoveryTool.pyc [-h] [--force] [--dry-run] [--type TYPE] [--target TARGET] |
七、执行恢复(有关键前置条件)
跑起来后工具先给出一段警告,这是整个操作里最需要注意的地方:
1 | NOTE: |
影响评估(动手前必须做)
停 VIT 服务会不会影响业务?逐项查证:
- vSAN 数据存储不受影响。VIT(vSAN iSCSI Target)是独立服务,vsanDatastore 上的所有 VM 完全不受波及。
- 受影响的只有 iSCSI LUN 的使用方(会短暂断连几分钟)。
- 检查 iSCSI datastore 挂在哪台主机、上面跑着什么 VM —— 确认这些和 VIT 无关。
结论:可以安全执行,但要让所有相关方知晓那几分钟的 iSCSI 中断。
步骤
1 | # 1) 集群内每台主机:停 VIT 数据服务 |
⚠️ 必须喂 3 个 y —— 工具有两次 Continue(Y/y/N/n) 确认,第二次出现在全部检查通过之后。只喂一个会看到:
1 | Continue(Y/y/N/n)[n]: Failed with: EOF when reading a line |
这属于”检查已全过、就差确认”,重新跑一遍喂足就行,不会留下半成品状态。
工具的先置检查清单
正常时应全部为 Y:
1 | Check hostd is alive ................................. Y |
中间出现的
clomdb-Cdb_FindNonPreferredFaultDomainId: Failed to find Non-Preferred FD是非拉伸集群的正常噪音,不是错误,忽略即可。
成功输出
1 | Backup vit.conf ..........................................................Success |
八、验证
修复前后对比:
| 指标 | 修复前 | 修复后 |
|---|---|---|
Is Compliant |
❌ false | ✅ true |
I/O Owner UUID |
❌ 空 | ✅ 6569b0ac-...(与正常目标一致) |
| 目标 UUID | 旧(已丢失) | ✅ 新的 uuid |
objectInformation |
❌ None | ✅ 有值 |
GetIscsiTarget(cluster, alias) |
❌ 报错找不到 | ✅ 正常返回 |
objectInformation 不再是 None → UI 插件的 buildIscsiTargetModel 不再崩溃 → “虚拟对象”页面恢复正常。
其他完整性检查:
- 所有 VM 照常运行,数量与操作前一致
- 两台主机
vitd+vitsafehd均已恢复运行 - vSAN 集群全绿
- 其余 iSCSI 目标未受影响,原有 LUN 保持
Online
小提示:修完后浏览器里的 vSphere Client 建议 Ctrl+F5 硬刷新(插件 JS 有缓存)。
九、经验总结
1. 报错信息虽然含糊,但日志里的 JS 堆栈是金子
那句”无法提取请求的数据”本身毫无信息量,但 vSphere Client 日志里的 TypeError 把问题精确定位到了具体函数。遇到前端报错,第一件事是去看前端日志,而不是从底层往上猜。
2. 反直觉的信号要当线索用
“vSAN 虚拟对象页面坏了” → 直觉是存储层问题 → 但集群健康是全绿的。这个矛盾点恰恰是最有价值的线索:它告诉你问题在渲染层,不在数据层。
3. 扒源码定位判空 bug,比读一百篇文档快
从报错给的字符偏移直接定位到那一行 O.has(v.objectInformation.vsanObjectUuid),几分钟就确认了根因。前端 bundle 就在 vCenter 本机,可以直接 curl 下来分析。
4. 官方工具常常藏在 /usr/lib/vmware/<组件>/bin/
vitRecoveryTool.pyc 不是常规管理命令,esxcli 里也没有。它就在工具目录里躺着。遇到”官方 API 走不通”的死结,先去翻各组件自带的工具目录。
5. “删不掉”不等于”修不好”
原目标是想删掉这个残留记录,但删除路径依赖的命名空间目录已不存在,形成死结。官方工具的解法不是”强行删除”,而是把命名空间重建出来 —— 目标恢复合规后,UI 恢复正常;此时若还想删,也能正常删掉了。
修复优先于删除。
6. 破坏性操作前把影响面查清楚
停 VIT 服务前,逐项确认了:vSAN 数据存储不受影响(独立服务)、受影响的只有 iSCSI LUN 使用方、iSCSI datastore 与服务无关。把这些查清楚了,才敢动手停服务。
7. 顺手记下几个 API 参数坑
GetIscsiLUNs(cluster, targetAliases)第二个参数是字符串列表['alias'],传单个对象会报not iterable,传列表里的对象会报expected type str- 删除目标前必须先删 LUN,否则报
Target <alias> is not empty RemoveIscsiTarget/RemoveIscsiLUN返回的是 Task,需要等待任务完成
十、一句话复盘
存储全绿、VM 全好、界面报错”无法提取数据” —— 这种矛盾组合大概率是前端插件被某条异常数据噎住了。这次是一条损坏的 vSAN iSCSI 目标记录,让一个没做判空的 JS 函数掀翻了整个页面。定位靠日志堆栈,修复靠官方工具,前提是把影响面查清楚再动手。