Skip to content

2. K8s 存储体系:PV/PVC/CSI

本章目标

  • 讲清 PV/PVC/StorageClass 三者关系与动态供给流程
  • 理解 CSI 的三段式架构:谁创建卷、谁挂载卷
  • 掌握 PVC Pending / Pod ContainerCreating 两大排障路径

2.1 从"容器无状态"说起

容器文件系统随 Pod 销毁 —— 持久化需求推动卷的演进:

text
emptyDir    Pod 内临时共享,Pod 死即亡

hostPath    直通宿主机目录,简单但绑定节点、无隔离

PV/PVC      集群级持久卷资源,独立于 Pod 生命周期

2.2 三个 API 对象

text
Pod ──volumeMounts──▶ PVC(我要 100Gi,RWO,class=block-nvme)
                        │ 绑定

                     PV(一个真实卷:RBD image / CephFS 子目录 / NFS export)
                        ▲ 动态创建
        StorageClass(provisioner=ceph-csi-rbd,参数=pool/副本策略…)
  • PVC:应用的"求购单",与具体实现解耦 —— 应用写 PVC,换存储后端不改应用
  • PV:库存卷,生命周期独立(PVC 删了,PV 是否删由 reclaimPolicy:Delete/Retain
  • StorageClass:动态供给的"配方" —— provisioner 指向哪个 CSI、什么参数;volumeBindingMode: WaitForFirstConsumer 让卷在 Pod 调度时才创建(尊重拓扑)

AccessModes 速查(RWO/RWX 的物理含义在第 4 章与块/文件接口一一对应):

模式含义典型后端
RWO ReadWriteOnce单节点读写RBD、云盘
ROX ReadOnlyMany多节点只读快照恢复的共享只读
RWX ReadWriteMany多节点同时读写CephFS、NFS、GPFS

2.3 CSI:存储的驱动标准

CSI(Container Storage Interface)把 K8s 与存储驱动解耦 —— K8s 只定义接口,任何存储实现同一套 gRPC:

text
┌─ Controller 段(集群级,Deploy)────────────────┐
│ CreateVolume / DeleteVolume / ControllerPublish │ ← 收到 PVC 后由 external-provisioner 触发
└────────────────────────────────────────────────┘
┌─ Node 段(每节点,DaemonSet)───────────────────┐
│ NodeStageVolume(格式化/全局挂载)                │
│ NodePublishVolume(bind-mount 进容器)           │ ← kubelet 在 Pod 起来时逐节点调用
└────────────────────────────────────────────────┘

一次动态供给的完整时序(排障时按图索骥):

text
PVC 创建 → SC 的 provisioner 调 CSI CreateVolume
        → 存储后端建卷(如 RBD create image)
        → PV 对象生成并与 PVC 绑定
Pod 调度 → kubelet 调 NodeStageVolume(attach+格式化)
        → NodePublishVolume(挂进容器)
        → Pod Running

快照(VolumeSnapshot)、扩容、拓扑调度都是这套体系的扩展 CRD —— 概念上与 LVM/RBD 快照同源,只是操作入口变成了 kubectl。

2.4 两大排障路径(存储工单大头)

路径一:PVC 一直 Pending

bash
kubectl describe pvc <name>     # 看 Events —— 线索永远在这里
kubectl -n <csi-ns> logs <provisioner-pod>   # provisioner 为什么没建卷

常见原因:SC 名字写错(本章 Lab 会故意踩)、池不存在/权限不足、topology 不满足、容量不合法。

路径二:Pod 一直 ContainerCreating

bash
kubectl describe pod <name>     # Events 里常见 "fail attach / fail mount"
journalctl -u kubelet | tail    # node 段 CSI 日志
kubectl -n <csi-ns> logs <node-plugin> -c csi-node

常见原因:客户端连不上存储集群(网络/密钥)、map 失败(RBD 特有的 kernel 模块/锁)、格式化超时。

排障纪律与第一篇方法论同源:先定范围(控制面 or 数据面),再看证据链(Events → provisioner 日志 → node 日志),不要一上来就重启。

本章小结

  • PVC 是求购单、PV 是库存、SC 是配方;应用只认 PVC
  • CSI 三段式:Controller 建卷、Node 段挂卷;排障按"Pending=控制面、ContainerCreating=节点面"分流
  • RWO↔块存储、RWX↔文件存储 —— AccessMode 是接口选型在 K8s 的投影

自测

Quiz一个 Deployment 需要多个副本 Pod 同时读写同一份数据,PVC 应该用什么 AccessMode + 后端组合?
QuizPod 卡在 ContainerCreating,PVC 已 Bound。最应该先看哪里?
🧪Lab · 动态供给全链路演练进阶
⏱ 90 分钟🖥 一套 K8s 集群 + 任一 CSI(local-storage 起步)
1. 部署 local-storage 或任一 CSI,创建 StorageClass(记得 `volumeBindingMode: WaitForFirstConsumer`)。 2. 创建 PVC + Pod 写数据;`kubectl get pv,pvc -o wide` 观察 PV 生成与绑定事件,记录 Events 时序。 3. 对卷做在线扩容(SC 需 `allowVolumeExpansion: true`),验证文件系统同步增长。 4. 故障注入:把 SC 的参数故意写错(如不存在的 pool),观察 PVC Events 与 provisioner 日志,从两个证据源定位失败原因。 5. **验收标准**:能画出从 PVC 创建到 Pod Running 的时序图(含各组件与日志位置),并演示一次完整的 Pending 排障。
📎 参考手册 ↗

上一章 · 下一章:3. Rook →