SStorpath
原理预计 35 分钟

K8s 存储模型:PV、PVC、SC 与 CSI

容器时代的存储接口层,运维和开发在这里分工。

学完这节你能做到

  • 说清 PV / PVC / StorageClass 三者的职责边界
  • 解释 CSI driver 的组件构成与调用链
  • 排查 PVC 一直 Pending 的常见原因

容器时代的存储接口层

容器里的文件是临时的:容器崩溃或重启,容器内创建和修改的文件就消失了。K8s 用 Volume 解决这个问题,并把它分成两大类。

Volume
├─ Ephemeral Volume 临时卷(与 Pod 同生命周期)
│   ├─ emptyDir      —— 用宿主机系统盘或内存
│   └─ configMap / secret —— 把配置挂进容器
│
└─ Persistent Volume 持久卷(超出 Pod 生命周期)
    ├─ PV  —— 集群里的一块存储,管理员创建
    ├─ PVC —— 用户对存储的申请
    └─ StorageClass —— 动态创建 PV 的模板

PV / PVC / SC:三者的职责边界

理解这三者最好的方式是看谁创建它、谁使用它

对象谁创建代表什么
PV管理员(或由 SC 自动创建)真实存在的一块存储后端
PVC应用开发者"我要一块 20G 的存储,用 xxx 类型"
StorageClass管理员创建 PV 的模板:用哪个驱动、什么参数

手动创建和管理大量 PV 非常繁琐,所以有了 动态供应(Dynamic Provisioning):管理员只定义 StorageClass,用户创建 PVC 时 K8s 自动调用驱动创建对应的 PV 并绑定。

管理员视角                          用户视角
─────────                          ────────
部署 CSI 驱动          →
创建 StorageClass      →           创建 PVC,指定 storageClassName
                                        ↓(自动发生)
                                   K8s 通过 SC 调用 CSI 创建 PV 并绑定
                                        ↓
                                   Pod 的 volumes 引用 PVC,
                                   通过 volumeMounts 挂到容器路径

accessModes:最容易配错的字段

模式缩写含义谁支持
ReadWriteOnceRWO单节点读写块存储(RBD、云盘、local)
ReadOnlyManyROX多节点只读多数
ReadWriteManyRWX多节点读写文件存储(CephFS、GPFS、NFS)
ReadWriteOncePodRWOP单 Pod 读写较新特性,比 RWO 更严格
×RWO 不是「只能一个 Pod 用」

RWO 的准确含义是单节点读写。同一个节点上的多个 Pod 是可以共享一个 RWO 卷的(因为挂载点在节点上)。

这经常导致一类困惑:本地测试时两个 Pod 恰好调度到同一节点,共享 RWO 卷工作正常;上生产后 Pod 分散到不同节点,就开始报 Multi-Attach error

要严格限制单 Pod 使用,用 ReadWriteOncePod。要真正多节点共享,必须用支持 RWX 的文件存储。

回收策略与卷绑定模式

reclaimPolicy: Delete        # PVC 删除后,PV 和后端存储一起删除
# reclaimPolicy: Retain      # PVC 删除后保留 PV 和数据,需手工清理

volumeBindingMode: Immediate            # PVC 创建后立刻创建并绑定 PV
# volumeBindingMode: WaitForFirstConsumer # 等到有 Pod 使用时再创建
!生产环境的重要数据用 Retain

Delete 是默认值,也是最方便的——但它意味着误删一个 PVC 就直接销毁了后端数据,没有回收站。

重要数据的 StorageClass 应该配 Retain:PVC 删除后 PV 变成 Released 状态,数据还在,确认无误后再手工删除。多花的一步操作,换来的是一次误删事故的兜底。

WaitForFirstConsumer 则用于本地存储或拓扑相关的存储:不知道 Pod 会调度到哪个节点前就创建 PV,可能创建在错误的可用区/节点上导致 Pod 无法调度。

CSI 架构

