问题背景
业务上云后,有状态组件几乎都会落到 PVC + CSI:数据库、消息中间件、自研有状态服务依赖块存储或文件存储,由集群里的 CSI Controller / Node 插件完成 CreateVolume、ControllerPublish、NodeStage、NodePublish 四段链路。平时滚动发布、扩副本都“无感”,真正第一次踩坑往往是:节点有 CPU/内存、镜像能拉、Quota 也够,Pod 就是卡在 Pending 或 ContainerCreating,描述里只有一句含糊的 FailedMount / AttachVolume.Attach failed。
我们线上是一套基于开源 CSI 的企业存储(iSCSI + multipath),集群规模中等,日常 Pod 创建成功率很高。本篇复盘一次“存储侧磁盘已建好,K8s 侧却长期挂不上去”的故障:表象像调度/配额问题,根因落在 CSI 插件超时阈值偏紧 + 个别节点 multipathd/iSCSI 会话僵死,属于容器运维里典型、但纯“查应用日志”永远绕不开的存储数据面问题。
故障现象
周三 09:10 起,订单中台晚高峰前的一次灰度发布把新版本有状态服务扩到 3 副本。两分钟内:
- 新 Pod 大量卡在
ContainerCreating,kubectl get pod里 READY 一直0/1,AGE 从几十秒滚到十几分钟还不 Ready。 kubectl describe pod反复出现:Warning FailedAttachVolume ... AttachVolume.Attach failed for volume "pvc-xxxx" : rpc error: code = DeadlineExceeded desc = context deadline exceeded- 或
FailedMount ... MountVolume.MountDevice failed ... timeout waiting for volume to be attached
- 同命名空间无状态 Deployment 正常;
kubectl top node显示 CPU/内存充裕;ResourceQuota、镜像拉取、NetworkPolicy 均无异常(也排除了近期写过的配额耗尽、ImagePull、NP 全隔离类故障)。 - 存储控制台侧:对应 LUN/卷已创建成功,说明问题不在“没盘”,而在“盘到节点”的 Attach/Mount 链路。
- 约 1/3 新副本会“碰巧”挂在某几台历史上运维过的老节点上,固定在那几台上必挂;调度到新节点偶发成功——这给了“节点本地存储通道有问题”的强线索。
业务侧表现是发布窗口超时、灰度入口 502/超时上升;值班最初误判为“调度器坏了 / CNI 又炸了”,浪费了将近二十分钟。
排查过程
1. 先把故障落点钉在 Volume 生命周期
对卡死的 Pod:
|
|
关键观察:
- PVC 状态多数是 Bound(StorageClass 已 provisione 成功),不是 Pending。
VolumeAttachment对象长时间ATTACHED=false,或ATTACHED=true但 Pod 仍 FailedMount——说明 Controller 侧 Attach 与 Node 侧 Mount 可能卡在不同阶段。- Events 时间戳间隔接近 CSI 默认/我们配置的超时(约 15s~2min 一跳),符合 gRPC DeadlineExceeded 特征,而不是立即拒绝。
这一步把排查从“调度/配额/镜像”收敛到 CSI 四段式链路。
2. 看 CSI Controller 与 Node 插件日志,不要只看业务容器
|
|
日志里能对上三组关键信息:
ControllerPublishVolume对部分 volumeID 反复重试,伴随context deadline exceeded;- 成功 Attach 的 volume,在问题节点上
NodeStageVolume卡在waiting for device to appear under /dev/disk/by-path/...; - 健康节点同一 volume 类操作通常在数秒内完成。
同时核对 CSI sidecar:
|
|
Controller 副本本身 Running,排除“插件全挂”。更像是 后端存储/节点通道慢,把默认超时打穿。
3. 对比“必挂节点”与“正常节点”的数据面
登录两台对照节点:
|
|
问题节点特征非常扎眼:
iscsiadm -m session有会话,但部分 session 处于 reconnecting / free 抖动;multipath -ll出现 paths down 或 map 不完整,对应 LUN 的 dm 设备迟迟不出现;dmesg有connection timed out、scsi host reset、偶发blocked for more than 120 seconds;multipathd内存与 fd 略高,日志刷path checker failed。
正常节点同存储、同网段则会话稳定、map 齐全。存储交换机侧无整网级告警,但问题节点上联端口错误计数略高——单节点存储网络半瞎比“整个 SAN 挂了”更符合离散失败画像。
4. 核对超时与并发:插件“太老实”也会放大故障
翻 CSI 部署参数与 sidecar 参数,发现历史变更里为了“失败快、别堵队列”,把:
timeout/--timeout(外部 attacher / provisioner)- 以及 driver 内
attachTimeout类配置
压得偏紧。存储正常时问题不大;节点 multipath 抖动时,一次登录+扫盘本身就可能超过阈值,于是:
- 调用方 DeadlineExceeded → 重试;
- 存储侧可能已有半成品会话;
- 重试并发顶高,控制器工作队列变长;
- 更多 Pod 进入 FailedAttach/FailedMount 循环。
这与业务高峰叠加,形成“越重试越堵”的正反馈。
5. 用最小实验验证因果
- 将一个 Pending Pod 打污点容忍/节点选择器,强制调度到健康节点:数分钟内 Mount 成功、容器起来。
- 在问题节点上手动
multipath -r+ 重启multipathd后再放回调度:同一 PVC 可完成 NodeStage。 - 只调大超时、不修节点:失败率下降但仍有长尾——说明超时是放大器,不是唯一根因。
结论成型:主因是问题节点 iSCSI/multipath 数据面僵死;次因是 CSI 超时与重试策略过激,把单点抖动放大成发布窗口级事故。
解决方案
紧急止血(发布窗口内)
- 给有状态工作负载临时避开坏节点
- 给问题节点加 taint:
storage.kubernetes.io/unschedulable=true:NoSchedule - 或在 STS/Deployment 上用
nodeAffinity先躲开故障机架/故障节点池。
- 给问题节点加 taint:
- 清理卡住的 VolumeAttachment(慎用,先确认存储侧无双挂)
- 对长期
ATTACHED=false且确认无业务在用的 attachment,按厂商手册做 force detach / 删除后重建,避免对象卡死阻塞队列。
- 对长期
- 节点侧快速恢复存储通道
|
|
- 临时放宽 CSI 超时(只作止血,回写变更单):将 attacher/provisioner 与 driver 超时调到能覆盖“慢扫盘”的上限,并限制并发 attach,防止打爆存储控制面。
按上述处理后,灰度副本在 10 分钟内全部 Ready,入口错误率回落。
根治动作
- 节点镜像与巡检:装机模板固化
iscsid/multipathd开启、合理的 path checker、启动顺序;巡检脚本定期抓multipath -ll异常与 session 重连次数。 - CSI 超时与重试策略分层:慢 IO 场景给够 deadline;失败区分 可重试(超时/暂不可用) 与 不可重试(参数错误/权限),避免无脑密集重试。
- 发布门禁:有状态服务发布前自动检查目标节点池的存储健康(session、multipath、CSI node plugin Ready),不健康节点先 taint 再发版。
- 可观测性:给 CSI Controller/Node 日志做
DeadlineExceeded/FailedMount聚合告警,并对VolumeAttachment长时间未 ATTACHED 做对象级告警,别等业务 Pod 红了才知道。 - 网络侧:问题节点上联错包排查(光模块、线缆、交换机口),存储 VLAN QoS/隔离复核,避免“计算网正常、存储网亚健康”。
根因分析
本故障是 数据面僵死 × 控制面超时策略 的叠加:
| 层次 | 问题 | 作用 |
|---|---|---|
| 节点存储栈 | iSCSI session / multipath path 异常,设备节点迟迟不出现 | 直接导致 NodeStage/NodePublish 失败 |
| CSI 插件 | Attach/Mount 超时偏紧、重试密集 | 把偶发慢变成队列堆积与大面积 DeadlineExceeded |
| 发布与调度 | 新副本摊到坏节点,无存储健康门禁 | 把节点级问题放大成版本发布失败 |
| 观测 | 只盯应用/节点 CPU,不盯 VolumeAttachment 与 CSI 日志 | 延长误判时间,浪费黄金止血窗口 |
存储控制台“卷已创建”只能证明 Provision 成功,不能证明 Attach/Mount 成功;K8s 有状态故障排查必须按 CSI 生命周期逐段取证。
预防措施
- 装机与基线:计算节点若跑有状态负载,存储多路径与 iSCSI/FC 客户端纳入基线,开 conf 变更审计。
- 发布 Checklist:有状态发版前检查 CSI driver Ready、问题
VolumeAttachment清零、目标节点 multipath 健康。 - 容量与超时联调:存储厂商 RTO 指标 vs CSI timeout 对齐压测;故意注入 path down,验证退避而非风暴重试。
- 节点池隔离:数据库类 STS 与批处理/高干扰负载分池,降低“邻居作业打爆存储会话”的概率。
- 演练:每季度做一次“拔存储链路/杀 multipathd”演练,验证 taint、驱逐、服务降级与回切文档是否可执行。
- 告警分层:应用延迟 / FailedMount 次数 / VolumeAttachment 年龄 / multipath failed path 四层告警,避免只看业务红灯。
总结
这次故障的教训很直接:Pod 起不来,不一定是调度、镜像或配额;有 PVC 时优先看 VolumeAttachment 和 CSI 四段链路。 表面是 DeadlineExceeded,底下往往是某台节点的 iSCSI/multipath 半死不活;超时参数过紧只会让重试帮倒忙。
排查顺序建议固化为:PVC Bound 了吗 → VolumeAttachment ATTACHED 了吗 → 卡在 Controller 还是 Node → 对照健康节点看 session/multipath/dmesg → 再决定 taint 止血还是调超时/修链路。有状态发布要把“存储数据面健康”写进门禁,而不是默认“节点 NotReady 才算有问题”。疏通 PVC 挂载,本质上是把 CSI 从“黑盒重试”变成“可观测、可熔断、可演练”的常规运维对象。