在 Kubernetes 持久化存储的学习中,PV(PersistentVolume)与 PVC(PersistentVolumeClaim)是绕不开的核心概念。本文将结合我在离线单节点环境下的完整实验经历,从理论到实战,带你彻底掌握 Local PV/PVC 的配置、部署与排障,同时深入理解 StorageClass 的动态供给能力。
🎯 一、PV/PVC 核心概念与设计价值
1.1 为什么需要 PV/PVC?
Kubernetes 中 Pod 是临时、可销毁 的,但很多应用(如数据库、日志系统)需要持久化存储 来保存数据。直接在 Pod 中使用 hostPath 或 emptyDir 存在以下问题:
- 存储与应用强耦合:开发人员需要关心底层存储细节,无法做到 "一次编写,到处运行"。
- 资源管理混乱:管理员无法统一管控集群存储资源,容易出现存储滥用或浪费。
- 数据迁移困难 :Pod 重建或调度到其他节点时,
hostPath数据无法跟随。
1.2 PV/PVC 如何解决问题?
PV/PVC 采用了 **"资源池 + 申请单"的设计模式,实现了存储的供给与使用解耦 **:
- 管理员视角(PV):负责创建和维护 PV 资源池,定义存储的容量、访问模式、回收策略等属性。
- 开发人员视角(PVC):只需通过 PVC 向集群 "申请" 存储,无需关心底层是 Local PV、NFS 还是 Ceph。
- 调度器视角:自动将 PVC 绑定到满足需求的 PV,实现存储的动态分配和管理。
📋 二、PV 核心属性与类型
2.1 PV 关键配置字段
| 字段 | 说明 | 可选值 |
|---|---|---|
capacity |
PV 的存储容量 | 如 1Gi、10Gi |
accessModes |
PV 的访问模式 | ReadWriteOnce(单节点读写)、ReadOnlyMany(多节点只读)、ReadWriteMany(多节点读写) |
persistentVolumeReclaimPolicy |
PV 回收策略 | Retain(保留数据,手动清理)、Delete(删除存储和数据)、Recycle(清理数据后复用,已废弃) |
storageClassName |
PV 所属存储类 | 如 local-storage、nfs-storage |
nodeAffinity |
PV 节点亲和性(仅 Local PV 需配置) | 强制 PV 绑定到特定节点 |
2.2 PV 主要类型
- Local PV:使用节点本地磁盘,性能高但不支持跨节点调度,适合离线单节点、数据库等场景。
- NFS PV:使用网络文件系统,支持多节点读写,适合开发测试、共享存储场景。
- Ceph PV:分布式块存储,高可用、高可靠,适合生产环境核心业务。
- 云厂商 PV:如 AWS EBS、阿里云云盘,与云平台深度集成,适合云原生场景。
2.3 PV 访问模式详解
- ReadWriteOnce(RWO):仅允许一个节点挂载并读写,是 Local PV 的唯一支持模式。
- ReadOnlyMany(ROX):允许多个节点挂载但仅能读取,适合静态内容共享场景。
- ReadWriteMany(RWX):允许多个节点挂载并读写,仅 NFS、Ceph 等网络存储支持。
💡 注意:PV 的访问模式是能力声明 ,而非强制限制。例如,一个
ReadWriteOnce的 PV 被绑定后,Kubernetes 会确保只有一个节点能挂载它,但不会阻止该节点上的多个 Pod 访问。
📝 三、PVC 核心属性与绑定机制
3.1 PVC 关键配置字段
PVC 的配置是对 PV 的 "筛选条件",Kubernetes 会根据这些条件匹配最合适的 PV:
| 字段 | 说明 |
|---|---|
accessModes |
申请的访问模式,必须与 PV 的 accessModes 兼容 |
resources.requests.storage |
申请的存储容量,不能超过 PV 的 capacity |
storageClassName |
申请的存储类,必须与 PV 的 storageClassName 完全匹配 |
selector |
标签选择器,进一步筛选 PV(可选) |
3.2 PV/PVC 绑定机制
- 静态供给:管理员提前创建 PV,PVC 提交后直接绑定到满足条件的 PV。
- 动态供给 :管理员配置
StorageClass和对应的存储插件,PVC 提交后自动创建 PV。
💡 绑定是一对一、不可逆 的:一个 PVC 只能绑定一个 PV,一个 PV 也只能被一个 PVC 绑定。绑定后,PVC 不能修改
accessModes或storageClassName。
3.3 PVC 与 Pod 的关联
Pod 通过 volumes 和 volumeMounts 字段使用 PVC:
spec:
volumes:
- name: www
persistentVolumeClaim:
claimName: www-web-0 # 关联 PVC 名称
containers:
- name: nginx
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html # 挂载到容器内路径
🔄 四、PV/PVC 生命周期
4.1 PV 生命周期阶段
- Available:PV 已创建,未被任何 PVC 绑定。
- Bound:PV 已被 PVC 绑定,处于使用中。
- Released :PVC 被删除,但 PV 回收策略为
Retain,数据保留,需手动清理。 - Failed:PV 回收或清理过程中发生错误。
4.2 PVC 生命周期阶段
- Pending:PVC 已创建,但未找到匹配的 PV。
- Bound:PVC 已绑定到 PV,可被 Pod 使用。
- Lost:绑定的 PV 丢失(如节点故障),PVC 无法正常使用。
🛠️ 五、离线单节点环境 PV/PVC + StorageClass 实战
5.1 环境准备
- 节点信息 :单节点集群,节点名称
172-16-2-47 - 存储目录 :在节点上创建
/root/1.19/nfs-test目录,放入index.html测试文件。 - 镜像准备 :离线环境下已导入
nginx:1.22.0-alpine镜像。
5.2 实验步骤
-
创建命名空间
apiVersion: v1 kind: Namespace metadata: name: nfs-storageclass -
创建 ServiceAccount 与 RBAC 权限为 StorageClass 实验创建专用的服务账号和权限,确保组件能正常访问 Kubernetes API。
-
创建 StorageClass
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-storage namespace: nfs-storageclass provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer parameters: onDelete: delete -
创建 Local PV
apiVersion: v1 kind: PersistentVolume metadata: name: local-pv-1 spec: capacity: storage: 1Mi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Delete storageClassName: local-storage local: path: "/root/1.19/nfs-test" nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - 172-16-2-47 -
创建 PVC
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-claim namespace: nfs-storageclass spec: accessModes: - ReadWriteMany resources: requests: storage: 1Mi storageClassName: local-storage -
创建测试 Pod
apiVersion: v1 kind: Pod metadata: name: test-pod namespace: nfs-storageclass spec: containers: - name: test-pod image: global.barn.registry/public/nginx:1.22.0-alpine volumeMounts: - name: nfs-pvc mountPath: "/usr/local/nginx/html" restartPolicy: "Never" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim
5.3 验证实验成功
bash
# 1. 验证 PVC 绑定
kubectl get pvc -n nfs-storageclass
# 输出:test-claim Bound local-pv-1 1Mi RWX local-storage 68s
# 2. 验证 Pod 状态
kubectl get pod test-pod -n nfs-storageclass
# 输出:test-pod 1/1 Running 0 25s
# 3. 验证数据挂载
kubectl exec -it test-pod -n nfs-storageclass -- cat /usr/local/nginx/html/index.html
# 输出:Local StorageClass Test
🚑 六、常见问题与排障指南
6.1 PV 无法删除,卡在 Terminating
现象 :执行 kubectl delete pv --all 后,PV 一直处于 Terminating 状态。原因 :PV 与 PVC 仍处于绑定状态,且 persistentVolumeReclaimPolicy: Retain 阻止了自动删除。解决:
bash
# 1. 先删除绑定的 PVC
kubectl delete pvc www-web-0 --grace-period=0 --force
# 2. 强制删除 PV
kubectl delete pv local-pv-1 --grace-period=0 --force
# 3. 若仍未删除,移除 PV 的 finalizers
kubectl patch pv local-pv-1 -p '{"metadata":{"finalizers":null}}'
6.2 Pod 处于 Pending 状态
现象 :Pod 一直处于 Pending,无法调度。原因 :PV 的 nodeAffinity 配置的节点名称与集群节点真实名称不匹配。解决:
bash
# 1. 获取节点真实名称
kubectl get nodes -o wide
# 2. 修正 PV 的 nodeAffinity 配置
# 将 values 改为节点真实名称(如 172-16-2-47)
6.3 PV 存储源不可变错误
现象 :修改 PV 配置时提示 spec.persistentvolumesource is immutable after creation。原因 :PV 的存储源(如 local.path)是不可变字段,创建后无法修改。解决:
bash
# 1. 删除旧 PV
kubectl delete pv local-pv-1 --grace-period=0 --force
# 2. 用新配置创建 PV
kubectl apply -f 05-pv.yaml
📊 七、生产环境最佳实践
7.1 PV 配置建议
- 回收策略 :测试环境用
Retain保留数据排障,生产环境用Delete自动清理(需存储插件支持)。 - 存储类 :为不同业务场景创建不同
StorageClass(如local-storage、nfs-storage),实现存储资源的隔离和精细化管理。 - 节点亲和性 :Local PV 必须配置
nodeAffinity,确保 Pod 调度到 PV 所在节点。
7.2 PVC 配置建议
- 容量规划:申请的容量应略大于实际需求,避免频繁扩容;同时不要过度申请,造成存储浪费。
- 访问模式 :根据业务场景选择最小必要的访问模式(如单节点业务用
RWO,多节点共享用RWX)。 - 标签选择器 :复杂场景下使用
selector精准匹配 PV,避免绑定到不符合预期的存储。
🎓 八、总结与思考
通过本次离线单节点环境的 PV/PVC + StorageClass 实验,我们不仅掌握了 Local PV/PVC 的配置与部署,更深入理解了 Kubernetes 持久化存储的核心原理。在离线环境下,我们需要:
- 选择合适的存储方案:Local PV 是离线单节点的最优选择。
- 重视节点亲和性:Local PV 必须绑定到特定节点,节点名称的正确性至关重要。
- 严格验证 YAML 格式:YAML 的缩进、拼写和大小写错误是常见的排障点。
- 理解不可变特性:PV 的核心字段创建后无法修改,需通过删除重建来更新配置。