前言
容器生来无常,Pod 一删数据就没了。K8s 存储体系要回答:怎么给易逝的 Pod 一个持久的家。这篇讲透 PV/PVC/StorageClass 三层抽象和实战用法。
一、问题与分层
开发者视角: "我要 10Gi 读写快的存储" ← PVC(声明)
│ 谁来满足?
集群管理员视角: "我有一堆 NFS/云盘供给" ← PV(资源) + StorageClass(供给方式)
│ 挂给谁?
Pod 视角: volumeMounts 挂载点 ← Volume(使用)
为什么隔一层 PVC? 开发者不应该关心"存储在哪台 NFS/哪种云盘"——就像申请内存不需要指定物理内存条编号。解耦需求与供给。
二、临时与半永久卷
# emptyDir: Pod 的"临时便签"(Pod 删除即消失, 崩溃重启仍在)
volumes:
- name: scratch
emptyDir: {}
# 用途: 缓存、中间产物、多容器共享文件
# hostPath: 用宿主机目录(危险但实用)
volumes:
- name: node-log
hostPath: {path: /var/log, type: Directory}
# 用途: DaemonSet 采集节点日志(node-exporter/promtail)
# ⚠️ Pod 会漂移 + 数据跟节点走, 业务数据绝不用它
三、PV 与 PVC:静态供给
管理员预创建 PV(以 NFS 为例):
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-data
spec:
capacity: {storage: 50Gi}
accessModes: ["ReadWriteMany"] # RWX: 多节点读写
persistentVolumeReclaimPolicy: Retain # PVC 删除后 PV 保留(防误删数据!)
nfs:
server: 10.0.2.100
path: /export/data
开发者提需求:
apiVersion: v1
kind: PersistentClaim
metadata:
name: app-data
spec:
accessModes: ["ReadWriteMany"]
resources:
requests: {storage: 50Gi}
storageClassName: "" # 绑定静态 PV
Pod 使用:
spec:
containers:
- name: app
volumeMounts: [{name: data, mountPath: /data}]
volumes:
- name: data
persistentVolumeClaim: {claimName: app-data}
accessModes:
| 模式 | 缩写 | 含义 |
|---|---|---|
| ReadWriteOnce | RWO | 单节点读写(云盘类) |
| ReadOnlyMany | ROX | 多节点只读 |
| ReadWriteMany | RWX | 多节点读写(NFS/CephFS/文件存储) |
| ReadWriteOncePod | RWOP | 单 Pod 读写(更严格) |
回收策略:Retain(保留,需手动清)/ Recycle(已废弃)/ Delete(动态供给默认,删 PVC 连底层盘一起删——慎用于重要数据)。
四、StorageClass:动态供给(主流玩法)
不用手动建 PV,PVC 一提,存储插件自动创建底层资源:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: diskplugin.csi.tencent.com # CSI 插件(云厂商内置)
parameters:
type: CLOUD_SSD
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer # 等首个 Pod 调度后再建盘(同可用区!)
---
apiVersion: v1
kind: PersistentClaim
metadata:
name: db-data
spec:
storageClassName: fast-ssd
accessModes: ["ReadWriteOnce"]
resources:
requests: {storage: 100Gi}
kubectl get pvc
# NAME STATUS VOLUME CAPACITY STORAGECLASS
# db-data Bound pvc-4f2a... 100Gi fast-ssd
kubectl get pv | head # 自动创建的 PV
volumeBindingMode 的坑:Immediate 会立刻建盘——若 Pod 与盘不在同可用区就挂不上;用 WaitForFirstConsumer 让存储跟着 Pod 走。
五、扩容与快照
# 在线扩容(存储类需 allowVolumeExpansion: true)
kubectl patch pvc db-data -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'
# 快照(需安装 CSI Snapshotter)
kubectl apply -f snapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: db-snap-20250802
spec:
volumeSnapshotClassName: csi-snapclass
source:
persistentVolumeClaimName: db-data
六、StatefulSet:有状态的正解
Deployment 的 Pod 是无面人(名字随机、存储共享);StatefulSet 给每个副本稳定的身份与独立存储:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql # 配套 headless service
replicas: 3
selector: {matchLabels: {app: mysql}}
template:
metadata: {labels: {app: mysql}}
spec:
containers:
- name: mysql
image: mysql:8.0
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates: # ★ 每个副本自动生成独立 PVC
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources: {requests: {storage: 100Gi}}
kubectl get pvc
# data-mysql-0 Bound ... # mysql-0 专属盘
# data-mysql-1 Bound ... # mysql-1 专属盘
# data-mysql-2 Bound ...
# Pod 名字稳定 + DNS 稳定:
kubectl run t --rm -it --image=busybox -- nslookup mysql-0.mysql.default.svc
适用:数据库主从、Kafka、ES、ZooKeeper 这类"每个节点有自己身份和数据"的应用。
七、数据保护清单(血泪法则)
- PVC 删除 ≠ 数据安全:确认 reclaimPolicy,重要数据用 Retain 或快照先行
- 云盘每天快照策略 + 异地(对象存储)备份
- 数据库别只靠快照——逻辑备份(mysqldump/xtrabackup)+ 物理快照双保险
- 测试恢复!没演练过的备份等于没有备份
- 监控 PVC 使用率(Prometheus:
kubelet_volume_stats_used_bytes),别等写满
八、常见坑
| 现象 | 原因 |
|---|---|
| PVC 一直 Pending | 没匹配的 PV(静态)/ provisioner 没装 / 容量/模式不匹配 |
| Pod 调度不了 + 盘在别的可用区 | volumeBindingMode 用 WaitForFirstConsumer |
| 多 Pod 挂不上同一 PVC | RWO 只支持单节点;要共享用 RWX(NFS/文件存储) |
| 删了 PVC 数据没了 | Delete 回收策略生效——重要数据 Retain |
| StatefulSet 缩容再扩容 | 新 Pod 复用旧 PVC(数据还在,好事); 删 PVC 才真清 |
| hostPath 数据"丢失" | Pod 漂移到别的节点了 |
小结
| 层 | 对象 | 一句话 |
|---|---|---|
| 使用 | Volume + volumeMounts | 挂进容器 |
| 声明 | PVC | 我要多少、什么模式 |
| 供给 | PV / StorageClass | 谁来满足、怎么自动创建 |
| 有状态 | StatefulSet + volumeClaimTemplates | 稳定身份 + 独立盘 |
| 保命 | 快照 + 备份 + Retain | 数据无价 |
本文是「云原生」系列第 12 篇。