Kubernetes 持久化存储与 CSI 生产排障完整指南
Kubernetes 存储问题通常跨越多个层次:Pod、PVC、PV、StorageClass、调度器、CSI 控制器、节点插件、操作系统挂载以及真正的存储后端。只盯着 Pod 事件,很容易在错误的层级反复重启。
本文建立一套从声明到数据面的排障路径,并覆盖动态供给、拓扑、扩容、快照、克隆、多可用区和故障恢复。
一、先理解完整链路
Pod
└─ PVC
└─ PV
└─ CSI volumeHandle
└─ 存储后端中的真实卷
控制面:
external-provisioner / attacher / resizer / snapshotter
节点面:
node-driver-registrar / CSI node plugin / kubelet / mount
常见对象职责:
| 对象 | 作用 |
|---|---|
| StorageClass | 指定 provisioner、参数、回收策略和绑定模式 |
| PVC | 用户对容量、访问模式和存储类的申请 |
| PV | 集群中的卷资源及后端句柄 |
| VolumeAttachment | 控制器记录卷与节点的附加关系 |
| CSINode | 节点可用 CSI 驱动及拓扑信息 |
| VolumeSnapshotClass | 定义快照驱动和删除策略 |
| VolumeSnapshot | 用户发起的快照请求 |
| VolumeSnapshotContent | 快照在集群与后端中的绑定对象 |
二、StorageClass设计
1. volumeBindingMode
对有可用区、机架或本地性限制的存储,通常应考虑:
volumeBindingMode: WaitForFirstConsumer
它会在 Pod 被调度时再结合节点拓扑创建卷,避免卷先被创建在错误可用区。Immediate 会在 PVC 创建后立即供给,适合后端真正全局可达的场景。
2. reclaimPolicy
Delete:删除 PVC/PV 后通常同时删除后端卷;Retain:保留后端数据,需要人工回收。
生产数据库、审计数据等场景应明确选择,不能依赖默认值。Retain 也不等于备份,它只是不自动删除原卷。
3. allowVolumeExpansion
allowVolumeExpansion: true
这只是允许 PVC 发起扩容,CSI 驱动、后端和文件系统还必须分别支持。
三、建立存储资产清单
kubectl get storageclass
kubectl get pvc -A
kubectl get pv
kubectl get csidriver
kubectl get csinode
kubectl get volumeattachment
建议资产表至少包含:
- 业务、命名空间和负责人;
- PVC、PV、StorageClass;
volumeHandle与后端卷 ID;- 容量、访问模式、卷模式;
- 可用区和节点拓扑;
- 快照与备份策略;
- RPO、RTO;
- 回收策略和数据保留要求。
没有后端卷 ID 映射表时,紧急恢复很容易误删或误挂载。
四、PVC一直Pending
1. 看事件
kubectl describe pvc -n <namespace> <pvc>
kubectl get events -n <namespace> --sort-by=.lastTimestamp
2. 判断绑定模式
如果 StorageClass 使用 WaitForFirstConsumer,PVC 在没有消费者 Pod 时保持 Pending 可能是正常行为。
3. 检查调度与拓扑
kubectl describe pod -n <namespace> <pod>
kubectl get csinode -o yaml
kubectl get nodes -L topology.kubernetes.io/zone
常见原因:
- Pod 的 nodeSelector、亲和性或污点容忍把可选节点限制为空;
- CSI 驱动未向节点注册拓扑键;
- 目标区域容量不足;
- 多个 PVC 的拓扑需求无法在同一节点满足;
- StorageClass provisioner 名称错误。
4. 检查 provisioner
kubectl get pods -A | grep -i csi
kubectl logs -n <csi-namespace> <controller-pod> -c csi-provisioner --since=30m
检查凭据、API 限流、后端配额、容量、参数和网络。
五、Pod卡在ContainerCreating
Pod 已调度但无法启动,通常进入 attach、stage 或 publish 阶段。
kubectl describe pod -n <namespace> <pod>
kubectl get volumeattachment
kubectl describe volumeattachment <name>
kubectl logs -n <csi-namespace> <node-plugin-pod> --all-containers --since=30m
journalctl -u kubelet --since '-30 min' --no-pager
常见错误与方向:
| 错误 | 排查方向 |
|---|---|
| Multi-Attach | 卷仍附加在旧节点,或访问模式不允许多节点写入 |
| timeout waiting for attach | 控制器、云 API、存储网络或后端任务超时 |
| mount failed | 文件系统、设备、权限、挂载参数 |
| rpc error | CSI 插件、凭据、连接或版本问题 |
| device busy | 残留挂载、进程占用或非正常断开 |
| no space left | 节点目录、inode、后端卷或配额不足 |
六、VolumeAttachment卡住
不要在不了解后端状态时直接删除 VolumeAttachment。
安全顺序:
- 确认旧 Pod 和旧节点是否仍访问数据;
- 确认后端卷是否真正从旧节点分离;
- 检查 CSI attacher 日志;
- 检查节点是否失联以及是否需要强制脱离;
- 按存储厂商流程处理 fencing;
- 后端状态一致后,再处理 Kubernetes 残留对象。
块存储被两个节点同时写入可能损坏文件系统,强制分离必须有明确的数据保护措施。
七、节点侧挂载排障
1. 设备与挂载
lsblk -f
findmnt
mount
df -hT
df -ih
2. 内核与文件系统
dmesg -T | tail -200
journalctl -k --since '-30 min' --no-pager
关注 I/O error、超时、只读重挂载、文件系统错误、multipath、iSCSI/NVMe-oF 连接和设备重置。
3. kubelet目录
只用于观察,不要手工删除:
find /var/lib/kubelet/plugins -maxdepth 3 -type d 2>/dev/null
find /var/lib/kubelet/pods -maxdepth 5 -type d -name volumes 2>/dev/null
如果存在残留挂载,先确认对应 Pod、设备和进程。手工 umount 前应 cordon 节点并评估业务。
八、卷扩容
1. 修改PVC
kubectl patch pvc <pvc> -n <namespace> \
-p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'
PVC 容量一般只能增大,不能直接缩小。
2. 观察控制面扩容
kubectl describe pvc -n <namespace> <pvc>
kubectl get pvc -n <namespace> <pvc> -o yaml
kubectl logs -n <csi-namespace> <controller-pod> -c csi-resizer --since=30m
3. 观察文件系统扩容
后端块设备扩大后,文件系统还需要由 kubelet、CSI 或人工完成扩容。检查 Pod 内部和节点侧实际容量,不要只看 PVC spec。
常见卡点:
- StorageClass 未允许扩容;
- 驱动只支持离线扩容;
- 后端配额不足;
- 文件系统类型不支持在线扩容;
- 节点插件未执行 NodeExpandVolume;
- PVC 长期处于
FileSystemResizePending。
九、快照不是完整备份
Kubernetes VolumeSnapshot 提供统一的快照接口,但快照质量取决于 CSI 驱动和后端。
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: database-before-change
spec:
volumeSnapshotClassName: production-snapshot
source:
persistentVolumeClaimName: database-data
必须明确:
- 是崩溃一致性还是应用一致性;
- 快照是否存放在同一存储系统;
- 删除策略是
Delete还是Retain; - 能否跨集群、跨账号、跨区域恢复;
- 是否支持多卷一致性组快照;
- 加密密钥是否一并可用。
数据库应在快照前执行冻结、刷盘或数据库原生备份协调。快照与源卷位于同一故障域时,不能替代异地备份。
十、从快照恢复
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: database-restore
spec:
storageClassName: production-block
dataSource:
name: database-before-change
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 200Gi
恢复演练必须验证:
- 新 PVC 能创建;
- Pod 能挂载;
- 文件系统能读取;
- 应用能够启动;
- 数据时间点符合预期;
- 业务校验通过;
- 原卷没有被误覆盖。
十一、访问模式容易误解
ReadWriteOnce 表示卷可被单个节点读写,不一定意味着只能被一个 Pod 使用。同一节点上的多个 Pod 是否可用,还取决于驱动和应用。
常见模式:
- RWO:单节点读写;
- ROX:多节点只读;
- RWX:多节点读写;
- RWOP:单个 Pod 读写。
访问模式不等于文件系统并发安全。即使后端支持 RWX,应用也必须支持多实例同时访问同一数据目录。
十二、Local PV与本地盘
本地盘性能高,但卷与节点绑定。必须设计:
- 节点故障后的数据恢复;
- 替换磁盘与重新调度;
- 拓扑感知;
- 容量碎片;
- 节点退役;
- 备份和副本机制。
Local PV 不应被当作普通网络块存储使用。数据库可依靠自身复制获得高可用,但要演练节点永久丢失后的重建流程。
十三、性能诊断
存储性能不能只看吞吐量。至少区分:
- IOPS;
- 带宽;
- 平均与 P95/P99 延迟;
- 队列深度;
- 读写比例;
- 随机与顺序访问;
- fsync 延迟;
- 后端降级或重建状态。
节点侧可结合:
iostat -xz 1
pidstat -d 1
lsblk -o NAME,KNAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS
容器内的慢 I/O 可能来自文件系统、块设备、网络、存储后端、限速策略或邻居负载。不要只根据 await 单一指标下结论。
十四、监控与告警
建议覆盖:
- PVC Pending 时间;
- CSI RPC 错误率和延迟;
- provision、attach、mount、resize、snapshot 成功率;
- VolumeAttachment 长时间未完成;
- 节点插件不可用;
- 后端容量、配额、延迟和错误;
- 卷吞吐、IOPS、延迟和队列;
- 快照失败和备份过期;
- 孤立 PV、无主快照和长期 Terminating 对象。
告警必须关联命名空间、PVC、PV、节点、StorageClass、volumeHandle 和后端卷,才能缩短定位时间。
十五、典型故障:节点宕机后卷无法挂到新节点
现象
- Pod 已被调度到新节点;
- 事件显示 Multi-Attach;
- 旧节点 NotReady;
- 后端仍显示卷附加在旧主机。
处理
- 确认旧节点是否确实失联且不会恢复写入;
- 对应用执行 fencing,防止双写;
- 检查 VolumeAttachment 和 CSI attacher;
- 按后端流程强制分离旧连接;
- 确认设备已从旧主机释放;
- 允许新节点重新附加;
- 检查文件系统并验证应用数据;
- 复盘节点失联检测和自动分离策略。
跳过 fencing 直接强制挂载,是最危险的处理方式。
十六、生产检查清单
设计
- [ ] StorageClass 的绑定和回收策略明确;
- [ ] 多可用区使用拓扑感知;
- [ ] 驱动、Kubernetes 与 sidecar 版本兼容;
- [ ] 关键卷有快照、应用备份和异地副本;
- [ ] RPO/RTO 已通过恢复演练验证。
日常
- [ ] PVC、PV、VolumeAttachment 无长期异常;
- [ ] CSI 控制器和节点插件健康;
- [ ] 后端容量和配额有余量;
- [ ] 快照与备份按计划完成;
- [ ] volumeHandle 与资产台账一致。
故障
- [ ] 先判断失败发生在供给、调度、附加、挂载还是应用层;
- [ ] 强制分离前已确认旧节点不会继续写入;
- [ ] 不直接删除 kubelet 插件目录和挂载目录;
- [ ] 恢复后已验证文件系统和业务一致性。
参考资料
- Kubernetes:Persistent Volumes
https://kubernetes.io/docs/concepts/storage/persistent-volumes/ - Kubernetes:Storage Capacity
https://kubernetes.io/docs/concepts/storage/storage-capacity/ - Kubernetes:Volume Snapshots
https://kubernetes.io/docs/concepts/storage/volume-snapshots/ - Kubernetes CSI Developer Documentation
https://kubernetes-csi.github.io/docs/