【Docker-Day 31】告别手动创建 PV!一文搞懂 Kubernetes StorageClass 工作原理与实战
Langchain系列文章目录
01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来
Python系列文章目录
PyTorch系列文章目录
机器学习系列文章目录
深度学习系列文章目录
Java系列文章目录
JavaScript系列文章目录
Python系列文章目录
Go语言系列文章目录
Docker系列文章目录
01-【Docker-Day 1】告别部署噩梦:为什么说 Docker 是每个开发者的必备技能?
02-【Docker-Day 2】从零开始:手把手教你在 Windows、macOS 和 Linux 上安装 Docker
03-【Docker-Day 3】深入浅出:彻底搞懂 Docker 的三大核心基石——镜像、容器与仓库
04-【Docker-Day 4】从创建到删除:一文精通 Docker 容器核心操作命令
05-【Docker-Day 5】玩转 Docker 镜像:search, pull, tag, rmi 四大金刚命令详解
06-【Docker-Day 6】从零到一:精通 Dockerfile 核心指令 (FROM, WORKDIR, COPY, RUN)
07-【Docker-Day 7】揭秘 Dockerfile 启动指令:CMD、ENTRYPOINT、ENV、ARG 与 EXPOSE 详解
08-【Docker-Day 8】高手进阶:构建更小、更快、更安全的 Docker 镜像
09-【Docker-Day 9】实战终极指南:手把手教你将 Node.js 应用容器化
10-【Docker-Day 10】容器的“持久化”记忆:深入解析 Docker 数据卷 (Volume)
11-【Docker-Day 11】Docker 绑定挂载 (Bind Mount) 实战:本地代码如何与容器实时同步?
12-【Docker-Day 12】揭秘容器网络:深入理解 Docker Bridge 模式与端口映射
13-【Docker-Day 13】超越默认Bridge:精通Docker Host、None与自定义网络模式
14-【Docker-Day 14】Docker Compose深度解析
15-【Docker-Day 15】一键部署 WordPress!Docker Compose 实战终极指南
16-【Docker-Day 16】告别单机时代:为什么 Docker Compose 不够用,而你需要 Kubernetes?
17-【Docker-Day 17】K8s 架构全解析:深入理解 Kubernetes 的大脑 (Master) 与四肢 (Node)
18-【Docker-Day 18】告别选择困难症:一文掌握 Minikube、kind、k3d,轻松搭建你的第一个 K8s 集群
19-【Docker-Day 19】万物皆 YAML:掌握 Kubernetes 声明式 API 的艺术
20-【Docker-Day 20】揭秘 Kubernetes 的原子单位:深入理解 Pod
21-【Docker-Day 21】Pod的守护神:ReplicaSet与ReplicationController,轻松实现应用高可用
22-【K8s-Day 22】深入解析 Kubernetes Deployment:现代应用部署的基石与滚动更新的艺术
23-【K8s-Day 23】从 Pod 的“失联”到 Service 的“牵线”:深入理解 ClusterIP 核心原理
24-【Docker-Day 24】K8s网络解密:深入NodePort与LoadBalancer,让你的应用走出集群
25-【Docker-Day 25】深入理解 Kubernetes Namespace:实现多租户与环境隔离的利器
26-【Docker-Day 26】K8s实战演练:从零开始部署一个完整的前后端分离Web应用
27-【K8s-Day 27】应用的“体检医生”:深入解析 Kubernetes 健康检查探针 (Probe)
28-【Docker-Day 28】K8s 核心配置管理:解密 ConfigMap,告别硬编码!
29-【Docker-Day 29】K8s 安全第一课:揭秘敏感信息管理器 Secret
30-【Docker-Day 30】解密 K8s 的“硬盘”:深入理解 PersistentVolume (PV) 与 PersistentVolumeClaim (PVC)
31-【Docker-Day 31】告别手动创建 PV!一文搞懂 Kubernetes StorageClass 工作原理与实战
文章目录
摘要
在上一篇文章中,我们学习了 Kubernetes 中通过 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) 来为有状态应用提供持久化存储的机制。然而,其中涉及的 PV 需要集群管理员预先手动创建,这种“静态供给”模式在大型、动态的云原生环境中显得既繁琐又低效。本文将深入探讨 Kubernetes 存储体系中的一个关键角色——StorageClass,它实现了存储资源的“动态供给”(Dynamic Provisioning)。我们将从其核心概念、工作原理解析入手,通过详尽的 YAML 字段说明和实战演练,带你彻底掌握如何利用 StorageClass 自动化创建和管理存储资源,让你的 K8s 集群存储管理能力迈上一个新台阶。
一、回顾:手动管理存储的痛点
在深入了解 StorageClass 之前,让我们先快速回顾一下上一章介绍的静态存储供给模式,并明确其局限性,这能帮助我们更好地理解 StorageClass 诞生的意义。
1.1 静态供给 (Static Provisioning) 的流程回顾
在静态供给模式下,数据持久化的完整流程如下:
- 集群管理员(Admin):根据底层可用的存储资源(如 NFS、Ceph、云厂商的磁盘),手动创建一个或多个
PersistentVolume(PV) 对象。这些 PV 包含了存储的详细信息,如容量、访问模式、存储类型等。 - 开发者(Developer):当应用需要存储时,创建一个
PersistentVolumeClaim(PVC) 对象,声明所需的存储容量和访问模式,但不关心具体的存储实现。 - Kubernetes 控制平面:扮演“中间人”的角色,根据 PVC 的要求在已存在的 PV 池中寻找一个满足条件的 PV,并将它们绑定(Bind)在一起。
- 应用 Pod:在 Pod 的定义中引用该 PVC,从而挂载并使用这块持久化存储。
1.2 手动模式的挑战
虽然静态供给模式逻辑清晰,但在实际应用中,尤其是在大规模和自动化的场景下,其弊端十分明显:
- 运维负担重:集群管理员必须预先规划并手动创建所有可能被用到的 PV。当存储需求频繁变化时,这项工作会变得异常繁重和耗时。
- 响应速度慢:开发者需要存储时,必须等待管理员创建好合适的 PV。这个过程依赖于人工沟通和操作,无法实现“按需即用”。
- 资源利用率低:管理员可能需要预先分配大量各种规格的 PV 以备不时之需,这可能导致部分存储资源长期闲置,造成浪费。
- 扩展性差:在公有云环境中,我们希望能够利用云厂商提供的弹性存储能力,按需创建磁盘。静态供给模式完全无法发挥这一优势。
为了解决这些问题,Kubernetes 引入了 StorageClass 和动态存储供给机制,将存储管理从手动挡升级为自动挡。
二、StorageClass 登场:什么是动态存储供给?
StorageClass 为集群管理员提供了一种描述“存储类别”的方法。你可以把它想象成一个创建 PV 的“模板”或“工厂”。当用户请求存储时,这个工厂会根据预设的模板自动生产出符合要求的 PV。
2.1 StorageClass 的核心定义
StorageClass,顾名思义,是存储的“类别”。它不是一块具体的存储,而是一个抽象的定义,它描述了:
- 由谁提供 (Provisioner):这块存储应该由哪个存储插件(如 AWS EBS, GCE PD, Ceph RBD)来创建。
- 提供什么样的 (Parameters):创建时需要附带哪些参数(如磁盘类型是
SSD还是HDD,文件系统是ext4还是xfs等)。 - 回收策略 (Reclaim Policy):当与之绑定的 PVC 被删除后,这个动态创建的 PV 应该被保留(
Retain)还是删除(Delete)。
通过定义不同的 StorageClass,管理员可以为用户提供多种存储选项,例如 fast-ssd、slow-hdd、backup-storage 等,而无需关心每一个 PV 的具体创建细节。
2.2 动态供给 (Dynamic Provisioning) 的工作流程
引入 StorageClass 后,存储供给的流程发生了根本性的变化,实现了完全自动化:
(1) 用户请求 (PVC)
开发者创建一个 PVC,除了指定容量和访问模式外,最关键的是通过 storageClassName 字段指定了想要的“存储类别”。
(2) SC 介入
Kubernetes 控制平面接收到 PVC 请求后,发现它指定了 storageClassName,于是会找到集群中同名的 StorageClass 资源。
(3) Provisioner 行动
Kubernetes 不会自己去创建存储。它会根据 StorageClass 中定义的 provisioner(供给者),调用相应的存储插件。这个插件才是真正与底层存储基础设施(如云 API、存储系统 API)交互的组件。
(4) 绑定完成
存储插件根据 StorageClass 中的 parameters 创建好底层的存储(例如,在 AWS 上创建一块新的 EBS 卷),并将其信息包装成一个 PV 对象。随后,Kubernetes 会自动将这个新创建的 PV 与用户的 PVC 进行绑定。
从开发者的角度看,他们只需提交一个 PVC,几秒或几十秒后,一个可用的、满足其需求的持久化存储就准备就绪了。
2.3 可视化工作流程
下面我们使用 Mermaid 流程图来直观地展示动态供给的全过程:
三、深入剖析 StorageClass 资源
要精通 StorageClass,必须理解其 YAML 定义中的各个关键字段。
3.1 YAML 文件结构详解
一个典型的 StorageClass YAML 文件如下所示:
# apiVersion 是必须的,对于 StorageClass,它是 storage.k8s.io/v1
apiVersion: storage.k8s.io/v1
# kind 也是必须的,指明这是一个 StorageClass 资源
kind: StorageClass
# metadata 中定义了该 StorageClass 的元数据,最重要的是名称
metadata:
name: standard-ssd
# annotations (注解)可以用来设置默认 StorageClass,后续会讲
# annotations:
# storageclass.kubernetes.io/is-default-class: "true"
# provisioner 指定了用于动态供给的卷插件
provisioner: kubernetes.io/aws-ebs
# parameters 包含了供给者需要的特定参数
parameters:
type: gp2 # AWS EBS 的卷类型,gp2 是通用型 SSD
fsType: ext4 # 文件系统类型
# reclaimPolicy 定义了 PV 的回收策略
reclaimPolicy: Delete # 当 PVC 删除时,PV 和底层存储也会被删除
# allowVolumeExpansion 允许用户在线扩展卷的大小
allowVolumeExpansion: true
# mountOptions 指定了卷的挂载选项
mountOptions:
- debug
# volumeBindingMode 控制卷的绑定和动态供给时机
volumeBindingMode: Immediate
3.2 关键字段解析
3.2.1 provisioner:供给者的身份
这是 StorageClass 最核心的字段,它决定了由谁来创建 PV。Kubernetes 内置了一些供给者,通常以 kubernetes.io/ 开头,例如:
| Provisioner | 对应存储 |
|---|---|
kubernetes.io/aws-ebs |
AWS ElasticBlockStore |
kubernetes.io/gce-pd |
Google Compute Engine Persistent Disk |
kubernetes.io/azure-disk |
Microsoft Azure Disk |
kubernetes.io/cinder-volume |
OpenStack Cinder |
kubernetes.io/nfs |
NFS (需要外部供给者) |
kubernetes.io/csi |
Container Storage Interface (CSI) 驱动 |
随着 CSI 成为标准,新的存储集成都通过外部 CSI 驱动来实现,其 provisioner 名称通常是反向域名格式,如 ebs.csi.aws.com 或 disk.csi.azure.com。
3.2.2 parameters:定制化的存储参数
这个字段的内容完全取决于 provisioner。每个存储插件都有自己的一套参数。例如:
- 对于
kubernetes.io/aws-ebs,你可以指定type(gp2, io1, st1),iopsPerGB(for io1),encrypted(true/false) 等。 - 对于 NFS 类型的供给者,可能需要指定
server地址和path。 - 对于 Ceph RBD,可能需要
monitors,pool,userId等。
在使用时,务必查阅对应存储插件的官方文档以了解所有可用的参数。
3.2.3 reclaimPolicy:PV 的回收策略
此字段定义了当与其绑定的 PVC 被删除后,动态创建的 PV 何去何从。
Delete(默认值): 当 PVC 被删除时,PV 和后端的物理存储(如 AWS EBS 卷)都会被 自动删除。这在开发测试或需要频繁创建销毁存储的场景下非常方便,但也需 极其小心,以防误删重要数据。Retain: 当 PVC 被删除时,PV 对象依然存在(状态变为Released),后端的物理存储也会被 保留。这为数据恢复提供了机会。管理员可以手动清理这些资源,或者将其数据备份后,删除 PV 再重新绑定给新的 PVC。
生产环境建议:对于重要数据,优先考虑使用 Retain 策略,并配合完善的备份机制。
3.2.4 allowVolumeExpansion:允许卷扩展
如果设置为 true,用户后续可以通过修改 PVC 的 spec.resources.requests.storage 字段来扩容卷。前提是底层的 provisioner 支持此功能。
3.2.5 volumeBindingMode:卷绑定模式
这是一个非常重要的性能和成本优化选项。
Immediate(默认值): 一旦 PVC 被创建,StorageClass就会立即触发 PV 的创建和绑定,不管这个 PVC 是否有 Pod 在使用。WaitForFirstConsumer: 这是推荐的模式。它会延迟 PV 的创建和绑定,直到第一个使用该 PVC 的 Pod 被调度。这样做有两大好处:- 确保存储位置亲和性:调度器在为 Pod 选择节点时,会同时考虑 Pod 的资源需求和存储的拓扑约束(如可用区)。它可以确保 Pod 和它要使用的存储位于同一个可用区,避免跨区访问带来的延迟和额外成本。
- 节约成本:如果 PVC 创建后长时间没有被 Pod 使用,
Immediate模式下存储资源已经产生并开始计费,而WaitForFirstConsumer则避免了这种情况。
四、实战演练:使用 StorageClass 动态创建 PV
接下来,我们通过一个完整的实例来演示 StorageClass 的工作流程。我们将使用 Minikube 自带的 standard StorageClass。
4.1 环境准备与检查
在你的 K8s 集群中(如 Minikube, kind, Docker Desktop),首先查看已有的 StorageClass。
# 查看集群中所有的 StorageClass
kubectl get storageclass
# 或者简写
kubectl get sc
在 Minikube 中,你可能会看到类似下面的输出:
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
standard (default) k8s.io/minikube-hostpath Delete Immediate false 10d
这表示 Minikube 有一个名为 standard 的默认 StorageClass,它使用 hostpath 供给者(在节点上创建一个目录作为存储),回收策略是 Delete。
4.2 定义一个 StorageClass(可选)
虽然 Minikube 已有默认的,我们也可以自己创建一个。假设我们要创建一个模拟的 “slow-hdd” 存储类别。由于我们仍使用 Minikube,provisioner 保持不变,只是改个名字和参数(虽然 hostpath 没什么参数可配,这里仅为演示)。
slow-hdd-sc.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow-hdd
provisioner: k8s.io/minikube-hostpath
reclaimPolicy: Retain # 改为 Retain 以观察不同行为
volumeBindingMode: WaitForFirstConsumer
应用它:
kubectl apply -f slow-hdd-sc.yaml
4.3 创建 PersistentVolumeClaim (PVC)
现在,我们创建一个 PVC,明确要求使用我们刚定义的 slow-hdd 这个 StorageClass。
my-app-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-app-pvc
spec:
# 关键点:指定使用哪个 StorageClass
storageClassName: slow-hdd
accessModes:
- ReadWriteOnce # 访问模式
resources:
requests:
storage: 1Gi # 请求 1 GiB 的存储空间
应用它:
kubectl apply -f my-app-pvc.yaml
4.4 验证动态供给结果
因为我们设置了 volumeBindingMode: WaitForFirstConsumer,此时查看 PVC 和 PV 会有特殊现象。
# 查看 PVC 状态
kubectl get pvc my-app-pvc
你会看到 PVC 的状态是 Pending,因为还没有 Pod 使用它。
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-app-pvc Pending slow-hdd 10s
此时查看 PV,你会发现 没有任何 新的 PV 被创建出来。
4.5 在 Pod 中使用 PVC
现在,我们创建一个 Pod 来消费这个 PVC。
my-app-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
spec:
containers:
- name: my-app-container
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: my-storage
mountPath: /usr/share/nginx/html
volumes:
- name: my-storage
# Pod 通过 PVC 的名字来引用它
persistentVolumeClaim:
claimName: my-app-pvc
部署这个 Pod:
kubectl apply -f my-app-pod.yaml
在 Pod 被调度后,StorageClass 的魔力才真正开始展现。再次检查 PVC 和 PV:
# 再次查看 PVC
kubectl get pvc my-app-pvc
输出会变为:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-app-pvc Bound pvc-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 1Gi RWO slow-hdd 2m
PVC 状态变成了 Bound,并关联了一个自动生成的 PV。
# 查看 PV
kubectl get pv
输出会显示一个新创建的 PV:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 1Gi RWO Retain Bound default/my-app-pvc slow-hdd 30s
注意这个 PV 的 RECLAIM POLICY 是 Retain,STORAGECLASS 是 slow-hdd,并且 CLAIM 字段指向了我们的 default/my-app-pvc。这完美地证明了动态供给的全过程。
五、默认 StorageClass 的妙用
5.1 为何需要默认 StorageClass
在大型组织中,为了简化开发者的工作,管理员通常会设定一个“标准”或“默认”的 StorageClass。这样,开发者在创建 PVC 时,如果不指定 storageClassName,Kubernetes 会自动使用这个默认的 StorageClass 来为他们供给存储。
5.2 如何设置默认 StorageClass
通过一个注解 storageclass.kubernetes.io/is-default-class: "true" 即可将一个 StorageClass 设为默认。
# 将我们之前创建的 slow-hdd 设为默认
kubectl patch storageclass slow-hdd -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
# 将原来的 standard 移除默认(一个集群中最好只有一个默认)
kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
设置后,如果你创建一个不带 storageClassName 的 PVC,Kubernetes 将会自动使用 slow-hdd 来创建 PV。
六、静态供给 vs. 动态供给:全面对比
为了加深理解,我们用一个表格来清晰地对比这两种存储供给模式。
| 特性 | 静态供给 (Static Provisioning) | 动态供给 (Dynamic Provisioning) |
|---|---|---|
| 核心机制 | 管理员预创建 PV,K8s 匹配 PVC 与 PV | 管理员创建 StorageClass,K8s 按需创建 PV |
| 运维复杂度 | 高,需手动管理每个 PV 的生命周期 | 低,只需维护少数几个 StorageClass |
| 响应速度 | 慢,依赖人工操作 | 快,自动化,按需即用 |
| 资源利用率 | 较低,可能因预分配导致资源闲置 | 高,仅在需要时创建存储 |
| 灵活性 | 差,存储类型和大小受限于已创建的 PV | 高,可定义多种存储类别,轻松应对不同需求 |
| 适用场景 | 管理已存在的、特殊的存储设备;小规模或测试环境 | 云原生环境、大规模集群、自动化 CI/CD 流程 |
| 关联对象 | PersistentVolume, PersistentVolumeClaim |
StorageClass, PersistentVolumeClaim |
七、总结
StorageClass 是 Kubernetes 存储体系中实现自动化和抽象化的关键一环,是从传统运维迈向云原生运维的标志性功能。通过本文的学习,我们应掌握以下核心要点:
- 解决了什么问题:
StorageClass通过动态供给机制,解决了静态供给模式下手动创建 PV 带来的运维负担重、响应慢、效率低的问题。 - 核心工作原理:
StorageClass充当 PV 的“工厂模板”,当用户提交带有storageClassName的 PVC 时,它会调用指定的provisioner插件来自动创建底层存储和对应的 PV,并完成绑定。 - 关键配置:
provisioner定义了供给者,parameters提供了定制化选项,reclaimPolicy(Delete/Retain) 决定了回收行为,而volumeBindingMode: WaitForFirstConsumer是实现拓扑感知和成本优化的最佳实践。 - 实用价值:
StorageClass极大地简化了开发者的存储使用体验,提升了运维效率,并能充分利用云厂商的弹性存储能力,是构建现代化、高可用有状态应用不可或缺的组件。
掌握了 StorageClass,你就掌握了在 Kubernetes 中进行大规模、自动化存储管理的钥匙。在接下来的文章中,我们将继续探讨更复杂的有状态应用部署模式,如 StatefulSet。
更多推荐


所有评论(0)