疏通PVC挂载:一次CSI插件超时导致业务Pod起不来的排查

有节点有配额PVC却Pending:CSI Controller超时+节点multipath卡死致挂载失败,扩超时与修节点通道后恢复。

问题背景

业务上云后,有状态组件几乎都会落到 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 副本。两分钟内:

  1. 新 Pod 大量卡在 ContainerCreatingkubectl get pod 里 READY 一直 0/1,AGE 从几十秒滚到十几分钟还不 Ready。
  2. 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
  3. 同命名空间无状态 Deployment 正常kubectl top node 显示 CPU/内存充裕;ResourceQuota、镜像拉取、NetworkPolicy 均无异常(也排除了近期写过的配额耗尽、ImagePull、NP 全隔离类故障)。
  4. 存储控制台侧:对应 LUN/卷已创建成功,说明问题不在“没盘”,而在“盘到节点”的 Attach/Mount 链路。
  5. 约 1/3 新副本会“碰巧”挂在某几台历史上运维过的老节点上,固定在那几台上必挂;调度到新节点偶发成功——这给了“节点本地存储通道有问题”的强线索。

业务侧表现是发布窗口超时、灰度入口 502/超时上升;值班最初误判为“调度器坏了 / CNI 又炸了”,浪费了将近二十分钟。

排查过程

1. 先把故障落点钉在 Volume 生命周期

对卡死的 Pod:

1
2
3
kubectl describe pod order-sts-2 -n order | sed -n '/Events/,$p'
kubectl get pvc -n order
kubectl get volumeattachment

关键观察:

  • 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 插件日志,不要只看业务容器

1
2
kubectl -n kube-system logs deploy/csi-controller --tail=200
kubectl -n kube-system logs ds/csi-node -c node-driver --tail=200 --prefix

日志里能对上三组关键信息:

  1. ControllerPublishVolume 对部分 volumeID 反复重试,伴随 context deadline exceeded
  2. 成功 Attach 的 volume,在问题节点上 NodeStageVolume 卡在 waiting for device to appear under /dev/disk/by-path/...
  3. 健康节点同一 volume 类操作通常在数秒内完成。

同时核对 CSI sidecar:

1
2
kubectl -n kube-system get pod -l app=csi-controller -o wide
kubectl -n kube-system get csidriver

Controller 副本本身 Running,排除“插件全挂”。更像是 后端存储/节点通道慢,把默认超时打穿

3. 对比“必挂节点”与“正常节点”的数据面

登录两台对照节点:

1
2
3
4
5
6
# iSCSI / multipath 状态
iscsiadm -m session
multipath -ll
systemctl status multipathd iscsid
dmesg -T | tail -100
ls -l /dev/disk/by-path/ | head

问题节点特征非常扎眼:

  • iscsiadm -m session 有会话,但部分 session 处于 reconnecting / free 抖动;
  • multipath -ll 出现 paths down 或 map 不完整,对应 LUN 的 dm 设备迟迟不出现;
  • dmesgconnection timed outscsi 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 抖动时,一次登录+扫盘本身就可能超过阈值,于是:

  1. 调用方 DeadlineExceeded → 重试;
  2. 存储侧可能已有半成品会话;
  3. 重试并发顶高,控制器工作队列变长;
  4. 更多 Pod 进入 FailedAttach/FailedMount 循环。

这与业务高峰叠加,形成“越重试越堵”的正反馈。

5. 用最小实验验证因果

  1. 将一个 Pending Pod 打污点容忍/节点选择器,强制调度到健康节点:数分钟内 Mount 成功、容器起来。
  2. 在问题节点上手动 multipath -r + 重启 multipathd 后再放回调度:同一 PVC 可完成 NodeStage。
  3. 只调大超时、不修节点:失败率下降但仍有长尾——说明超时是放大器,不是唯一根因。

结论成型:主因是问题节点 iSCSI/multipath 数据面僵死;次因是 CSI 超时与重试策略过激,把单点抖动放大成发布窗口级事故。

