7 月云原生存储故障复盘:那三次 PVC 挂载失败的根本原因
一、三次故障,同一个信号:存储层成了最薄弱的一环
七月发生了三次 PVC 挂载失败的生产故障,每次都表现为 Pod 卡在 ContainerCreating 状态,Events 中报出Unable to attach or mount volumes。三次故障总共造成了 58 分钟的停机时间,影响到 4 个推理服务和 2 个微服务。
表面上看是三个独立事件,但深挖后发现它们共享同一个根因模式:存储容量规划和 QoS 策略跟不上工作负载的变化速度。本月的故障复盘不只是记录"发生了什么",更要说明"为什么之前没发现"和"怎么保证不再发生"。
二、三次故障的全链条分析
故障一:CSI Driver 超时导致 Pod 挂载失败(22 分钟)
时间线:7 月 12 日 14:03,运维部署新版 CodeLlama-34B 推理服务。Pod 卡在 ContainerCreating 21 分钟。14:24 干预后恢复。
直接原因:新版模型权重文件从 8GB 膨胀到 35GB。EBS CSI Driver 执行NodeStageVolume时,需要将 EBS 卷从当前节点 detach 再 attach 到目标节点。35GB 的 EBS 卷 attach 耗时约 35 秒,加上文件系统检查和格式化,总耗时约 48 秒。但 Kubelet 的默认 volume mount 超时时间是 120 秒——看起来足够,但在节点 EBS 连接数(默认上限 28 个)接近饱和时,每个 attach 操作还需要额外的排队时间。实测排队最长达到了 95 秒,加上 48 秒的操作时间,总耗时 143 秒,超过了 120 秒超时。
为什么之前没发现:旧的模型权重文件都在 10GB 以下,attach 操作能在 30 秒内完成,从未触发超时。因此没人注意到这个隐性约束。
修复:短期——将 Kubelet 的--volume-plugin-dir超时参数从 120s 调整到 300s,同时在节点上限制单节点 EBS 挂载数不超过 20(留出 burst 空间)。长期——将模型权重存储从 PVC 挂载方案迁移到基于对象存储的缓存方案,避免每次部署都挂载大卷。
故障二:PVC 配额不足阻塞新模型部署(18 分钟)
时间线:7 月 18 日 10:15,团队部署一个新的内部微调模型,需要 50GB 存储空间。PVC 一直处于 Pending 状态。10:33 排查到 StorageClass 默认配额限制后调整恢复。
直接原因:集群的默认 StorageClass 通过 ResourceQuota 限制了单 PVC 最大 40GB。新模型需要 50GB,但 PVC 的 Events 只显示failed to provision volume with StorageClass "ebs-gp3",没有明确提示是配额不足还是其他原因。操作人员误以为是 CSI Driver 故障,花了 15 分钟排查 CSI 日志方向才发现是配额问题。
为什么之前没发现:这套配额规则是年初设定的,当时最大模型只有 15GB。半年来模型体积持续增长,但配额从未更新。
修复过程:
# 修复前的 ResourceQuota(仅限制总数,未限制单卷) apiVersion: v1 kind: ResourceQuota metadata: name: storage-quota namespace: model-serving spec: hard: persistentvolumeclaims: "20" requests.storage: "500Gi" # 修复后:增加突发弹性上限,同时添加告警 --- apiVersion: v1 kind: ResourceQuota metadata: name: storage-quota namespace: model-serving spec: hard: persistentvolumeclaims: "30" requests.storage: "1000Gi" # 增加 gp3 单卷上限到 100Gi ebs-gp3.storageclass.storage.k8s.io/requests.storage: "100Gi"同时添加了 Prometheus 告警规则:当 PVC 请求大小达到配额上限的 70% 时触发预警,避免将来再次踩坑。
故障三:集群升级导致 PV 回收策略被重置(18 分钟)
时间线:7 月 25 日 03:00,集群从 K8s 1.28 升级到 1.29。升级过程中节点重启,部分 Pod 被驱逐。03:15 重建 Pod 时发现 3 个 PVC 处于 Lost 状态,对应的 PV 已被删除。
直接原因:升级过程中,StorageClass 的reclaimPolicy从Retain被意外重置为默认值Delete。节点重启触发 Pod 驱逐后,PVC 和 PV 的绑定关系断开,K8s 按照Delete策略删除了 PV。3 个模型推理服务的权重文件数据丢失。
为什么之前没发现:集群升级的回滚方案只覆盖了 K8s 核心组件,没有覆盖 StorageClass 的配置变更验证。而且升级前没有做 PV 快照。
修复:第一,将 StorageClass 的 reclaimPolicy 配置通过 GitOps 管理,升级后自动 diff 验证;第二,对生产级 PVC 启用 VolumeSnapshot 定时备份(每 6 小时一次);第三,升级脚本中加入kubectl get sc -o yaml的 pre/post 对比检查。
三、存储故障的共性模式:四种信号值得关注
三次故障揭示了四个共性问题:
容量规划滞后于工作负载变化:存储配额和超时参数设置后就不再更新,而工作负载却在持续增长。存储配置需要和工作负载指标一起被定期 Review。
错误信息质量差:CSI Driver 和 Provisioner 返回的错误信息经常不够明确,导致排障走错方向。需要在监控层面对存储错误做二次解析和分类。
变更管理缺乏存储专项检查:集群升级、模型部署、节点扩缩容都应该包含存储侧的配置验证步骤。
数据备份策略覆盖不足:推理服务的权重文件通常不列入备份范围(因为可以重新下载),但这个假设在批量故障时会导致严重的恢复延时。
四、八月的改进计划与已知局限
针对三次故障制订的三项改进已经在八月初上线:
- 挂载超时动态计算:根据 PVC 大小和当前节点 EBS 连接数,通过 admission webhook 动态计算合理的超时值,而非一刀切的 120s 或 300s。
- PVC 生命周期监控:新增
pvc_pending_duration_seconds和pv_reclaim_events_total两个指标,覆盖 PVC 从创建到绑定的全链路。 - 存储故障演练:每月执行一次 PVC 挂载故障的 Chaos Engineering 实验,验证恢复流程的有效性。
但需要承认,这些措施并不能覆盖所有存储风险。例如分布式存储系统(Ceph/Longhorn)本身的 Bugs、云厂商存储服务的底层故障,这些仍然不在我们的控制范围内。当前架构的选择是"能控制的部分做到极致,不能控制的部分做好容灾降级"。
五、总结
七月三次存储故障的总影响时长 58 分钟,根因分别是 CSI 超时(模型膨胀触发)、PVC 配额不足(规划滞后)和升级导致的 PV 删除(变更管理缺失)。三个故障的共同特征是:配置和规划没能跟上工作负载的增长速度。
存储层是基础设施中最容易被忽视的一层——正常情况下它默默工作,但一旦出问题往往是致命级。基础设施不需要漂亮话,存储层更需要在故障发生之前就把底兜住。