概述
你有没有遇到过这种情况:
- 创建了 PVC,但状态一直是
Pending,Pod 无法启动? - 删除了 PVC,结果云盘被自动删除,数据找不回来了?
- 明明有 100Gi 的 PV,PVC 申请 50Gi 却无法绑定?
- 多个 Pod 想共享同一个存储,却提示挂载失败?
- 动态供给创建了 PV,但不知道底层存储在哪里?
这些问题,很可能是因为你没有理解 Kubernetes 存储的核心机制------PersistentVolume 与 PersistentVolumeClaim 的绑定与生命周期
什么是 PV 与 PVC
PersistentVolume (PV) 是 集群中的一块存储资源,由管理员预先配置或通过 StorageClass 动态创建。
PersistentVolumeClaim (PVC) 是 用户对存储资源的申请请求,开发者只需声明"我需要多大容量、什么访问模式"。
简单来说:
PV 是"云硬盘",PVC 是"硬盘申请单"。
它是 Kubernetes 实现存储资源解耦 与动态供给 的核心机制
为什么需要 PV 与 PVC
直接使用 Volume(如 hostPath、nfs)虽然简单,但存在严重问题:
| 问题 | 后果 |
|---|---|
| 存储细节暴露 | 开发者需要知道 NFS 服务器地址、云盘类型等底层信息 |
| 资源无法复用 | 每个 Pod 都要单独配置存储,无法统一管理 |
| 容量浪费 | 无法实现存储资源的超卖与动态分配 |
| 权限混乱 | 没有统一的访问控制,容易导致数据泄露 |
| 回收困难 | 删除 Pod 后,存储资源残留,造成资源泄漏 |
没有 PV/PVC 的后果
假设你的应用需要 100Gi 存储:
传统方式(直接配置 Volume)
├── Pod A: 直接挂载 NFS 服务器 192.168.1.100:/data/app
├── Pod B: 直接挂载 NFS 服务器 192.168.1.100:/data/app
└── 问题
├── NFS 服务器 IP 变更,所有 Pod 都要修改
├── 无法限制 Pod A 和 Pod B 的容量使用
├── 删除 Pod 后,NFS 目录残留,无人清理
└── 结果:运维噩梦,存储管理混乱
如果你使用 PV/PVC:
- 管理员统一管理存储资源池
- 开发者只需申请容量,无需关心底层细节
- 删除 PVC 后,根据策略自动回收或保留
- 结果:存储资源高效、安全、可维护
核心属性
PV 的核心属性
| 属性 | 说明 | 示例 |
|---|---|---|
| capacity | 存储容量 | storage: 10Gi |
| accessModes | 访问模式(RWO/ROX/RWX) | ReadWriteOnce |
| persistentVolumeReclaimPolicy | 回收策略(Retain/Delete/Recycle) | Retain |
| storageClassName | 存储类名称 | fast-ssd |
| volumeMode | 卷模式(Filesystem/Block) | Filesystem |
| nodeAffinity | 节点亲和性(本地存储用) | required |
PVC 的核心属性
| 属性 | 说明 | 示例 |
|---|---|---|
| resources.requests.storage | 申请容量 | storage: 5Gi |
| accessModes | 访问模式(必须与 PV 匹配) | ReadWriteOnce |
| storageClassName | 存储类名称(动态供给用) | fast-ssd |
| selector | 标签选择器(静态绑定用) | matchLabels: {env: prod} |
| volumeName | 指定 PV 名称(强制绑定) | pv-specific-001 |
三种访问模式
访问模式决定了 PV 能被多少个节点同时挂载:
| 访问模式 | 缩写 | 说明 | 适用场景 |
|---|---|---|---|
| ReadWriteOnce | RWO | 只能被单个节点以读写方式挂载 | 数据库(MySQL、PostgreSQL) |
| ReadOnlyMany | ROX | 可以被多个节点以只读方式挂载 | 静态资源、配置文件共享 |
| ReadWriteMany | RWX | 可以被多个节点以读写方式挂载 | 共享日志、多副本应用上传文件 |
💡 关键点 :PVC 的访问模式必须是 PV 访问模式的子集。例如,PV 支持 RWX,PVC 可以申请 RWO、ROX 或 RWX。
三种回收策略
当 PVC 被删除时,PV 的处理方式:
| 回收策略 | 说明 | 适用场景 | 风险等级 |
|---|---|---|---|
| Retain | 保留 PV 和数据,需要手动清理 | 生产环境、重要数据 | 🟢 安全 |
| Delete | 自动删除 PV 和底层存储(如云盘) | 测试环境、临时数据 | 🟡 自动 |
| Recycle | 清空数据(rm -rf),保留 PV |
已废弃,不推荐使用 | 🔴 危险 |
⚠️ 警告 :
Recycle策略因安全性问题已在 Kubernetes 1.23+ 版本中移除,建议使用动态供给替代。
PV 与 PVC 的两种供给方式
静态供给(Static Provisioning)
管理员手动创建 PV,开发者创建 PVC 申请使用。
yaml
# 1. 管理员创建 PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-10gi
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
nfs:
server: 192.168.1.100
path: /data/nfs/share1
---
# 2. 开发者创建 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data-pvc
spec:
storageClassName: manual
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi # 小于 PV 的 10Gi,可以绑定
| 优点 | 缺点 |
|---|---|
| 精确控制存储资源 | 需要手动管理,效率低 |
| 适合固定存储需求 | 无法自动扩容或动态创建 |
动态供给(Dynamic Provisioning)
管理员配置 StorageClass,开发者创建 PVC 时自动创建 PV。
yaml
# 1. 管理员创建 StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
fsType: ext4
reclaimPolicy: Delete
volumeBindingMode: Immediate
---
# 2. 开发者创建 PVC(自动触发 PV 创建)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dynamic-pvc
spec:
storageClassName: fast-ssd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
| 优点 | 缺点 |
|---|---|
| 自动化,无需手动创建 PV | 需要配置 CSI 驱动 |
| 支持动态扩容、快照 | 存储资源可能超卖 |
PV 与 PVC 的绑定规则
PVC 与 PV 的绑定需要满足以下条件:
| 条件 | 说明 | 示例 |
|---|---|---|
| 容量匹配 | PVC 申请容量 ≤ PV 容量 | PVC 申请 5Gi,PV 有 10Gi ✅ |
| 访问模式匹配 | PVC 访问模式 ⊆ PV 访问模式 | PVC 申请 RWO,PV 支持 RWX ✅ |
| StorageClass 匹配 | 两者 StorageClass 相同 | 都为 fast-ssd ✅ |
| 标签选择器匹配 | PVC 的 selector 匹配 PV 的 labels | matchLabels: {env: prod} ✅ |
💡 注意 :一旦绑定,PV 与 PVC 的关系是一对一 且不可更改的,除非删除 PVC 重新申请。
PV 与 PVC 的生命周期
┌─────────────────────────────────────────────────────────────┐
│ PV 与 PVC 生命周期 │
│ │
│ 1. 供给(Provisioning) │
│ ├── 静态:管理员手动创建 PV │
│ └── 动态:StorageClass 自动创建 PV │
│ │
│ 2. 绑定(Binding) │
│ ├── PVC 创建后,K8s 自动匹配符合条件的 PV │
│ └── 状态变为 Bound │
│ │
│ 3. 使用(Using) │
│ └── Pod 通过 volumes 字段引用 PVC,挂载到容器 │
│ │
│ 4. 释放(Releasing) │
│ └── 删除 PVC,PV 状态变为 Released │
│ │
│ 5. 回收(Reclaiming) │
│ ├── Retain:保留 PV,手动清理 │
│ └── Delete:自动删除 PV 和底层存储 │
└─────────────────────────────────────────────────────────────┘
验证效果
你可以通过以下方式验证 PV 与 PVC 是否生效:
1. 查看绑定状态
bash
# 查看 PVC 状态
kubectl get pvc
# STATUS 应为 Bound
# 查看 PV 状态
kubectl get pv
# STATUS 应为 Bound,CLAIM 显示绑定的 PVC
# 查看详情
kubectl describe pvc app-data-pvc
2. 验证数据持久化
bash
# 创建 Pod 并写入数据
kubectl run test-pod --image=nginx --rm -it -- bash
echo "持久化数据" > /data/test.txt
# 删除 Pod
kubectl delete pod test-pod
# 删除 PVC(测试 Retain 策略)
kubectl delete pvc app-data-pvc
# 查看 PV 是否保留
kubectl get pv
# 状态应为 Released,数据仍在底层存储
# 重新创建 PVC 绑定原 PV(需手动编辑 PV 移除 claimRef)
kubectl edit pv nfs-pv-10gi
# 删除 claimRef 字段,然后重新创建 PVC
3. 调试 Pending 问题
bash
# 查看 PVC 事件
kubectl describe pvc dynamic-pvc
# 常见错误:
# - FailedBinding: 没有符合条件的 PV
# - ProvisioningFailed: StorageClass 配置错误或 CSI 驱动异常
# - WaitForFirstConsumer: 本地存储等待 Pod 调度
# 查看 PV 事件
kubectl describe pv nfs-pv-10gi
4. 检查底层存储
bash
# 云环境(AWS EBS)
aws ec2 describe-volumes --filters "Name=tag:kubernetes.io/created-for/pv-name,Values=nfs-pv-10gi"
# NFS 存储
showmount -e 192.168.1.100
最佳实践
| 建议 | 说明 |
|---|---|
| 生产环境用 Retain | 防止误删 PVC 导致数据丢失 |
| 测试环境用 Delete | 自动清理,避免资源浪费 |
| 启用动态供给 | 配置 StorageClass,减少手动操作 |
| 使用标签选择器 | 静态绑定时用 selector.matchLabels 精确匹配 |
| 监控存储使用率 | 避免 PVC 容量浪费或不足 |
| 定期备份 PV 数据 | PVC 不等于备份,配合 Velero 做快照 |
| 限制 PVC 创建权限 | 使用 RBAC 防止存储资源滥用 |
| 文档化 StorageClass | 团队内部统一存储类命名和用途 |
常见问题
| 问题 | 说明 | 解决方案 |
|---|---|---|
| PVC 一直 Pending | 没有匹配的 PV 或 StorageClass 错误 | 检查 kubectl describe pvc 事件 |
| PV 无法绑定 | 容量不足或访问模式不匹配 | 确保 PVC 申请容量 ≤ PV 容量 |
| 删除 PVC 数据丢失 | 用了 Delete 策略 | 生产环境改用 Retain |
| PV 残留 | Retain 策略后未手动清理 | 删除 PV 前备份数据,然后 kubectl delete pv |
| 多 Pod 挂载失败 | RWO 模式被多个 Pod 使用 | 改用 RWX 或确保单副本 |
| StorageClass 不生效 | PVC 未指定 storageClassName | 检查 PVC 的 storageClassName 与 StorageClass 名称一致 |
| 本地存储调度失败 | PV 有 nodeAffinity,Pod 未调度到对应节点 | 使用 volumeBindingMode: WaitForFirstConsumer |
| 权限错误 | 容器用户与存储权限不匹配 | 设置 PVC 的 spec.volumeMode 和 Pod 的 securityContext.fsGroup |
选择决策树
需要持久化存储吗?
├── 否 → 使用 emptyDir 或不需要 Volume
│
└── 是 → 有现有存储系统吗?
├── 是(NFS/Ceph)→ 静态供给 PV
│ └── 管理员手动创建 PV,开发者申请 PVC
│
└── 否 → 云平台或 CSI 驱动?
├── 是 → 动态供给(推荐)
│ └── 配置 StorageClass,开发者直接申请 PVC
│
└── 否 → 测试环境可用 hostPath PV(不推荐生产)
总结
| 关键点 |
|---|
| PV 是集群级存储资源,PVC 是命名空间级存储申请 |
| 静态供给 适合固定存储,动态供给适合灵活需求 |
| Retain 策略保护生产数据,Delete策略简化测试清理 |
| 正确理解绑定规则能避免 Pending 和 数据丢失 |
PV 与 PVC 在 K8s 生态中的位置:
┌─────────────────────────────────────────────────────────┐
│ Kubernetes 存储体系 │
├─────────────────┬─────────────────┬─────────────────────┤
│ Volume │ PV │ PVC │
│ (挂载抽象) │ (存储资源) │ (存储申请) │
│ │ │ │
│ • emptyDir │ • NFS │ • 容量申请 │
│ • hostPath │ • AWS EBS │ • 访问模式 │
│ • configMap │ • 云盘 │ • StorageClass │
└─────────────────┴─────────────────┴─────────────────────┘
PV 是仓库里的货物,PVC 是提货单,StorageClass 是自动补货系统。