Lesson 13:PV/PVC 持久化存储与 StorageClass

admin 发布于 8 天前 9 次阅读


学习目标

  • 理解容器存储的临时性问题
  • 掌握 PV/PVC 的绑定机制
  • 理解 StorageClass 动态供给
  • 完成 hostPath 存储的实际挂载与数据持久化验证

1. 为什么需要持久化存储?

容器的文件系统是临时的(OverlayFS 可写层)。当容器重启或 Pod 被重新调度到其他节点时,所有写入的数据都会丢失。对于数据库、文件上传等有状态应用,这是不可接受的。

Volume 是 K8s 提供的存储抽象,它的生命周期独立于容器,可以在容器重启后保留数据。


2. PV 与 PVC

PV(PersistentVolume)—— 存储资源

由管理员创建,代表集群中的一块实际存储(NFS 共享目录、云盘、本地磁盘等)。

yamlapiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 5Gi                           # 存储容量
  accessModes:
    - ReadWriteMany                        # 多节点读写
  persistentVolumeReclaimPolicy: Retain    # PVC 删除后保留数据
  nfs:
    server: 172.16.11.104
    path: /data/nfs-share

参数解读

  • capacity.storage: 5Gi:声明这块存储的总容量为 5 GiB
  • accessModes:定义这块存储支持的访问方式(详见下表)
  • persistentVolumeReclaimPolicy: Retain:当绑定的 PVC 被删除后,PV 中的数据保留不动,需要管理员手动清理。生产环境强烈推荐 Retain
  • nfs:存储后端的具体实现(这里是 NFS),不同的存储后端有不同的配置字段

PVC(PersistentVolumeClaim)—— 存储需求

由用户创建,描述需要多大、什么类型的存储。K8s 自动匹配合适的 PV 进行绑定。

yamlapiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi                # 申请 5Gi 存储

设计哲学:PV 和 PVC 是一种生产者-消费者模式。管理员负责"供货"(创建 PV),开发者负责"下单"(创建 PVC)。两者通过 accessModes 和 storage 大小自动匹配。这种解耦让开发者不需要关心底层存储是 NFS、Ceph 还是云盘。

绑定流程

bash用户创建 PVC("我需要 5Gi 的 RWX 存储")
    ↓
K8s 匹配满足条件的 PV → 绑定(Bound)
    ↓
Pod 挂载 PVC 使用存储

访问模式

模式缩写含义
ReadWriteOnceRWO单节点读写(最常见,如本地磁盘、云盘)
ReadOnlyManyROX多节点只读
ReadWriteManyRWX多节点读写(NFS、CephFS 支持)

回收策略(PVC 被删除后 PV 的数据怎么办)

策略行为
Retain保留数据,需手动清理(生产推荐)
Delete自动删除后端存储数据
Recycle清空数据后重新可用(已弃用)

3. StorageClass 动态供给

手动创建 PV 无法应对大量存储需求。StorageClass 定义了存储的"模板",当用户创建 PVC 时,K8s 自动创建对应的 PV。

yaml# StorageClass 示例(NFS 动态供给需要额外部署 NFS Provisioner)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-storage
provisioner: nfs-provisioner    # 供给器名称
parameters:
  archiveOnDelete: "true"       # 删除 PVC 时归档而非直接删除

参数解读

  • provisioner:指定由哪个存储驱动来自动创建 PV。不同的存储方案有不同的 provisioner(如 AWS EBS 用 ebs.csi.aws.com,NFS 用自定义的 provisioner)
  • parameters:传递给 provisioner 的参数,不同 provisioner 支持的参数不同
yaml# PVC 引用 StorageClass(不再需要手动创建 PV)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: auto-pvc
spec:
  storageClassName: nfs-storage   # 指定 StorageClass
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi

工作流程:PVC 指定了 storageClassName: nfs-storage → K8s 找到对应的 StorageClass → 调用 provisioner 自动创建一个 10Gi 的 PV → 自动绑定。整个过程无需管理员介入。


动手实验

实验 13.1:验证容器存储的临时性

bash# === 在 Master 节点 (172.16.11.104) 上执行 ===

# 创建一个 Pod,写入数据到容器文件系统
kubectl run temp-data --image=docker.m.daocloud.io/library/busybox --restart=Never -- sh -c \
  "echo 'important data' > /tmp/data.txt && sleep 3600"
sleep 3

# 验证数据存在
kubectl exec temp-data -- cat /tmp/data.txt

预期输出

bashimportant data
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===

# 删除并重建同名 Pod
kubectl delete pod temp-data --wait=true
kubectl run temp-data --image=docker.m.daocloud.io/library/busybox --restart=Never -- sh -c 'cat /tmp/data.txt 2>&1; sleep 3600'
sleep 3

