CSI Inline Volume 孤儿问题:Kubelet 重启后 Volume 无法清理的根因与修复
当 Pod 正在 Terminating 期间 kubelet 发生重启,CSI Inline Volume(ephemeral volume)可能进入”孤儿”状态——kubelet 重启后既不卸载该 volume,也不清理挂载点,导致底层 LVM 资源泄漏。本文分析其根因,并给出修复方案。
问题现象
Pod 处于 Terminating 状态时,节点上的 kubelet 重启。重启后日志中出现如下错误:
1 | kubelet: I reconciler.go:388] "Could not construct volume information, cleaning up mounts" |
最终结果:Pod 被从 API Server 删除(SyncLoop DELETE),但 volume 的 LVM 逻辑卷仍然存在于节点上,成为孤儿资源。
根因分析
1. reconstructVolume 走错了插件查找路径
Kubelet 重启后,会通过 reconstructVolume 从磁盘上残留的挂载信息重建 volume 状态。核心流程如下:
1 | kubelet restart |
CSI Inline Volume(api.CSIVolumeSource)与 CSI PV(api.CSIPersistentVolumeSource)是两种不同的结构体。getPVSourceFromSpec 只处理后者,遇到前者时直接报错。
2. 应走 GetUniqueVolumeNameFromSpecWithPod
对于 ephemeral volume,唯一名称需要结合 Pod UID 生成,应调用 GetUniqueVolumeNameFromSpecWithPod,而非 GetUniqueVolumeNameFromSpec。判断走哪条路径的逻辑是:
1 | if attachablePlugin != nil || deviceMountablePlugin != nil { |
CSI Inline Volume 不应有 attachablePlugin 和 deviceMountablePlugin,但代码中使用了 FindAttachablePluginByName(按名字查找),而非 FindDeviceMountablePluginBySpec(按 spec 查找)。这导致本应进入 else 分支的 inline volume 错误地进入了 if 分支。
3. reconstructVolume 失败后的后果
1 | reconstructedVolume, err := rc.reconstructVolume(volume) |
由于 Pod 正在 Terminating,Pod 没有被加入 Desired State of World(DSW),所以 volumeInDSW 为 false。kubelet 调用 cleanupMounts → UnmountVolume,但此时容器还没有完全退出,底层 CSI 驱动返回 Aborted(NodeUnpublish 操作仍在进行中)。
这是唯一一次清理机会,失败后 kubelet 不再重试,volume 就此成为孤儿。
触发条件
三个条件同时满足才会触发:
- Pod 正处于 Terminating 状态
- Kubelet 在此期间重启或宕机
reconstructVolume失败,且 Pod 未被加入 DSW
CSI 驱动侧的报错
在 kubelet 尝试清理的同时,CSI 驱动(本地 LVM 驱动)也尝试执行 NodeUnpublishVolume,但因 LV 仍被使用而失败:
1 | CSI local driver: NodeUnpublishVolume req=volume_id:"csi-17ef0134..." target_path:"...mount" |
LV 因容器仍在使用文件系统而无法被删除,NodeUnpublish 以 Internal 错误返回。
修复方案
上游修复
该 bug 已在 Kubernetes 1.25 修复:kubernetes/kubernetes#108997
修复核心:将 reconstructVolume 中对 CSI Inline Volume 的插件查找改为使用 FindDeviceMountablePluginBySpec,使其正确跳过 attachablePlugin 和 deviceMountablePlugin 的查找,进而走 GetUniqueVolumeNameFromSpecWithPod 路径,成功重建 volume 状态并正常触发卸载流程。
低版本临时处理
在 bug 修复版本未上线之前,需要手动清理孤儿 LV。步骤:
1 | # 1. 确认孤儿 LV(无对应挂载点的 LV) |
总结
| 环节 | 问题 | 影响 |
|---|---|---|
| 插件查找 | FindAttachablePluginByName 误匹配 inline volume |
reconstructVolume 报错 |
| 路径判断 | inline volume 错进 GetUniqueVolumeNameFromSpec |
volume 无法加入 ASW |
| 清理时机 | 容器未退出时 cleanupMounts 必然失败 |
volume 成为孤儿 |
根本原因是 CSI Inline Volume 在 kubelet 重建路径上的判断逻辑与 CSI PV 混用,导致重启窗口期内的 volume 无法被正确重建和清理。上游在 1.25 通过区分插件查找方式解决了这一问题。