Kubernetes 存储 PV PVC 与 StorageClass
GreySuit
11 分钟阅读
Kubernetes 存储 PV PVC 与 StorageClass
理解 K8s 存储只要抓住一条主线:Pod 不直接碰存储后端,它通过 PersistentVolumeClaim 申请容量,集群用 PersistentVolume 满足它,而 StorageClass 决定这块盘“怎么来、什么规格、删了怎么办”。搞混这三层,就会出现“明明申请了 10G 却挂不上”或者“删了 PVC 数据神秘消失”。存储是所有有状态工作负载的地基,地基理解错,上层再花哨也会塌。先把三层关系理顺,再谈性能和高可用,顺序反了就会在各种 Pending 和误删里打转。
三层职责拆分
| 对象 |
角色 |
由谁创建 |
生命周期 |
| PV |
集群里一块已就绪的存储,是资源实体 |
管理员或控制器 |
独立于 Pod |
| PVC |
workload 对存储的“需求声明” |
应用开发者 |
跟随使用方 |
| StorageClass |
动态供给的模板,描述存储类型与回收方式 |
集群管理员 |
集群级 |
Pod 只引用 PVC 名字,解耦了“用存储”和“造存储”。这个解耦让同一份应用定义可以跑到不同存储后端上,只要 PVC 能被满足。开发者不必知道盘在哪、是什么类型,只声明“我要 10G、能读写”,剩下的交给集群。
PV 的两种来源
静态供给是管理员预先创建好一批 PV:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-01
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs:
server: 10.0.0.20
path: /exports/data
动态供给不需要预建 PV。只要集群装了对应驱动并配置了 StorageClass,PVC 一提交就自动创建 PV 并绑定。生产中绝大多数场景走动态供给,避免人工开卷。动态供给出来的 PV 其 reclaimPolicy 继承自 StorageClass,这点常被忽略,以为手建 Retain 就万事大吉,结果用的还是 Delete 类 SC,删 PVC 时盘照样没。
PVC 描述需求而非位置
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: fast-ssd
绑定逻辑是“满足且尽量小”:集群找 capacity ≥ 10Gi、accessMode 匹配、storageClassName 一致的 PV。accessMode 不匹配会一直 Pending。如果 PVC 不写 storageClassName,会绑定到集群的默认 StorageClass(若有的话);写空字符串 "" 则明确不用动态供给、只在已有静态 PV 里找,这个区别决定了是“自动造盘”还是“只能捡现成的”,写错直接导致状态卡在 Pending。
StorageClass 决定盘的脾气
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: csi.example.com
parameters:
type: gp3
iops: "3000"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy 是雷区:设为 Delete 时,PVC 一删,底层盘跟着没;设为 Retain 时盘保留,需管理员手动回收,适合数据库这种不能误删的数据。allowVolumeExpansion: true 才允许后续在线扩容。volumeBindingMode: WaitForFirstConsumer 会延迟绑定,等 Pod 真正调度到某节点后才在该节点所在拓扑创建盘,对受拓扑约束的存储(比如只在特定区有的后端)很关键。
CSI 与存储插件模型
动态供给背后是 CSI 驱动,它由一组 sidecar 协作:external-provisioner 监听 PVC 事件调用 CreateVolume 造盘;external-resizer 处理扩容;external-snapshotter 处理快照;external-attacher 处理挂载。排查“盘没创建出来”时,先看 provisioner 的日志有没有收到事件,而不是只看 PVC 状态,能少绕很多路。理解这个分工,才知道该去哪个组件查问题。
原始块卷与卷模式
多数应用要的是文件系统,但数据库这类负载有时想直接管理裸块设备:
spec:
volumeMode: Block
volumeMode 默认是 Filesystem,配错成 Block 而应用按文件系统挂载,会启动失败;反之想要裸设备却用默认 Filesystem,应用读到的也不是期望的块设备。模式不匹配是隐蔽的挂载失败来源,describe 里往往只报挂载错误,要回头核对 PVC 和 PV 的 mode 是否一致。
拓扑与延迟绑定深入
WaitForFirstConsumer 的意义在于避免错位:若存储后端只在特定可用区有,提前绑定会让调度器先把 Pod 放到无盘的节点,再发现建不了盘。延迟绑定让调度器先确定节点、再在该节点所在拓扑建盘,绑定就稳了。但代价是 PVC 在 Pod 起来前一直 Pending,这是正常的,别误判成故障去删重建,那样反而循环卡住。
本地存储 local PV
spec:
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: ["node-1"]
local PV 把名字和具体路径、节点绑死,节点故障即数据不可迁移,只适合有副本的分布式存储或明确能丢的缓存。用之前要想清楚:这块盘挂了,业务能不能扛。盲目用 local PV 存单副本数据库,节点坏一次就是一次事故。
subPath 与多挂载
一个卷要挂到容器里多个子路径,用 subPath 避免互相覆盖:
volumeMounts:
- name: data
mountPath: /app/logs
subPath: logs
不指定 subPath,挂载会覆盖整个 mountPath 目录下的原有内容,这是容器里“配置被盘盖掉”的常见原因。多容器共享一个卷时,subPath 能各自占一块,互不干扰。
容量申请与实际
PVC 申请的 storage 是“期望上限”,底层盘按 provisioner 参数可能向上取整到特定步长,别假设“申请 10G 就是精确 10G”。看 PV 的实际 capacity 才是真实给到的。容量规划时按实际给到的算,而非按申请值算,否则临近上限才发现不够。
扩容的边界
PVC 扩容取决于两件事:StorageClass 开 allowVolumeExpansion,以及底层驱动支持文件系统在线扩。扩完 PVC 的 capacity 变了,但 Pod 内 df 不马上变,要在节点上执行文件系统扩容(多数 CSI 会自动做,但需 Pod 重新挂载或触发)。经验上,缩容几乎不支持,规划容量时宁大勿小。扩容前还要确认应用能识别到更大的文件系统,否则扩了盘应用也用不上,白扩。
快照与克隆
现代 CSI 支持 VolumeSnapshot,对盘做时间点副本:
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: db-snap
spec:
volumeSnapshotClassName: csi-snap-class
source:
persistentVolumeClaimName: data-pvc
快照可用于备份、克隆新 PVC 做测试。注意快照占用后端空间,且不是所有驱动都支持,要先确认 provisioner 的能力清单,别假设一定可用。快照没设保留策略,时间一长占满后端配额,反而影响正常卷供给,要配定期清理。
踩坑实录
- 删 PVC 前先确认 reclaimPolicy。测试库用了 Delete 策略,PVC 一删,三天数据找不回。生产库一律 Retain 加定期快照。
- 本地盘(hostPath / local PV)绑定到具体节点,Pod 漂移会挂不上,必须配合节点亲和或 DaemonSet 思路。
- 大量小文件写入共享 NFS 时,元数据处理是瓶颈,量级上比块存储慢一到两个数量级,别把高 IOPS 需求压到 NFS。
- PV 状态是 Released 而非 Available 时,多半是 PVC 删了但 PV 还记着旧绑定,需手动清
spec.claimRef 才能复用。
- 默认的 Delete 回收在集群升级、误操作
kubectl delete pvc 时都是数据黑洞,权限上要对 PVC 删除做管控。
- StorageClass 一旦被 PVC 引用就难以改名,初期命名要稳定,避免后期想换驱动却牵一发动全身。
WaitForFirstConsumer 模式下,PVC 在 Pod 起来前一直 Pending 是正常的,别误判成故障去删重建。
- 快照没设保留策略,时间一长占满后端配额,反而影响正常卷供给,要配定期清理。
- volumeMode 配错导致挂载失败,describe 只报挂载错误,要回头核对 PVC 和 PV 的 mode 是否一致。
排查存储问题先看 kubectl describe pvc 的 Events,九成的 Pending 原因都在那里写明,比猜快得多。
浏览 0
点赞 0
评论 0