IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章助你少走弯路。在第 34 篇中我们学了 emptyDir 和 hostPath。它们解决的是“Pod 内部容器间共享文件”和“访问宿主机特定路径”的问题。但一个最核心的生产需求还没有解决Pod 重建后数据如何保留如果 Redis 的 Pod 被重新调度到另一台节点上emptyDir 和 hostPath 都无法将数据带过去。emptyDir 随 Pod 删除而销毁hostPath 只存在于特定节点上。我们需要一种与 Pod 生命周期解耦、可在集群任意节点访问的持久化存储。这正是 PVPersistentVolume持久卷和 PVCPersistentVolumeClaim持久卷声明的设计目标。在 Docker 生态中我们通过docker volume create来创建命名卷容器重启、删除都不会丢失数据。但在 K8s 集群中管理员不可能为每个应用手动在每台节点上创建 Volume——我们需要一种自动化的动态供给机制。今天这篇我们就从 PV/PVC 的核心概念讲起通过贯穿案例的 Redis 持久化存储深入理解 StorageClass 如何实现“声明即存储”。一、PV 和 PVC 的抽象模型1.1 为什么需要 PV 和 PVC在第 7 篇 Docker 数据管理中我们创建 Volume 的命令是docker volume create redis-data然后在docker run时用-v redis-data:/data挂载。这种方式在单机上工作良好但在 K8s 集群中有两个致命缺陷不跨节点Pod 重新调度到另一台节点时新节点上没有这个 Volume。人工运维管理员需要提前创建好 Volume开发者需要知道 Volume 的具体名称和配置。K8s 的解决方案是将存储抽象为两个独立的对象实现管理员与开发者的职责分离PVPersistentVolume集群管理员准备的存储资源可以来自 NFS、云存储AWS EBS、GCE PD、本地磁盘等。PV 与 Pod 生命周期无关——Pod 被删了PV 还在。PVCPersistentVolumeClaim开发者声明对存储的需求“我需要 1Gi 空间读写一次即可”。K8s 自动找匹配的 PV 绑定给这个 PVC。1.2 用“租房子”来理解 PV 和 PVC把 K8s 集群想象成一个租房市场PV是房东已装修好的房源一套具体的房子位于某小区某单元由管理员中介提前准备好。PVC是租客的求租需求——“我需要 50 平米、朝南的一居室”。租客不需要知道具体哪套房子只需要描述需求。K8s 的匹配引擎是中介自动将合适的房源PV匹配给租客需求PVC。匹配后这套房子就归这个租客独占使用。如果租客搬走了Pod 删除PVC 还在房子仍然保留PV 保留。新租客重建的 Pod可以通过同一个 PVC 继续使用同一套房子和里面的家具数据。1.3 PV 的三种供给方式在实际工作中静态供给只适合极少数固定存储资源已知的场景比如一个已经部署好的 NFS 服务器。生产环境的标配是动态供给——你只需要声明“我要 1Gi 的云盘”StorageClass 自动调用云 API 创建。二、静态供给理解 PV 和 PVC 的匹配机制在进入动态供给之前先通过一个静态供给的简单示例理解 PV 和 PVC 的绑定关系。2.1 创建一个 PVapiVersion: v1 kind: PersistentVolume metadata: name: task-pv-volume spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce hostPath: path: /mnt/data关键字段capacity.storage: 1GiPV 的容量accessModes: ReadWriteOnce访问模式单个节点读写hostPath在 Minikube 环境中使用 hostPath 模拟真实存储生产环境会替换为云存储如awsElasticBlockStore2.2 创建一个 PVCapiVersion: v1 kind: PersistentVolumeClaim metadata: name: task-pvc-claim spec: accessModes: - ReadWriteOnce resources: requests: storage: 1GiPVC 不指定具体 PV 名称只声明需求“我要 1Gi、单节点读写”。K8s 自动找到匹配的 PV 并绑定。kubectl apply-ftask-pv.yaml kubectl apply-ftask-pvc.yaml kubectl get pvc输出NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE task-pvc-claim Bound task-pv-volume 1Gi RWO 30sSTATUSBound表示 PVC 已成功绑定到task-pv-volume这个 PV。STORAGECLASS列为空表示这是静态供给没有用到 StorageClass。2.3 在 Pod 中使用 PVCvolumes: - name: task-storage persistentVolumeClaim: claimName: task-pvc-claimPod 引用 PVCPVC 绑定 PVPV 对应物理存储。这是三层引用链——开发者只需关心 PVC不必知道底层存储的具体位置和类型。如果你删除了这个 Pod重新创建时引用同一个 PVC依然能访问到原来的数据。2.4 访问模式大多数数据库MySQL、Redis需要 RWO 模式。共享文件存储如 NFS可以支持 RWX 模式。如果 Pod 被调度到不同节点上RWO 模式的 PV 将无法在新节点上挂载——这就是为什么数据库这类有状态应用需要用 StatefulSet第 27 篇并配合节点亲和性来管理。三、StorageClass 与动态供给静态供给的问题很明显管理员需要预先创建每个 PV开发者扩容时要找管理员创建新的 PV。当集群中有数百个应用、每个应用都需要存储时这根本不可行。StorageClass解决了这个问题。它定义了一个“存储模板”当 PVC 被创建时StorageClass 自动调用后端存储的 API 创建 PV并绑定给 PVC。3.1 StorageClass 的工作原理PVC 创建 → StorageClass → Provisioner 调用 API → 自动创建 PV → 绑定 PVC → Pod 挂载管理员只需要创建 StorageClass 对象一次性配置之后开发者创建 PVC 时PV 会自动生成。这正是“声明式存储”的核心思想——你不再需要关心 PV 是谁创建的、在哪创建的只需要在 PVC 中声明“我要什么”。3.2 Minikube 中的 StorageClassMinikube 默认自带一个 StorageClass输出NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE standard(default)k8s.io/minikube-hostpath Delete Immediate(default)集群的默认 StorageClassPVC 不指定storageClassName时自动使用PROVISIONER负责实际创建存储的组件minikube-hostpath在宿主机上创建目录模拟真实存储RECLAIMPOLICY: DeletePVC 被删除时PV 也被自动删除VOLUMEBINDINGMODE: ImmediatePVC 创建后立即绑定 PV不需要等待 Pod 调度3.3 删除策略Retain策略适用于需要保留数据做审计或恢复的场景。但 Retain 后 PV 变为Released状态不能被新的 PVC 自动绑定——需要管理员手动清除 PV 中的旧数据并重新标记为Available。3.4 卷绑定模式WaitForFirstConsumer在云环境中非常重要如果 PVC 立即创建了可用区 A 的云盘但 Pod 被调度到可用区 BPod 将无法挂载这个云盘。延迟绑定确保了存储资源始终与 Pod 在同一可用区避免了跨可用区的存储挂载问题。四、实战为 Redis 配置动态持久化存储现在把理论应用到贯穿案例中。为 Redis 创建一个使用动态供给的 PVC确保 Redis 数据在 Pod 重建后不丢失。4.1 创建 PVCapiVersion: v1 kind: PersistentVolumeClaim metadata: name: redis-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: standardPVC 不指定任何具体的 PV 名称或节点路径只声明了“我要 1Gi、RWO 模式、使用 standard StorageClass”。剩下的全部自动化——StorageClass 调用 Provisioner 创建 PVK8s 将 PV 绑定到 PVC。kubectl apply-fredis-pvc.yaml kubectl get pvc# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE# redis-pvc Bound pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890 1Gi RWO standard 10s4.2 部署 Redis 使用 PVCapiVersion: apps/v1 kind: Deployment metadata: name: redis spec: replicas:1selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:alpine ports: - containerPort:6379volumeMounts: - name: redis-data mountPath: /data volumes: - name: redis-data persistentVolumeClaim: claimName: redis-pvc重要如果使用 RWO 模式单节点读写请务必将replicas设为 1。Redis Deployment 的多个副本会创建多个 Pod而 RWO 卷只能挂载到单个节点上——如果两个 Pod 被调度到不同节点第二个 Pod 将无法挂载同一个 PVC导致启动失败。多副本有状态应用应使用 StatefulSet每个 Pod 绑定独立的 PVC。4.3 验证持久性# 写入数据kubectlexecdeploy/redis -- redis-clisetcounter100# 删除 Pod不删除 PVCkubectl delete pod-lappredis# 等待新 Pod 启动kubectl get pods-lappredis-w# 验证数据仍然存在kubectlexecdeploy/redis -- redis-cli get counter# 100这就是 PVC 持久化的直观体现Pod 被删了PV 还在PVC 还在数据完好无损。新 Pod 通过同一个 PVC 挂载到同一个 PVRedis 启动时从 AOF 文件中恢复了之前的键值对。# 查看自动创建的 PVkubectl getpv# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS# pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890 1Gi RWO Delete Bound五、对比 Docker Compose 的存储方式Docker Compose 的命名卷适合单机场景但在跨主机、动态供给、访问控制方面有明显的局限性。这正是 K8s PV/PVC 体系的设计价值所在。六、PVC 扩容PVC 支持动态扩容需要 StorageClass 启用allowVolumeExpansion: truekubectl patch pvc redis-pvc-p{spec:{resources:{requests:{storage:2Gi}}}}# persistentvolumeclaim/redis-pvc patchedkubectl get pvc redis-pvc-w# 等待 CAPACITY 从 1Gi 变为 2Gi注意并非所有存储后端都支持扩容且只能扩容不能缩容。云存储通常支持在线扩容不会影响正在运行的 Pod。七、命令速查表八、本篇总结PV 和 PVC 的关系PV 是实际的存储资源PVC 是应用对存储的需求声明。PVC 绑定 PV 后Pod 通过 PVC 挂载持久化存储。这种抽象实现了管理员与开发者的职责分离。StorageClass 动态供给当 PVC 指定 StorageClass 时系统自动调用 Provisioner 创建 PV无需管理员预先准备存储。这是生产环境的标配。访问模式与 Pod 副本的关系RWO 卷只能挂载到单个节点多副本有状态应用应使用 StatefulSet后续第 27 篇相关主题的扩展内容每个 Pod 绑定独立的 PVC。与 Compose 的演进关系Compose 的命名卷是单机手动模式PVC 是集群自动化模式。思路一致卷的生命周期独立于容器但 K8s 在规模和自动化程度上提升了几个数量级。通过本篇Redis 的数据真正实现了跨 Pod 生命周期的持久化。下一篇——第 36 篇资源管理Requests、Limits 与 QoS我们将学习如何限制 Pod 的 CPU 和内存使用防止单个 Pod 过度消耗资源影响同节点的其他 Pod让集群的资源使用更加公平和可预测。想了解更多还可以去各个平台搜索「IT策士」一起升级 IT 思维
第 35 篇 k8s之PVC 与 StorageClass:动态存储供应
IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章助你少走弯路。在第 34 篇中我们学了 emptyDir 和 hostPath。它们解决的是“Pod 内部容器间共享文件”和“访问宿主机特定路径”的问题。但一个最核心的生产需求还没有解决Pod 重建后数据如何保留如果 Redis 的 Pod 被重新调度到另一台节点上emptyDir 和 hostPath 都无法将数据带过去。emptyDir 随 Pod 删除而销毁hostPath 只存在于特定节点上。我们需要一种与 Pod 生命周期解耦、可在集群任意节点访问的持久化存储。这正是 PVPersistentVolume持久卷和 PVCPersistentVolumeClaim持久卷声明的设计目标。在 Docker 生态中我们通过docker volume create来创建命名卷容器重启、删除都不会丢失数据。但在 K8s 集群中管理员不可能为每个应用手动在每台节点上创建 Volume——我们需要一种自动化的动态供给机制。今天这篇我们就从 PV/PVC 的核心概念讲起通过贯穿案例的 Redis 持久化存储深入理解 StorageClass 如何实现“声明即存储”。一、PV 和 PVC 的抽象模型1.1 为什么需要 PV 和 PVC在第 7 篇 Docker 数据管理中我们创建 Volume 的命令是docker volume create redis-data然后在docker run时用-v redis-data:/data挂载。这种方式在单机上工作良好但在 K8s 集群中有两个致命缺陷不跨节点Pod 重新调度到另一台节点时新节点上没有这个 Volume。人工运维管理员需要提前创建好 Volume开发者需要知道 Volume 的具体名称和配置。K8s 的解决方案是将存储抽象为两个独立的对象实现管理员与开发者的职责分离PVPersistentVolume集群管理员准备的存储资源可以来自 NFS、云存储AWS EBS、GCE PD、本地磁盘等。PV 与 Pod 生命周期无关——Pod 被删了PV 还在。PVCPersistentVolumeClaim开发者声明对存储的需求“我需要 1Gi 空间读写一次即可”。K8s 自动找匹配的 PV 绑定给这个 PVC。1.2 用“租房子”来理解 PV 和 PVC把 K8s 集群想象成一个租房市场PV是房东已装修好的房源一套具体的房子位于某小区某单元由管理员中介提前准备好。PVC是租客的求租需求——“我需要 50 平米、朝南的一居室”。租客不需要知道具体哪套房子只需要描述需求。K8s 的匹配引擎是中介自动将合适的房源PV匹配给租客需求PVC。匹配后这套房子就归这个租客独占使用。如果租客搬走了Pod 删除PVC 还在房子仍然保留PV 保留。新租客重建的 Pod可以通过同一个 PVC 继续使用同一套房子和里面的家具数据。1.3 PV 的三种供给方式在实际工作中静态供给只适合极少数固定存储资源已知的场景比如一个已经部署好的 NFS 服务器。生产环境的标配是动态供给——你只需要声明“我要 1Gi 的云盘”StorageClass 自动调用云 API 创建。二、静态供给理解 PV 和 PVC 的匹配机制在进入动态供给之前先通过一个静态供给的简单示例理解 PV 和 PVC 的绑定关系。2.1 创建一个 PVapiVersion: v1 kind: PersistentVolume metadata: name: task-pv-volume spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce hostPath: path: /mnt/data关键字段capacity.storage: 1GiPV 的容量accessModes: ReadWriteOnce访问模式单个节点读写hostPath在 Minikube 环境中使用 hostPath 模拟真实存储生产环境会替换为云存储如awsElasticBlockStore2.2 创建一个 PVCapiVersion: v1 kind: PersistentVolumeClaim metadata: name: task-pvc-claim spec: accessModes: - ReadWriteOnce resources: requests: storage: 1GiPVC 不指定具体 PV 名称只声明需求“我要 1Gi、单节点读写”。K8s 自动找到匹配的 PV 并绑定。kubectl apply-ftask-pv.yaml kubectl apply-ftask-pvc.yaml kubectl get pvc输出NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE task-pvc-claim Bound task-pv-volume 1Gi RWO 30sSTATUSBound表示 PVC 已成功绑定到task-pv-volume这个 PV。STORAGECLASS列为空表示这是静态供给没有用到 StorageClass。2.3 在 Pod 中使用 PVCvolumes: - name: task-storage persistentVolumeClaim: claimName: task-pvc-claimPod 引用 PVCPVC 绑定 PVPV 对应物理存储。这是三层引用链——开发者只需关心 PVC不必知道底层存储的具体位置和类型。如果你删除了这个 Pod重新创建时引用同一个 PVC依然能访问到原来的数据。2.4 访问模式大多数数据库MySQL、Redis需要 RWO 模式。共享文件存储如 NFS可以支持 RWX 模式。如果 Pod 被调度到不同节点上RWO 模式的 PV 将无法在新节点上挂载——这就是为什么数据库这类有状态应用需要用 StatefulSet第 27 篇并配合节点亲和性来管理。三、StorageClass 与动态供给静态供给的问题很明显管理员需要预先创建每个 PV开发者扩容时要找管理员创建新的 PV。当集群中有数百个应用、每个应用都需要存储时这根本不可行。StorageClass解决了这个问题。它定义了一个“存储模板”当 PVC 被创建时StorageClass 自动调用后端存储的 API 创建 PV并绑定给 PVC。3.1 StorageClass 的工作原理PVC 创建 → StorageClass → Provisioner 调用 API → 自动创建 PV → 绑定 PVC → Pod 挂载管理员只需要创建 StorageClass 对象一次性配置之后开发者创建 PVC 时PV 会自动生成。这正是“声明式存储”的核心思想——你不再需要关心 PV 是谁创建的、在哪创建的只需要在 PVC 中声明“我要什么”。3.2 Minikube 中的 StorageClassMinikube 默认自带一个 StorageClass输出NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE standard(default)k8s.io/minikube-hostpath Delete Immediate(default)集群的默认 StorageClassPVC 不指定storageClassName时自动使用PROVISIONER负责实际创建存储的组件minikube-hostpath在宿主机上创建目录模拟真实存储RECLAIMPOLICY: DeletePVC 被删除时PV 也被自动删除VOLUMEBINDINGMODE: ImmediatePVC 创建后立即绑定 PV不需要等待 Pod 调度3.3 删除策略Retain策略适用于需要保留数据做审计或恢复的场景。但 Retain 后 PV 变为Released状态不能被新的 PVC 自动绑定——需要管理员手动清除 PV 中的旧数据并重新标记为Available。3.4 卷绑定模式WaitForFirstConsumer在云环境中非常重要如果 PVC 立即创建了可用区 A 的云盘但 Pod 被调度到可用区 BPod 将无法挂载这个云盘。延迟绑定确保了存储资源始终与 Pod 在同一可用区避免了跨可用区的存储挂载问题。四、实战为 Redis 配置动态持久化存储现在把理论应用到贯穿案例中。为 Redis 创建一个使用动态供给的 PVC确保 Redis 数据在 Pod 重建后不丢失。4.1 创建 PVCapiVersion: v1 kind: PersistentVolumeClaim metadata: name: redis-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: standardPVC 不指定任何具体的 PV 名称或节点路径只声明了“我要 1Gi、RWO 模式、使用 standard StorageClass”。剩下的全部自动化——StorageClass 调用 Provisioner 创建 PVK8s 将 PV 绑定到 PVC。kubectl apply-fredis-pvc.yaml kubectl get pvc# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE# redis-pvc Bound pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890 1Gi RWO standard 10s4.2 部署 Redis 使用 PVCapiVersion: apps/v1 kind: Deployment metadata: name: redis spec: replicas:1selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:alpine ports: - containerPort:6379volumeMounts: - name: redis-data mountPath: /data volumes: - name: redis-data persistentVolumeClaim: claimName: redis-pvc重要如果使用 RWO 模式单节点读写请务必将replicas设为 1。Redis Deployment 的多个副本会创建多个 Pod而 RWO 卷只能挂载到单个节点上——如果两个 Pod 被调度到不同节点第二个 Pod 将无法挂载同一个 PVC导致启动失败。多副本有状态应用应使用 StatefulSet每个 Pod 绑定独立的 PVC。4.3 验证持久性# 写入数据kubectlexecdeploy/redis -- redis-clisetcounter100# 删除 Pod不删除 PVCkubectl delete pod-lappredis# 等待新 Pod 启动kubectl get pods-lappredis-w# 验证数据仍然存在kubectlexecdeploy/redis -- redis-cli get counter# 100这就是 PVC 持久化的直观体现Pod 被删了PV 还在PVC 还在数据完好无损。新 Pod 通过同一个 PVC 挂载到同一个 PVRedis 启动时从 AOF 文件中恢复了之前的键值对。# 查看自动创建的 PVkubectl getpv# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS# pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890 1Gi RWO Delete Bound五、对比 Docker Compose 的存储方式Docker Compose 的命名卷适合单机场景但在跨主机、动态供给、访问控制方面有明显的局限性。这正是 K8s PV/PVC 体系的设计价值所在。六、PVC 扩容PVC 支持动态扩容需要 StorageClass 启用allowVolumeExpansion: truekubectl patch pvc redis-pvc-p{spec:{resources:{requests:{storage:2Gi}}}}# persistentvolumeclaim/redis-pvc patchedkubectl get pvc redis-pvc-w# 等待 CAPACITY 从 1Gi 变为 2Gi注意并非所有存储后端都支持扩容且只能扩容不能缩容。云存储通常支持在线扩容不会影响正在运行的 Pod。七、命令速查表八、本篇总结PV 和 PVC 的关系PV 是实际的存储资源PVC 是应用对存储的需求声明。PVC 绑定 PV 后Pod 通过 PVC 挂载持久化存储。这种抽象实现了管理员与开发者的职责分离。StorageClass 动态供给当 PVC 指定 StorageClass 时系统自动调用 Provisioner 创建 PV无需管理员预先准备存储。这是生产环境的标配。访问模式与 Pod 副本的关系RWO 卷只能挂载到单个节点多副本有状态应用应使用 StatefulSet后续第 27 篇相关主题的扩展内容每个 Pod 绑定独立的 PVC。与 Compose 的演进关系Compose 的命名卷是单机手动模式PVC 是集群自动化模式。思路一致卷的生命周期独立于容器但 K8s 在规模和自动化程度上提升了几个数量级。通过本篇Redis 的数据真正实现了跨 Pod 生命周期的持久化。下一篇——第 36 篇资源管理Requests、Limits 与 QoS我们将学习如何限制 Pod 的 CPU 和内存使用防止单个 Pod 过度消耗资源影响同节点的其他 Pod让集群的资源使用更加公平和可预测。想了解更多还可以去各个平台搜索「IT策士」一起升级 IT 思维