IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。
在第 34 篇中,我们学了 emptyDir 和 hostPath。它们解决的是“Pod 内部容器间共享文件”和“访问宿主机特定路径”的问题。但一个最核心的生产需求还没有解决:Pod 重建后,数据如何保留?
如果 Redis 的 Pod 被重新调度到另一台节点上,emptyDir 和 hostPath 都无法将数据带过去。emptyDir 随 Pod 删除而销毁,hostPath 只存在于特定节点上。我们需要一种与 Pod 生命周期解耦、可在集群任意节点访问的持久化存储。
这正是 PV(PersistentVolume,持久卷)和 PVC(PersistentVolumeClaim,持久卷声明)的设计目标。在 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 的解决方案是将存储抽象为两个独立的对象,实现管理员与开发者的职责分离:
-
PV(PersistentVolume):集群管理员准备的存储资源,可以来自 NFS、云存储(AWS EBS、GCE PD)、本地磁盘等。PV 与 Pod 生命周期无关——Pod 被删了,PV 还在。
-
PVC(PersistentVolumeClaim):开发者声明对存储的需求:“我需要 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 创建一个 PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: task-pv-volume
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /mnt/data
关键字段:
-
capacity.storage: 1Gi:PV 的容量 -
accessModes: ReadWriteOnce:访问模式,单个节点读写 -
hostPath:在 Minikube 环境中使用 hostPath 模拟真实存储,生产环境会替换为云存储(如awsElasticBlockStore)
2.2 创建一个 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: task-pvc-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
PVC 不指定具体 PV 名称,只声明需求:“我要 1Gi、单节点读写”。K8s 自动找到匹配的 PV 并绑定。
kubectl apply -f task-pv.yaml
kubectl apply -f task-pvc.yaml
kubectl get pvc
输出:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
task-pvc-claim Bound task-pv-volume 1Gi RWO 30s
STATUS=Bound 表示 PVC 已成功绑定到 task-pv-volume 这个 PV。STORAGECLASS 列为空,表示这是静态供给,没有用到 StorageClass。
2.3 在 Pod 中使用 PVC
volumes:
- name: task-storage
persistentVolumeClaim:
claimName: task-pvc-claim
Pod 引用 PVC,PVC 绑定 PV,PV 对应物理存储。这是三层引用链——开发者只需关心 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 中的 StorageClass
Minikube 默认自带一个 StorageClass:
输出:
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE
standard (default) k8s.io/minikube-hostpath Delete Immediate
-
(default):集群的默认 StorageClass,PVC 不指定storageClassName时自动使用 -
PROVISIONER:负责实际创建存储的组件,minikube-hostpath在宿主机上创建目录模拟真实存储 -
RECLAIMPOLICY: Delete:PVC 被删除时,PV 也被自动删除 -
VOLUMEBINDINGMODE: Immediate:PVC 创建后立即绑定 PV,不需要等待 Pod 调度
3.3 删除策略
Retain 策略适用于需要保留数据做审计或恢复的场景。但 Retain 后 PV 变为 Released 状态,不能被新的 PVC 自动绑定——需要管理员手动清除 PV 中的旧数据并重新标记为 Available。
3.4 卷绑定模式
WaitForFirstConsumer 在云环境中非常重要:如果 PVC 立即创建了可用区 A 的云盘,但 Pod 被调度到可用区 B,Pod 将无法挂载这个云盘。延迟绑定确保了存储资源始终与 Pod 在同一可用区,避免了跨可用区的存储挂载问题。
四、实战:为 Redis 配置动态持久化存储
现在把理论应用到贯穿案例中。为 Redis 创建一个使用动态供给的 PVC,确保 Redis 数据在 Pod 重建后不丢失。
4.1 创建 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: standard
PVC 不指定任何具体的 PV 名称或节点路径,只声明了“我要 1Gi、RWO 模式、使用 standard StorageClass”。剩下的全部自动化——StorageClass 调用 Provisioner 创建 PV,K8s 将 PV 绑定到 PVC。
kubectl apply -f redis-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 10s
4.2 部署 Redis 使用 PVC
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:alpine
ports:
- containerPort: 6379
volumeMounts:
- 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 验证持久性
# 写入数据
kubectl exec deploy/redis -- redis-cli set counter 100
# 删除 Pod(不删除 PVC)
kubectl delete pod -l app=redis
# 等待新 Pod 启动
kubectl get pods -l app=redis -w
# 验证数据仍然存在
kubectl exec deploy/redis -- redis-cli get counter
# "100"
这就是 PVC 持久化的直观体现:Pod 被删了,PV 还在,PVC 还在,数据完好无损。新 Pod 通过同一个 PVC 挂载到同一个 PV,Redis 启动时从 AOF 文件中恢复了之前的键值对。
# 查看自动创建的 PV
kubectl get pv
# 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: true):
kubectl patch pvc redis-pvc -p '{"spec":{"resources":{"requests":{"storage":"2Gi"}}}}'
# persistentvolumeclaim/redis-pvc patched
kubectl 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 思维 !