StorageClass 让 Kubernetes 的存储不只是“有一块盘”,而是变成“按策略提供什么样的盘”。它决定 PVC 如何被动态创建、默认会拿到什么存储、删除后底层卷怎么处理,以及在多可用区环境里如何和调度配合。
StorageClass 到底控制什么
- 用哪个 provisioner 创建卷
- 底层参数是什么
- 删除后的回收方式是什么
- 在有拓扑约束时怎么绑定
- 是否允许扩容
换句话说,StorageClass 决定了 PVC 背后的默认存储契约。
为什么它很重要
StorageClass 选错了,最常见的结果有三种:
- PVC 一直
Pending - 数据被误删
- 业务无意中吃到过贵或过慢的存储
所以它并不只是“平台同学的对象”,而是会直接影响应用上线体验的基础配置。
它也是这篇文章和 kubernetes-quickstart-pv-pvc.md 之间最关键的连接点:PVC 负责提出存储需求,而 StorageClass 决定由哪个 provisioner 或 CSI 驱动去动态创建底层卷。
最小示例
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: csi.example.com
parameters:
type: ssd
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
最值得先理解的几个字段
provisioner
它标识底层存储插件,通常是某个 CSI 驱动。
如果 provisioner 不存在或状态异常,PVC 很可能永远不会成功绑定。
reclaimPolicy
Delete:删除 claim 时,底层卷也一起删Retain:删除 claim 后保留底层卷,等待人工处理
这个字段和数据是否容易被误删直接相关。
volumeBindingMode
在多可用区或拓扑敏感的存储里,这个字段很重要:
Immediate:PVC 创建时立刻绑定WaitForFirstConsumer:等 Pod 真正被调度时再决定最终存储位置
对于 zonal 存储,WaitForFirstConsumer 往往更稳。
allowVolumeExpansion
表示后续是否允许扩容。这个功能很好用,但也要确认底层后端和文件系统是否真的支持完整扩容流程。
默认 StorageClass 的影响
很多集群都会设置默认 StorageClass。PVC 没写 storageClassName 时,就会自动落到默认类上。
这很方便,但也很危险:一旦大家默认以为“默认类就是对的”,平台稍微调整一下默认值,业务就可能默默吃到另一种存储。
所以,别把默认类当成“理所当然”。
一个够用的起步策略
对很多团队来说,刚开始两类 StorageClass 就够了:
standard:通用型、成本更稳fast:更高性能,给数据库或延迟敏感场景
过早搞一堆层级很复杂的类,往往只会把平台和应用团队都绕晕。
StorageClass 和 PV/PVC 的关系
- PVC:应用说自己要什么
- PV:最终得到的那块存储资源
- StorageClass:平台决定怎么满足这份请求
这个分层的好处是:应用 YAML 可以相对稳定,而底层存储实现可以逐步演进。
如果你想先从“应用怎么申请存储”这个角度理解,可以先读 kubernetes-quickstart-pv-pvc.md;如果你更关心“平台到底怎么把卷造出来”,那就从这篇继续往下看。
社区里常见的 provisioner / CSI 驱动
不同环境下,团队常用的 provisioner 会不一样。下面这些是社区里最常见的一批:
rancher/local-path-provisioner
- GitHub:
https://github.com/rancher/local-path-provisioner - 适合:本地实验、K3s、轻量开发集群
- 常见部署方式:K3s 默认内置,或者直接应用官方 manifest
- 特点:上手很快,但不适合追求多节点高可用的数据场景
kubernetes-sigs/nfs-subdir-external-provisioner
- GitHub:
https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner - 适合:基于现有 NFS 的共享 RWX 存储场景
- 常见部署方式:Helm 或官方 manifest
- 特点:共享读写方便,但性能和锁行为高度依赖 NFS 后端质量
longhorn/longhorn
- GitHub:
https://github.com/longhorn/longhorn - 适合:裸金属或自建集群里的分布式块存储
- 常见部署方式:Helm 或 Longhorn 官方安装流程
- 特点:很符合 Kubernetes 运维习惯,但依然需要认真做容量与副本规划
rook/rook + Ceph CSI
- GitHub:
https://github.com/rook/rook - 适合:希望在 Kubernetes 内部运行完整分布式存储平台的团队
- 常见部署方式:先部署 Rook operator,再创建 Ceph 集群和对应 StorageClass
- 特点:功能很强,但复杂度和运维门槛也明显更高
kubernetes-sigs/aws-ebs-csi-driver
- GitHub:
https://github.com/kubernetes-sigs/aws-ebs-csi-driver - 适合:EKS 或 AWS 上基于 EBS 的块存储场景
- 常见部署方式:EKS add-on 或 Helm
- 特点:AWS 场景下很常见,但要特别注意可用区和
WaitForFirstConsumer这类拓扑行为
一般的安装顺序是什么
实际工作里,通常不是先创建 StorageClass,而是按这个顺序来:
- 先安装 provisioner / CSI 驱动
- 确认 controller 和 node 组件状态正常
- 再创建引用该 provisioner 的 StorageClass
- 最后拿一个测试 PVC 验证动态供给是否真的可用
顺序很重要。StorageClass 可以先存在,但如果后面的 provisioner 根本没跑起来,PVC 一样会一直卡住。
常见 CSI StorageClass 示例
Longhorn 示例
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate
Rook Ceph RBD 示例
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ceph-rbd
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
clusterID: rook-ceph
pool: replicapool
imageFormat: "2"
imageFeatures: layering
reclaimPolicy: Delete
allowVolumeExpansion: true
AWS EBS CSI 示例
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
provisioner: ebs.csi.aws.com
parameters:
type: gp3
fsType: ext4
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
这些示例只是起点,不是通用默认值。真正上生产前,一定要对照对应驱动文档确认 provisioner 名称、参数、回收策略和拓扑行为。
常见故障模式
PVC 一直 Pending
优先检查:
- StorageClass 存不存在
- provisioner 是否正常工作
- 请求的容量和访问模式是否被支持
- 是否有拓扑约束阻塞创建
分配到了不合适的存储层级
很多时候并不是 PVC 写错,而是默认 StorageClass 指向了和你预期不同的类。
清理资源时顺手把数据删了
这类事故里,回收策略几乎总是关键因素之一。
拓扑和调度为什么会扯到一起
有些存储后端是按可用区绑定的。如果卷过早在错误的 zone 创建出来,后续 Pod 调度就会很别扭,甚至永远挂不上。
这也是为什么 volumeBindingMode 看起来只是个小字段,实际上决定了调度和存储是合作还是互相打架。
性能和成本取舍
StorageClass 也是最容易无意中烧钱的地方之一:
- 高 IOPS 类存储看起来最稳,但成本可能很高
- 普通网络盘更灵活,但不一定扛得住高写入负载
- 很多团队不是存储不够,而是性能等级买高了
最好基于真实指标和压测来选,而不是凭直觉给所有业务都上“最快那档”。
一套很实用的验证动作
在正式让业务使用一个新的 StorageClass 之前,至少跑一次小型验证:
kubectl get storageclass
kubectl apply -f pvc-test.yaml
kubectl get pvc
kubectl describe pvc test-claim
kubectl get pv
这组烟雾测试通常能提前暴露 provisioner 问题、回收策略误配和拓扑冲突。
如果你想从工作负载角度看这套流程怎么落到 PVC 上,可以直接对照 kubernetes-quickstart-pv-pvc.md 里的 claim 示例一起看,会更容易把 StorageClass 和动态供给串起来。
FAQ
Q: PVC 一定要显式写 storageClassName 吗?
A: 不一定,但前提是你非常清楚默认 StorageClass 指向什么,以及它为什么适合你的业务。
Q: 什么情况下更适合用 Retain?
A: 当误删成本高、你希望保留恢复窗口,或者需要人工确认后再处理底层卷时。
Q: 为什么 WaitForFirstConsumer 经常更推荐?
A: 因为它会等 Pod 真正被调度时再确定卷的落点,更适合有可用区和拓扑约束的环境。
下一步阅读
- 如果你想从应用视角理解存储,接着读
kubernetes-quickstart-pv-pvc.md - 如果你要跑有状态工作负载,再读
kubernetes-quickstart-statefulset.md - 如果你要上数据库,再继续看 MySQL 系列
收个尾
StorageClass 看起来像一个“默认配置对象”,但它很容易默默地决定业务后续会遇到什么样的存储体验。只要把默认类、回收策略和绑定模式看清楚,就能少掉很多没必要的存储事故。