解决方案

紧急止血(发布窗口内)

  1. 给有状态工作负载临时避开坏节点
    • 给问题节点加 taint:storage.kubernetes.io/unschedulable=true:NoSchedule
    • 或在 STS/Deployment 上用 nodeAffinity 先躲开故障机架/故障节点池。
  2. 清理卡住的 VolumeAttachment(慎用,先确认存储侧无双挂)
    • 对长期 ATTACHED=false 且确认无业务在用的 attachment,按厂商手册做 force detach / 删除后重建,避免对象卡死阻塞队列。
  3. 节点侧快速恢复存储通道
1
2
3
4
5
systemctl restart iscsid multipathd
multipath -r
iscsiadm -m session -R
# 确认 by-path / dm 设备出现后再删掉失败 Pod 让控制器重试
kubectl delete pod order-sts-2 -n order --force --grace-period=0
  1. 临时放宽 CSI 超时(只作止血,回写变更单):将 attacher/provisioner 与 driver 超时调到能覆盖“慢扫盘”的上限,并限制并发 attach,防止打爆存储控制面。

按上述处理后,灰度副本在 10 分钟内全部 Ready,入口错误率回落。

根治动作

  1. 节点镜像与巡检:装机模板固化 iscsid/multipathd 开启、合理的 path checker、启动顺序;巡检脚本定期抓 multipath -ll 异常与 session 重连次数。
  2. CSI 超时与重试策略分层:慢 IO 场景给够 deadline;失败区分 可重试(超时/暂不可用)不可重试(参数错误/权限),避免无脑密集重试。
  3. 发布门禁:有状态服务发布前自动检查目标节点池的存储健康(session、multipath、CSI node plugin Ready),不健康节点先 taint 再发版。
  4. 可观测性:给 CSI Controller/Node 日志做 DeadlineExceeded / FailedMount 聚合告警,并对 VolumeAttachment 长时间未 ATTACHED 做对象级告警,别等业务 Pod 红了才知道。
  5. 网络侧:问题节点上联错包排查(光模块、线缆、交换机口),存储 VLAN QoS/隔离复核,避免“计算网正常、存储网亚健康”。

根因分析

本故障是 数据面僵死 × 控制面超时策略 的叠加:

层次 问题 作用
节点存储栈 iSCSI session / multipath path 异常,设备节点迟迟不出现 直接导致 NodeStage/NodePublish 失败
CSI 插件 Attach/Mount 超时偏紧、重试密集 把偶发慢变成队列堆积与大面积 DeadlineExceeded
发布与调度 新副本摊到坏节点,无存储健康门禁 把节点级问题放大成版本发布失败
观测 只盯应用/节点 CPU,不盯 VolumeAttachment 与 CSI 日志 延长误判时间,浪费黄金止血窗口

存储控制台“卷已创建”只能证明 Provision 成功,不能证明 Attach/Mount 成功;K8s 有状态故障排查必须按 CSI 生命周期逐段取证。

预防措施

  1. 装机与基线:计算节点若跑有状态负载,存储多路径与 iSCSI/FC 客户端纳入基线,开 conf 变更审计。
  2. 发布 Checklist:有状态发版前检查 CSI driver Ready、问题 VolumeAttachment 清零、目标节点 multipath 健康。
  3. 容量与超时联调:存储厂商 RTO 指标 vs CSI timeout 对齐压测;故意注入 path down,验证退避而非风暴重试。
  4. 节点池隔离:数据库类 STS 与批处理/高干扰负载分池,降低“邻居作业打爆存储会话”的概率。
  5. 演练:每季度做一次“拔存储链路/杀 multipathd”演练,验证 taint、驱逐、服务降级与回切文档是否可执行。
  6. 告警分层:应用延迟 / 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 从“黑盒重试”变成“可观测、可熔断、可演练”的常规运维对象。

使用 Hugo 构建
主题 StackJimmy 设计