Kubernetes 存储实战:PV、PVC、StorageClass 与动态供给
系统讲解 Kubernetes 存储体系:PersistentVolume 静态供给、StorageClass 动态供给、PVC 存储申请、访问模式与回收策略,覆盖 NFS、Ceph RBD、阿里云 CSI 等主流存储后端,以及 StatefulSet 有状态存储、数据备份迁移等生产实战场景。
Kubernetes 把存储抽象为三层:StorageClass(存储类型/供给方式)→ PersistentVolume(PV)(实际存储资源)→ PersistentVolumeClaim(PVC)(应用申请的存储)。理解这三层及其关系,是运维有状态应用(数据库、消息队列、文件存储)的基础。
存储体系架构
┌──────────────────────────────────────────────────────────┐
│ 应用层 │
│ Pod → volumeMounts → PVC(申请 10Gi,ReadWriteOnce) │
└────────────────────────┬─────────────────────────────────┘
│ 绑定
┌────────────────────────▼─────────────────────────────────┐
│ K8s 存储层 │
│ PV(10Gi,绑定到具体存储后端) │
│ ↑ 动态创建(StorageClass)或 静态预先创建 │
└────────────────────────┬─────────────────────────────────┘
│
┌────────────────────────▼─────────────────────────────────┐
│ 存储后端 │
│ NFS / Ceph RBD / 阿里云云盘 / AWS EBS / 本地磁盘 │
└──────────────────────────────────────────────────────────┘
一、静态 PV(手动预先创建)
适合:存储资源固定、数量少、需要精确控制的场景。
# 创建 PV(以 NFS 为例)
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-01
labels:
type: nfs
env: production
spec:
capacity:
storage: 100Gi # 存储容量
accessModes:
- ReadWriteMany # 访问模式(见下文)
persistentVolumeReclaimPolicy: Retain # 回收策略
storageClassName: "" # 空字符串:不绑定 StorageClass(手动绑定)
nfs:
server: 192.168.1.100 # NFS 服务器地址
path: /exports/k8s/data # NFS 导出路径
readOnly: false
# 创建 PVC 绑定到这个 PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
namespace: production
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
storageClassName: "" # 空字符串匹配 storageClassName="" 的 PV
selector:
matchLabels:
type: nfs # 精确匹配有这些标签的 PV
# 查看 PV 和 PVC 绑定状态
kubectl get pv
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
# nfs-pv-01 100Gi RWX Retain Bound production/data-pvc
kubectl get pvc -n production
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
# data-pvc Bound nfs-pv-01 100Gi RWX
访问模式详解
ReadWriteOnce (RWO):只能被一个节点以读写方式挂载
→ 适合:数据库、单实例应用
→ 支持:云盘(阿里云、AWS EBS)、本地磁盘
ReadOnlyMany (ROX):可以被多个节点以只读方式挂载
→ 适合:静态内容分发
ReadWriteMany (RWX):可以被多个节点以读写方式挂载
→ 适合:日志收集、共享文件
→ 支持:NFS、CephFS、Azure Files
ReadWriteOncePod (RWOP):只能被一个 Pod 以读写方式挂载(K8s 1.22+)
→ 最严格的独占模式
回收策略
Retain:PVC 删除后,PV 保留(状态变为 Released,需手动清理后才能重新绑定)
→ 生产数据库首选,防止误删数据
Delete:PVC 删除后,PV 和底层存储一起删除(动态供给默认策略)
→ 临时存储可以用
Recycle:已废弃,不用
二、StorageClass 与动态供给
动态供给:PVC 创建时,K8s 自动调用存储插件(CSI Driver)创建 PV,无需手动。
NFS 动态供给(使用 nfs-subdir-external-provisioner)
# 安装 NFS 动态供给器(Helm)
helm repo add nfs-subdir-external-provisioner \
https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
--set nfs.server=192.168.1.100 \
--set nfs.path=/exports/k8s \
--set storageClass.name=nfs-client \
--set storageClass.defaultClass=false \
-n kube-system
# StorageClass(NFS 动态供给)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
annotations:
storageclass.kubernetes.io/is-default-class: "false"
provisioner: cluster.local/nfs-subdir-external-provisioner
parameters:
archiveOnDelete: "true" # 删除 PVC 时归档而不是删除数据
reclaimPolicy: Retain # 默认 Delete,这里改为 Retain 更安全
volumeBindingMode: Immediate
阿里云 CSI 存储类(生产常用)
# 阿里云云盘(适合 RWO 场景)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: alicloud-disk-ssd
provisioner: diskplugin.csi.alibabacloud.com
parameters:
type: cloud_ssd # cloud_efficiency=高效盘, cloud_ssd=SSD盘, cloud_essd=ESSD盘
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer # 等 Pod 调度后再供给(匹配 AZ)
---
# 阿里云 NAS(适合 RWX 场景)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: alicloud-nas
provisioner: nasplugin.csi.alibabacloud.com
parameters:
server: "xxx.cn-shanghai.nas.aliyuncs.com"
path: "/k8s"
vers: "3"
reclaimPolicy: Retain
使用 StorageClass 动态申请存储
# PVC 动态申请(指定 StorageClass)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data
namespace: production
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
storageClassName: alicloud-disk-ssd # 指定 StorageClass,会自动创建 PV
# 查看动态创建的 PV
kubectl get pv
# NAME CAPACITY ACCESS MODES RECLAIM STATUS STORAGECLASS
# pvc-a1b2c3d4-... 50Gi RWO Retain Bound alicloud-disk-ssd
三、StatefulSet 存储(volumeClaimTemplates)
StatefulSet 每个 Pod 需要独立的存储,使用 volumeClaimTemplates 自动为每个 Pod 创建独立 PVC。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: production
spec:
serviceName: mysql # 需要配套 Headless Service
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.4
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
volumeMounts:
- name: data
mountPath: /var/lib/mysql # 数据目录
- name: config
mountPath: /etc/mysql/conf.d
resources:
requests:
memory: 2Gi
cpu: 500m
limits:
memory: 4Gi
cpu: 2000m
volumes:
- name: config
configMap:
name: mysql-config
# 每个 Pod 自动创建独立的 PVC
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: alicloud-disk-ssd
resources:
requests:
storage: 100Gi
# StatefulSet 创建后,自动生成的 PVC 命名规则:<volumeClaimTemplate名>-<pod名>
kubectl get pvc -n production
# NAME STATUS VOLUME CAPACITY
# data-mysql-0 Bound pvc-aaa... 100Gi
# data-mysql-1 Bound pvc-bbb... 100Gi
# data-mysql-2 Bound pvc-ccc... 100Gi
# StatefulSet 缩容后,PVC 不会自动删除(保护数据)
# 需要手动删除:kubectl delete pvc data-mysql-2 -n production
四、存储容量扩容
# 前提:StorageClass 开启了 AllowVolumeExpansion
kubectl get storageclass alicloud-disk-ssd -o yaml | grep allowVolumeExpansion
# allowVolumeExpansion: true
# 直接编辑 PVC 扩容(不需要删除 Pod)
kubectl edit pvc mysql-data -n production
# 修改 spec.resources.requests.storage 为更大的值
# 或者 patch
kubectl patch pvc mysql-data -n production -p \
'{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'
# 查看扩容状态
kubectl describe pvc mysql-data -n production
# Conditions:
# Type: FileSystemResizePending → 等待 Pod 重启后文件系统扩展
# 或者
# Type: Resizing → 正在扩容
# 某些存储驱动需要 Pod 重启才能完成文件系统扩容
kubectl rollout restart deployment mysql -n production
五、数据备份与迁移
使用 VolumeSnapshot(K8s 原生快照)
# 先创建 VolumeSnapshotClass
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: alicloud-disk-snapshot
driver: diskplugin.csi.alibabacloud.com
deletionPolicy: Retain
---
# 对 PVC 做快照
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: mysql-data-snapshot-20260722
namespace: production
spec:
volumeSnapshotClassName: alicloud-disk-snapshot
source:
persistentVolumeClaimName: mysql-data
# 查看快照状态
kubectl get volumesnapshot -n production
# NAME READYTOUSE SOURCEPVC RESTORESIZE AGE
# mysql-data-snapshot-20260722 true mysql-data 100Gi 5m
# 从快照恢复到新 PVC
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data-restored
namespace: production
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
storageClassName: alicloud-disk-ssd
dataSource:
name: mysql-data-snapshot-20260722
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
EOF
PVC 数据迁移(跨命名空间/集群)
# 迁移数据:使用 rsync Pod
kubectl run migration \
--image=alpine \
--restart=Never \
--overrides='
{
"spec": {
"containers": [{
"name": "migration",
"image": "alpine",
"command": ["sh", "-c", "apk add rsync && rsync -av /src/ /dst/"],
"volumeMounts": [
{"name": "src", "mountPath": "/src"},
{"name": "dst", "mountPath": "/dst"}
]
}],
"volumes": [
{"name": "src", "persistentVolumeClaim": {"claimName": "old-data"}},
{"name": "dst", "persistentVolumeClaim": {"claimName": "new-data"}}
]
}
}'
存储问题排查
# PVC 一直 Pending
kubectl describe pvc <pvc-name> -n <namespace>
# 常见原因:
# 1. 没有匹配的 PV(静态)或 StorageClass 不存在
# 2. StorageClass provisioner 故障
# 3. 存储容量不足
# 4. AZ 不匹配(云盘和 Pod 不在同一 AZ)
# 查看存储供给器日志
kubectl logs -n kube-system -l app=nfs-subdir-external-provisioner
# Pod 挂载 PVC 失败
kubectl describe pod <pod-name> | grep -A 10 Events
# Events: FailedMount → 节点无法挂载存储
# 检查节点是否安装了必要的客户端(nfs-utils、ceph-common 等)
# 查看 CSI 驱动状态
kubectl get csinode
kubectl get csidrivers
kubectl get pods -n kube-system -l app.kubernetes.io/name=csi-disk-plugin
小结
K8s 存储体系:静态 PV 适合存量存储资源的精确控制;StorageClass 动态供给 是生产中的主流方式,PVC 提交即自动创建 PV;StatefulSet + volumeClaimTemplates 是有状态应用(数据库、消息队列)的标准模式,每个 Pod 独享独立存储;VolumeSnapshot 提供原生快照能力,是数据库备份的基础设施。选择存储时的关键考量:访问模式(RWO 单点 vs RWX 多节点共享)决定存储类型,回收策略(Retain vs Delete)决定数据安全级别。
