K8s 存储生产配置与排障实战:PV/PVC/StorageClass 避坑指南
来源:老郭 | 发布日期:2026-06-02
基础概念速览
| 组件 | 类比 | 说明 |
|---|---|---|
| PV | 仓库 | 管理员提前准备的存储空间 |
| PVC | 租仓单 | 用户申请多大空间、什么类型 |
| StorageClass | 仓库分类标准 | 高性能 SSD / 普通 HDD / 便宜大碗 |
PV 生命周期
Available → Bound → Released → Failed
回收策略
| 策略 | 行为 | 推荐 |
|---|---|---|
| Retain ✅ | 保留数据,需手动清理 | 生产环境默认策略 |
| Delete | 自动删除 PV 和底层存储 | 临时数据 |
| Recycle | 已废弃(v1.20+) | ❌ 别用 |
访问模式
| 模式 | 缩写 | 说明 |
|---|---|---|
| ReadWriteOnce | RWO | 单节点读写(数据库标配) |
| ReadOnlyMany | ROX | 多节点只读 |
| ReadWriteMany | RWX | 多节点读写(NFS 等共享存储) |
静态供给实战
# PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-static-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.1.100
path: /data/nfs-share
---
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-static-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
# Pod 使用
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: storage
mountPath: /data
volumes:
- name: storage
persistentVolumeClaim:
claimName: my-static-pvc
绑定规则:PVC 容量 ≤ PV 容量,访问模式必须匹配。不匹配则 PVC 一直 Pending。
动态供给 StorageClass
云环境(AWS gp3)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
fsType: ext4
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer # 重要:按 Pod 调度可用区创建 PV
allowVolumeExpansion: true
WaitForFirstConsumer: PV 创建和绑定延迟到真正有 Pod 使用该 PVC 时才执行。确保 PV 在与 Pod 相同的可用区创建,避免跨区挂载失败。EBS 等 zone 级存储必须设此模式。
NFS 动态供给(自建首选)
通过 Helm 部署 nfs-subdir-external-provisioner:
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=/data/nfs-k8s
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
pathPattern: "{.PVC.name}"
onDelete: delete
reclaimPolicy: Delete
PVC 指定 storageClassName: nfs-storage 即可自动创建 PV。
Local SSD 高性能存储(数据库场景)
NVMe SSD 作为 Local Volume 可提供 50 万+ IOPS。但绑定在特定节点上,节点故障数据不可用。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-ssd
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer # Local PV 必须
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-node1
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-ssd
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1
PVC Pending 排查三板斧
PVC Pending 是最常见的存储报错。
第一板斧:看事件
kubectl describe pvc <name> -n <ns>
事件会直接写明原因。
第二板斧:检查 StorageClass
kubectl get sc # 确认 PVC 中指定的 SC 存在
kubectl get sc <name> -o yaml
SC 名称写错或不存在是最常见原因。
第三板斧:检查动态供给器
kubectl get pods -n kube-system | grep -E "provisioner|csi"
kubectl logs -n kube-system <provisioner-pod>
动态供给器挂了 → PV 创建不出来。
隐藏坑:ResourceQuota 限制了 namespace 总存储上限
kubectl get resourcequota -n <ns>
PVC Terminating 卡住处理
# 1. 找出还有 Pod 在用该 PVC
kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.volumes[].persistentVolumeClaim.claimName == "your-pvc-name")'
# 2. 强制移除 Finalizer(慎用,确认无业务使用后)
kubectl patch pvc <name> -p '{"metadata":{"finalizers":null}}' --type=merge
静态迁移到动态(换 StorageClass)
使用 pvmigrate 工具:
pvmigrate --source-sc default --dest-sc fast-ssd
流程:验证 SC 存在 → 找出 PVC → 创建新 PVC → 停 Pod → rsync 拷贝数据 → 切换 PVC → 恢复 Pod。仅支持 StatefulSet 和 Deployment,建议维护窗口操作。
RBAC 安全配置
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pvc-manager
rules:
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-pvc-access
subjects:
- kind: ServiceAccount
name: app-sa
roleRef:
kind: Role
name: pvc-manager
apiGroup: rbac.authorization.k8s.io
Pod 设置 securityContext 确保非 root 运行并设置存储卷组权限:
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
隐藏坑点合集
| 坑 | 现象 | 解决 |
|---|---|---|
| subPath 硬链接风险 | subPath 挂载子目录时可能存在符号链接逃逸 | 非必要不用 subPath,确保低权限运行 |
| 挂载系统目录 | 把 PVC 挂到 / 或 /var/run → Pod 崩溃 |
只挂载到应用数据目录 |
| 回收策略默认 Delete | 动态供给默认 Delete,误删 PVC 数据直接没 | 生产环境一律手动设 reclaimPolicy: Retain |
| Released 无法直接复用 | PVC 删后 PV 处于 Released,不能自动绑定给新 PVC | 编辑 PV 删掉 claimRef 字段,或删掉重建 |
| WaitForFirstConsumer 遗漏 | EBS 等 zone 级存储不设此模式 → PV 可能落到不同可用区 | StorageClass 必须设置此模式 |
关联页面
| 页面 | 关联点 |
|---|---|
| k8s-persistent-storage-guide | K8s 持久化存储(概念基础篇) |
| storage-troubleshooting | 存储排障 |
| k8s-resource-limits-configuration | 资源限制配置 |
| k8s-statefulset-guide | StatefulSet 完全指南 |
| database-on-kubernetes-debate | 数据库上 K8s 架构选型 |