CSI(Container Storage Interface)是标准化接口,把第三方存储和 K8s 解耦。一个 CSI 驱动通常包含两部分:

controller(Deployment,通常 2 副本)
  ├─ external-provisioner   ← 处理创建/删除卷
  ├─ external-attacher      ← 处理 attach/detach
  ├─ external-resizer       ← 处理扩容
  ├─ external-snapshotter   ← 处理快照
  └─ CSI driver 本体

node(DaemonSet,每个节点一个)
  ├─ node-driver-registrar  ← 向 kubelet 注册
  └─ CSI driver 本体        ← 执行实际的 mount/umount

排障时的关键认知:创建卷的问题看 controller,挂载卷的问题看 node

# 卷创建不出来(PVC Pending)→ 看 provisioner
kubectl -n <ns> logs deploy/<csi-controller> -c csi-provisioner

# 卷创建了但 Pod 挂不上(Pod ContainerCreating)→ 看对应节点的 node plugin
kubectl -n <ns> logs <csi-node-pod-on-that-node> -c csi-<driver>

演练:PVC 一直 Pending

root@k8s-master1
目标 0/5
  1. 1.确认 PVC 的状态和它请求的 StorageClass
  2. 2.看 Events,让 K8s 直接告诉你卡在哪
  3. 3.确认 StorageClass 引用的 secret 与集群参数
  4. 4.确认 secret 里的用户名
  5. 5.在 Ceph 侧核对该用户的权限范围
模拟终端:一个 PVC 创建了 10 分钟仍是 Pending,业务 Pod 起不来。
输入 goals 看目标,hint 要提示。
[root@k8s-master1 ~]#
help 查看用法 · goals 看目标 · hint 要提示 · ↑↓ 翻历史

结论client.kubernetes 的 OSD 权限被限定在 pool=kube,而 StorageClass 里指定的池是 rbd。修复方式是二者对齐:

# 方式一:放开用户权限到正确的池
ceph auth caps client.kubernetes \
  mon 'profile rbd' \
  osd 'profile rbd pool=rbd' \
  mgr 'profile rbd pool=rbd'

# 方式二:把 StorageClass 的 pool 改成 kube

PVC Pending 排查清单

按顺序走,绝大多数问题在前三步就能定位:

检查项命令常见原因
1. PVC 事件kubectl describe pvc信息最直接,先看这里
2. StorageClass 存在吗kubectl get sc名字写错、SC 不存在
3. provisioner 日志kubectl logs <csi-controller>权限、网络、参数错误
4. secret 是否正确kubectl get secret -o yamlkey 名字错、namespace 错、内容过期
5. 后端存储健康ceph -s / mmhealth存储侧真的有问题
6. accessMode 是否支持对比 SC 与 PVC用 RBD 要求 RWX
7. 容量与配额ceph df / mmlsquota后端没空间了
8. 拓扑约束describe 里的调度信息WaitForFirstConsumer + 无可用节点
检查点单选

两个 Pod 要共享同一份数据。PVC 用了 ceph-csi-rbd 的 StorageClass,accessMode 写 ReadWriteMany。会发生什么?

检查点单选

PVC Pending,describe 显示 provisioner 报 Permission denied。最该往哪个方向查?

检查点单选

关于 reclaimPolicy,生产环境重要数据应该怎么配?

这节课的落点

  • PV 是实物、PVC 是申请、SC 是模板;动态供应把管理员从手工建 PV 中解放出来
  • RWO 是单节点而非单 Pod,跨节点共享必须用支持 RWX 的文件存储
  • 重要数据用 reclaimPolicy: Retain
  • CSI 分 controller(创建卷)和 node(挂载卷)两部分,按症状选择看哪边的日志
  • PVC Pending 的排查顺序:describe 事件 → SC → provisioner 日志 → secret → 后端
  • Permission denied 通常是 caps 限定的池与 SC 的 pool 不一致

延伸资料

  • ·k8s-in-actionstorage/README.md
  • ·k8s-in-actionstorage/local-storage/