← 返回 Atlas
Kubernetes

PV、PVC 与 StorageClass

每个 Kubernetes 新手都问:容器重启数据还在吗?Pod 删了数据还在吗?默认答案都是不在——除非你用卷。而卷这一套设计,是集群里最像『云』的部分:PV 是机房里的磁盘,被登记成资产;PVC 是申请单,声明『我要 5G、可读写一块』;StorageClass 是自动贩卖机,你递出 PVC,它当场卷一块 PV 给你。三样加起来,存储就从『管理员手动挂盘』变成了『开发者自助申请』。

k8s · 存储

从 hostPath 到 PV:存储为什么必须抽象一层

最早只有 emptyDir 和 hostPath:emptyDir 随 Pod 生灭(适合缓存),hostPath 直接挂宿主机目录(只在本机有意义,Pod 漂移就丢)。存储要跟 Pod 走,就得把『盘』和『机器』解耦。PV(PersistentVolume)就是这层抽象:它描述『集群里有一块 5G 盘,由 NFS/云盘/local 供给』,PV controller 把它登记成集群资产。Pod 不再直接说『挂 /mnt』,而是声明『给我一块盘』——具体哪块,由 PV 层决定。

PV 与 PVC 的绑定:一场严格的双向选择

PVC(PersistentVolumeClaim)是需求侧:声明 size 和 accessMode(ReadWriteOnce 单节点读写 / ReadWriteMany 多节点 / ReadOnlyMany)。PV 是供给侧:标明自己能提供的容量和 accessMode。PVC 创建时,PV controller 在候选池里找一个『容量够、accessMode 匹配』的 PV 绑定,绑定后这个 PV 被占用。没有合适的 PV 就 Pending。accessMode 不匹配是绑定失败第一大原因——RWO 的盘,两个副本 Pod 想都挂,就是不绑。

StorageClass:把『手动配盘』变成『自动贩卖机』

没有 StorageClass 的时代,管理员得预先建好 PV,开发者等盘。SC 把动态供给接进来:SC 定义一个 provisioner(通常是云厂商的 CSI 驱动)+ 参数(磁盘类型 io1/gp3、大小上限)。PVC 声明 storageClassName: standard,provisioner 收到请求,当场调云 API 建一块盘、生成 PV、完成绑定——全程无人值守。没写 storageClassName 的 PVC 用默认 SC(default storageclass);写 storageClassName: "" 表示显式拒绝动态供给,只等手动建好的 PV。

CSI:所有厂商挤进同一根管子

早期每个云厂商的卷插件都写死在 kubelet 里(in-tree),加一个新厂商等于改内核。CSI(Container Storage Interface)2017 年引入:存储厂商只要实现 CSI 驱动(一个符合规范的 gRPC 服务),kubelet 和外部 provisioner 统一调用它。从此 AWS/Azure/GCE/NFS/本地盘全都走同一根管子,SC 的 provisioner 名就是 CSI driver 名。in-tree 插件在 1.26+ 基本全部搬走。

实战示例

minikube 上建一个 PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 5Gi
  storageClassName: standard
Apply 后 kubectl get pvc 变 Bound,再看 kubectl get pv——minikube 的 hostpath provisioner 已经现场建了一块 5G 的 PV 并绑定。Pod 里 volumes 引用这个 PVC,挂载后 df -h 就是 5G。全程没手动建过任何盘,这就是 StorageClass 的动态供给。

子关键词

PV 集群里的存储资产
PersistentVolume,描述一块具体盘的容量、accessMode、供给方(NFS/云盘/local)与回收策略。由 PV controller 登记管理。
PVC 应用的申请单
PersistentVolumeClaim,声明容量与 accessMode,创建时绑定一块匹配的 PV。Pending 说明没找到合适的盘。
StorageClass 动态供给的自动贩卖机
定义 provisioner 与参数,PVC 声明 storageClassName 就自动建盘绑 PV。default SC 兜底没写名字的 PVC。
AccessMode RWO / RWX / ROX
ReadWriteOnce 单节点读写、ReadWriteMany 多节点、ReadOnlyMany 只读。绑定和挂载都受它约束。
回收策略 Retain / Delete / Recycle
PVC 删除后 PV 怎么办:Retain 保留(手动清)、Delete 连底层盘一起删、Recycle 清空复用(已废弃)。
CSI 驱动 存储厂商统一插口
Container Storage Interface,厂商实现 gRPC 服务接入 kubelet,SC 的 provisioner 名即 CSI driver 名。
topology 感知 卷和节点的区域绑定
云盘创建在特定 zone,调度器要知道盘在哪才能把 Pod 调度过去,避免拿不到盘的 Pending。

衍生角度

设计模式:资源-请求-绑定

PV/PVC 是『供给资源-需求声明-自动绑定』三元组,和云上 EBS、甚至 CPU 请求是同一模式:需求方不感知具体资源,只管声明。

概念拆解:PV(供)vs PVC(求)vs SC(自动化)

PV 描述有什么盘,PVC 声明要什么盘,SC 定义怎么自动造盘。三层各管一段,排错先问自己是哪一层卡住了。

风格架构:声明与控制解耦

PVC 只声明需求,底层是 NFS/云盘/本地由 PV 和 SC 决定,应用代码不感知。这是控制面与数据面分离在存储上的体现。

原理分析:为什么 accessMode 绑定严格

RWO 的盘同一时刻只能被一个节点挂载,多副本 Pod 抢一块会数据损坏。所以绑定必须按 accessMode 过滤,宁可 Pending 也不乱绑。

实际用法与坑

  • accessMode 别高估:RWO 只保证单节点,多副本读要 RWX 或每个副本独立 PVC。
  • Retain 策略的盘,删 PV 不会删底层:你得手动清,否则同一块底层盘再绑会数据错乱。
  • PVC 一直 Pending:八成是 storageClassName 没对上,或 SC 的 provisioner 驱动没装。kubectl describe pvc 看 Events。
  • accessMode 是绑定后不可变字段:想改只能重建 PVC,别硬试。
  • 云厂商盘和节点区域绑定:Pod 被调度到别的 zone 会拿不到盘,靠 topology 感知的 SC 解决。

下一步动手做

minikube 上建一个默认 SC 的 PVC,观察 PV 自动出现并绑定;删掉 PVC 看回收策略怎么处理;再手动建一个 PV + 无 SC 的 PVC 绑定,体会 Retain 策略下清理底层盘的负担;最后读 CSI 驱动清单,认识一个云厂商的 driver。

相关书籍

Kubernetes in Action

存储章节,PV/PVC/StorageClass 的字段与动态供给流程,配 CSI 背景一次讲清。

关联关键词