# 查看日志——数据还在吗?
kubectl logs temp-data

预期输出

bashcat: can't open '/tmp/data.txt': No such file or directory

输出解读:数据丢失了!/tmp/data.txt 不存在。这就是容器存储的临时性——Pod 被删除重建后,之前写入的所有数据都会消失,因为新 Pod 拿到的是一个全新的、干净的可写层。

bash# === 在 Master 节点 (172.16.11.104) 上执行 ===

kubectl delete pod temp-data

实验 13.2:使用 PV/PVC 持久化数据

bash# === 在 Master 节点 (172.16.11.104) 上执行 ===

# 创建 PV 和 PVC(使用 hostPath 做简单演示)
kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolume
metadata:
  name: demo-pv
spec:
  capacity:
    storage: 1Gi
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /tmp/k8s-pv-demo
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: demo-pvc
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 1Gi
  volumeName: demo-pv
EOF

预期输出

bashpersistentvolume/demo-pv created
persistentvolumeclaim/demo-pvc created

YAML 解读

  • hostPath.path: /tmp/k8s-pv-demo:使用节点本地目录作为存储(仅适合实验,生产环境应使用 NFS/Ceph/云盘等分布式存储)
  • volumeName: demo-pv:PVC 明确指定要绑定的 PV 名称,跳过自动匹配。这在集群没有配置 StorageClass 时是必需的
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===

# 验证绑定状态
kubectl get pv,pvc

预期输出

bashNAME                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/demo-pv   1Gi        RWO            Retain           Bound    default/demo-pvc                  <unset>                          2s

NAME                             STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/demo-pvc   Bound    demo-pv   1Gi        RWO                           <unset>                 2s

输出解读

  • PV 的 STATUS: Bound + CLAIM: default/demo-pvc —— PV 已经被 default 命名空间的 demo-pvc 绑定
  • PVC 的 STATUS: Bound + VOLUME: demo-pv —— PVC 成功绑定到了 demo-pv
  • RECLAIM POLICY: Retain —— 即使删除 PVC,数据也会保留
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===

# 创建 Pod 挂载 PVC,写入数据
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: pv-writer
spec:
  containers:
  - name: writer
    image: docker.m.daocloud.io/library/busybox
    command: ['sh', '-c', 'echo persistent-data-12345 > /data/test.txt && echo wrote data && sleep 3600']
    volumeMounts:
    - name: my-storage
      mountPath: /data
  volumes:
  - name: my-storage
    persistentVolumeClaim:
      claimName: demo-pvc
EOF

kubectl wait --for=condition=Ready pod/pv-writer --timeout=60s

预期输出

bashpod/pv-writer created
pod/pv-writer condition met
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===

# 验证数据已写入
kubectl exec pv-writer -- cat /data/test.txt

预期输出

bashpersistent-data-12345
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===

# 删除 Pod(模拟 Pod 故障/重建)
kubectl delete pod pv-writer --wait=true

# 重新创建 Pod 挂载同一个 PVC
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: pv-reader
spec:
  containers:
  - name: reader
    image: docker.m.daocloud.io/library/busybox
    command: ['sh', '-c', 'cat /data/test.txt && sleep 3600']
    volumeMounts:
    - name: my-storage
      mountPath: /data
  volumes:
  - name: my-storage
    persistentVolumeClaim:
      claimName: demo-pvc
EOF

kubectl wait --for=condition=Ready pod/pv-reader --timeout=60s
bash# === 在 Master 节点 (172.16.11.104) 上执行 ===

# 数据还在吗?
kubectl logs pv-reader

预期输出

bashpersistent-data-12345

输出解读:数据完好无损!与实验 13.1 形成鲜明对比——使用 PVC 挂载的存储,数据的生命周期独立于 Pod。即使 Pod 被删除重建,只要挂载同一个 PVC,之前写入的数据就可以继续读取。这就是持久化存储解决的核心问题。

bash# === 在 Master 节点 (172.16.11.104) 上执行 ===

# 清理
kubectl delete pod pv-reader
kubectl delete pvc demo-pvc
kubectl delete pv demo-pv

预期输出

bashpod "pv-reader" deleted
persistentvolumeclaim "demo-pvc" deleted
persistentvolume "demo-pv" deleted

检查点

  •  能解释为什么容器需要持久化存储(实验 13.1 的对比体验)
  •  能创建 PV 和 PVC 并验证绑定关系(STATUS: Bound)
  •  理解三种访问模式和三种回收策略
  •  能解释 StorageClass 动态供给的工作原理
此作者没有提供个人介绍。
最后更新于 2026-04-27