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 · 动态供给全链路演练进阶
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 排障。
📎 参考手册 ↗