上一篇【第37篇】PV和PVC——存储的"供需匹配"平台
下一篇【第39篇】本地持久化存储——Local PV和hostPath的正确使用姿势
摘要
你走进一家自助餐厅。传统模式(静态PV供应)是:你告诉服务员"我要吃菜",服务员跑到后厨手动做,你等着。StorageClass模式是:你走到菜品区,自己拿——想拿多少拿多少,想拿什么拿什么,系统自动补货。
StorageClass就是K8s存储的"自助餐系统"。你只需要在PVC里写一行storageClassName: fast-ssd,K8s就自动去找对应的StorageClass,调用Provisioner在云平台上创建一个真实的存储卷(比如AWS EBS),然后自动创建PV、自动绑定PVC——全程零人工介入。
本文把StorageClass从里到外讲透:(1)Provisioner这个"大厨"怎么工作;(2)parameters参数怎么把"要几分熟"的要求传递到存储后端;(3)volumeBindingMode的两种模式:Immediate(立即绑定)和WaitForFirstConsumer(等有人来了再绑),各自的利弊场景;(4)云厂商的StorageClass最佳实践。
一、没有StorageClass的世界有多痛苦
先回顾一下上一篇静态供应的痛:
# 静态供应——运维的"日常噩梦"# 第1天:开发说"我要部署10个微服务"kubectl apply-fpv-service1.yaml# 手动建PV-1kubectl apply-fpv-service2.yaml# 手动建PV-2# ...手动建10个PV...# 第3天:开发说"新需求,再加5个服务"# 运维:"操,又要建PV"(心态崩了)# 第7天:开发说"3个服务下线了,PV可以回收了"# 运维:"操,又要清理PV"(心态二次崩了)StorageClass一句话解决问题:你写一个PVC,剩下的全自动。
# 有了StorageClass——开发只需要这样写apiVersion:v1kind:PersistentVolumeClaimmetadata:name:my-app-dataspec:storageClassName:fast-ssd# ← 就这一行!剩下的全自动accessModes:-ReadWriteOnceresources:requests:storage:10Gi# 几秒后:# 1. K8s找到 fast-ssd 这个StorageClass# 2. 调用对应的Provisioner(比如AWS EBS CSI Driver)# 3. Provisioner调用AWS API创建10G的EBS卷# 4. Provisioner自动创建PV对象# 5. PV自动绑定到PVC# 6. 你的Pod直接mount上去用# 全程不用运维!不用等审批!不用写NFS地址!二、StorageClass的解剖——"自助餐"菜单怎么写
2.1 StorageClass最简示例
# StorageClass——最简配置apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:fast-ssd# SC的名字,PVC里用这个名字引用annotations:storageclass.kubernetes.io/is-default-class:"true"# 标记为默认SCprovisioner:ebs.csi.aws.com# ← 最关键的字段!用哪个"大厨"parameters:# 传给Provisioner的"菜谱参数"type:gp3# EBS卷类型fsType:ext4# 文件系统格式encrypted:"true"# 是否加密iops:"3000"# IOPS(gp3支持独立配置IOPS)throughput:"125"# 吞吐量(gp3新特性)reclaimPolicy:Delete# PVC删除后自动删除EBS卷allowVolumeExpansion:true# 允许扩容PVCvolumeBindingMode:WaitForFirstConsumer# 等有人要用再创建(详后)mountOptions:# 挂载选项-debug-noatime要点:StorageClass本质就是一个"配方表"——定义好用什么厨师(provisioner)、怎么做(parameters)、吃完怎么处理(reclaimPolicy)。你点菜(PVC)的时候说"按这个配方的来一份",厨房(Provisioner)就照方抓药。不同的配方可以做不同风格的菜——SSD快速存储、HDD廉价存储、加密存储、共享文件存储,全是换个配方的事。
2.2 Provisioner——StorageClass的灵魂
Provisioner就是那个"大厨"。不同的Provisioner会做不同的"菜":
【Provisioner——各种"大厨"和他们的"拿手菜"】 ┌─────────────────────────────────────────────────────────┐ │ StorageClass (菜单) │ │ │ │ provisioner: "请指定哪个大厨来做这道菜" │ └────────────────────────┬────────────────────────────────┘ │ ┌───────────────┼───────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐ │ AWS EBS Chef │ │ GCE PD Chef │ │ NFS Chef │ │ (ebs.csi.aws │ │(pd.csi.gcp │ │(nfs.csi.k8s │ │ .com) │ │ .com) │ │ .io) │ │ │ │ │ │ │ │ 拿手菜: │ │ 拿手菜: │ │ 拿手菜: │ │ • gp3 SSD │ │ • pd-ssd │ │ • NFS共享目录 │ │ • io2 高性能 │ │ • pd-hdd │ │ • 多Pod读写 │ │ • st1 大容量 │ │ │ │ │ └─────────────────┘ └─────────────┘ └─────────────────┘ 常见Provisioner一览: ┌────────────────────────────────┬──────────────────────────┐ │ Provisioner │ 对应存储 │ ├────────────────────────────────┼──────────────────────────┤ │ kubernetes.io/aws-ebs │ AWS EBS (in-tree, 弃用) │ │ ebs.csi.aws.com │ AWS EBS (CSI, 推荐) │ │ pd.csi.storage.gke.io │ GCE Persistent Disk │ │ disk.csi.azure.com │ Azure Disk │ │ file.csi.azure.com │ Azure Files │ │ nfs.csi.k8s.io │ NFS (CSI) │ │ cephfs.csi.ceph.com │ CephFS │ │ rbd.csi.ceph.com │ Ceph RBD │ │ rook-ceph.rbd.csi.ceph.com │ Rook Ceph RBD │ │ nfs-subdir-external-provisioner│ NFS子目录Provisioner │ └────────────────────────────────┴──────────────────────────┘要点:区分in-tree和out-of-tree的provisioner很重要。
kubernetes.io/aws-ebs是K8s内置的老版本(in-tree),已经不推荐使用了。新项目一律用CSI版本的provisioner(如ebs.csi.aws.com),独立发布、独立迭代、不依赖K8s版本。
2.3 parameters——“少盐、微辣、七分熟”
parameters是传给Provisioner的"烹饪参数"——你想让存储卷有什么特性,就在这里配置:
# 不同存储后端的parameters差异很大——类比不同菜系的"配方"# AWS EBS 的参数apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:premium-ssdprovisioner:ebs.csi.aws.comparameters:type:gp3# 卷类型fsType:ext4# 文件系统encrypted:"true"# 加密kmsKeyId:"arn:aws:kms:..."# 加密密钥iops:"16000"# IOPSthroughput:"1000"# 吞吐量 MB/s---# Azure Disk 的参数apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:azure-premiumprovisioner:disk.csi.azure.comparameters:skuName:Premium_LRS# Premium SSDkind:Managed# 托管磁盘cachingMode:ReadOnly# 缓存模式---# GCE PD 的参数apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:gce-ssdprovisioner:pd.csi.storage.gke.ioparameters:type:pd-ssd# SSD持久盘replication-type:regional-pd# 跨区域复制fsType:xfs# XFS文件系统# 查看集群中的所有StorageClass及其参数kubectl get storageclass# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE# fast-ssd ebs.csi.aws.com Delete WaitForFirstConsumer# standard ebs.csi.aws.com Delete Immediate# premium-rwx efs.csi.aws.com Retain Immediate# 查看特定SC的详细配置kubectl describe storageclass fast-ssd# Parameters:# encrypted: "true"# fsType: ext4# iops: "3000"# type: gp3三、volumeBindingMode——“先上菜"还是"等人来再上菜”
这是StorageClass里最容易理解错但最重要的一项配置。它决定了PV什么时候被创建、什么时候绑定给PVC。
3.1 两种模式的核心区别
【volumeBindingMode——"提前做" vs "等人来再做"】 Immediate(立即绑定) WaitForFirstConsumer(等人来再绑) ──────────────────── ────────────────────────────── PVC一创建 → 立即创建PV PVC创建 → 先不创建PV → 立即绑定PV → 等着 → Pod还没影呢 → Pod被调度器安置到Node → 然后才创建PV、绑定 时间线: 时间线: ──────── ──────── t0: 创建PVC t0: 创建PVC t1: 立即创建PV(在某个可用区) t1: 啥也不干(Pending) t2: 立即绑定 t2: 啥也不干(Pending) t3: 创建Pod t3: 创建Pod t4: 调度Pod t4: 调度器决定Pod放哪个Node t5: Pod启动 ✅ t5: 创建PV(在Pod所在可用区) t6: 绑定PV t7: Pod启动 ✅ 问题:PV可能在Zone-A,Pod被调度 好处:PV一定在Pod所在可用区 到Zone-B → 挂载失败! 绝对不会挂错区3.2 场景选择——什么时候用哪种
| 场景 | 推荐模式 | 原因 |
|---|---|---|
| 多可用区集群 + 块存储(EBS/PD) | WaitForFirstConsumer | EBS只能在同AZ挂载,等确认Pod位置再建卷 |
| 单可用区集群 | Immediate | 没有跨AZ问题,立即创建更简单 |
| 网络存储(NFS/CephFS/EFS) | Immediate | 网络存储跨AZ,不需要等 |
| 开销敏感的测试环境 | WaitForFirstConsumer | PVC不消费Pod就不创建,省资源 |
| 需要预热的存储 | Immediate | 有些存储创建需要时间,提前准备好 |
要点:volumeBindingMode的选择本质上是一个"先有鸡还是先有蛋"的问题——你是先创建PV再等Pod(Immediate),还是先等Pod调度再创建PV(WaitForFirstConsumer)。对于块存储(EBS/PD/本地盘),永远选WaitForFirstConsumer。这个决策只有一个例外:你的集群只有一个可用区——那选哪个都行。多AZ环境下选Immediate就是找死——你会看到一半的Pod因为跨AZ挂载失败而CrashLoopBackOff。
# WaitForFirstConsumer 标准配置(多AZ集群推荐)apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:fast-ssd-waitprovisioner:ebs.csi.aws.comvolumeBindingMode:WaitForFirstConsumer# ← 关键配置parameters:type:gp3allowVolumeExpansion:true---# Immediate 标准配置(单AZ或网络存储)apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:nfs-sharedprovisioner:nfs.csi.k8s.iovolumeBindingMode:Immediate# ← NFS跨AZ,可以立即绑parameters:server:nfs-server.default.svc.cluster.localshare:/exports3.3 WaitForFirstConsumer的完整工作流
【WaitForFirstConsumer——完整时间线】 时间 事件 PVC状态 PV状态 ──── ──── ────── ───── t0 开发者创建PVC Pending 不存在 "我要10Gi, fast-ssd" t1 K8s看到WaitForFirstConsumer Pending 不存在 "先等等,别急着做" t2 开发者创建Pod引用这个PVC Pending 不存在 t3 调度器为Pod选Node Pending 不存在 "Pod放Node-3(在AZ-a)" t4 Kubelet发现Pod的PVC未绑定 Pending 不存在 触发volume provisioning t5 Provisioner在AZ-a创建EBS卷 Pending Creating "在AZ-a建了个10G的gp3卷" t6 EBS卷创建完成 Pending Available 创建PV对象 t7 K8s将PV绑定到PVC Bound Bound t8 Kubelet挂载EBS卷到Node-3 Bound Bound Pod启动!✅要点:WaitForFirstConsumer本质上是一种"延迟绑定"策略——它把PV的创建时机推迟到Pod调度决策完成后。这对于多可用区集群使用块存储是必须的——因为EBS/PD等块存储有AZ亲和性,卷必须在Pod所在AZ才能挂载。如果你用Immediate模式在多AZ集群里跑,有50%的概率Pod启动失败——因为它被调度到了另一个AZ。
四、默认StorageClass——“不用点名,自动找你”
# 如果集群设置了默认StorageClass# PVC不需要写storageClassName,自动使用默认的# 查看默认StorageClasskubectl get storageclass# NAME PROVISIONER RECLAIMPOLICY# gp2 (default) ebs.csi.aws.com Delete# ↑ 有 (default) 标记的就是默认SC# 设置某个SC为默认kubectl patch storageclass gp2-p'{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'# 取消默认SCkubectl patch storageclass gp2-p'{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"null"}}}'# 创建不指定SC的PVC——自动用默认SCapiVersion: v1 kind: PersistentVolumeClaim metadata: name: auto-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi# 不写 storageClassName → 自动使用默认SC要点:默认StorageClass很方便,但要小心"隐式行为"。如果你同时在用多种存储(SSD和HDD),不要设默认SC——让开发者显式指定用哪个。否则有人写了个PVC忘了声明storageClassName,结果系统给了一个HDD慢速存储,等到性能问题暴露已经晚了。原则:生产环境,宁可多写一行
storageClassName,也别依赖默认值。
五、扩容——“吃完了还能续杯”
# StorageClass 需要开启 allowVolumeExpansionapiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:expandable-ssdprovisioner:ebs.csi.aws.comallowVolumeExpansion:true# ← 允许扩容!必须显式开启parameters:type:gp3# 扩容PVC(在线扩容,不需要停Pod)kubectl patch pvc my-app-data-p'{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'# 前提:# 1. StorageClass 设置了 allowVolumeExpansion: true# 2. 底层存储支持扩容(gp3支持,gp2也支持)# 3. 新的容量 > 旧容量(只能扩不能缩!)# 查看扩容进度kubectl describe pvc my-app-data# Conditions:# Type Status LastProbeTime# FileSystemResizePending True <正在扩容文件系统...># 几秒后变成 ResizeSucceeded# ⚠️ 重要限制# - 只能扩容不能缩容(只增不减)# - 扩容过程中Pod可以继续使用# - 扩容完成后Pod内文件系统自动扩展# - 有些存储类型需要重启Pod才能看到新容量六、云厂商StorageClass最佳实践
6.1 AWS EKS 推荐配置
# 分层存储策略——按性能和成本分为三个等级# 1. 高性能SSD(数据库)apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:premium-ssdprovisioner:ebs.csi.aws.comvolumeBindingMode:WaitForFirstConsumer# 多AZ,必须等allowVolumeExpansion:truereclaimPolicy:Deleteparameters:type:gp3fsType:ext4encrypted:"true"iops:"16000"throughput:"1000"---# 2. 标准SSD(一般应用)apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:standard-ssdprovisioner:ebs.csi.aws.comvolumeBindingMode:WaitForFirstConsumerallowVolumeExpansion:truereclaimPolicy:Deleteparameters:type:gp3fsType:ext4iops:"3000"throughput:"125"---# 3. HDD大容量(备份/日志)apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:bulk-hddprovisioner:ebs.csi.aws.comvolumeBindingMode:WaitForFirstConsumerallowVolumeExpansion:truereclaimPolicy:Deleteparameters:type:st1fsType:ext46.2 阿里云 ACK 推荐配置
# 阿里云极速SSDapiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:alicloud-essdprovisioner:diskplugin.csi.alibabacloud.comvolumeBindingMode:WaitForFirstConsumerallowVolumeExpansion:trueparameters:type:cloud_essd# ESSD云盘performanceLevel:PL2# PL0/PL1/PL2/PL3encrypted:"true"---# 阿里云NAS(共享文件存储,支持RWX)apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:alicloud-nasprovisioner:nasplugin.csi.alibabacloud.comvolumeBindingMode:Immediate# NAS是网络存储,可以立即绑parameters:volumeAs:subpathserver:"xxxxx.cn-hangzhou.nas.aliyuncs.com:/"path:/k8svers:"3"options:"nolock,proto=tcp,rsize=1048576,wsize=1048576"七、StorageClass的日常操作
# 创建StorageClasskubectl apply-fstorageclass.yaml# 查看所有StorageClasskubectl get sc# kubectl get storageclass 也一样# 查看详细信息kubectl describe sc fast-ssd# 修改StorageClass(注意:immutable字段改不了)kubectl edit sc fast-ssd# 可修改的字段:allowVolumeExpansion, mountOptions, reclaimPolicy, metadata# 不可修改的字段(改了也不生效):provisioner, parameters, volumeBindingMode# 删除StorageClasskubectl delete sc old-sc# 注意:删除SC不会影响已经用这个SC创建的PV和PVC!# 查看PVC使用的是哪个SCkubectl get pvc-owide# 或者kubectl describe pvc my-pvc|grepStorageClass# 查看PVC对应的底层存储卷IDkubectl describepv$(kubectl get pvc my-pvc-ojsonpath='{.spec.volumeName}')|grepVolumeID本篇小结
StorageClass让K8s存储从"半自动"进化到"全自动"——你再也不用手动管理PV的创建和删除。核心掌握三点:
- Provisioner是灵魂:不同的provisioner对应不同的存储后端,选对了直接决定了你能用什么参数和特性
- volumeBindingMode是开关:多AZ集群用块存储必须配
WaitForFirstConsumer,否则Pod可能因跨AZ挂载失败 - parameters是可选的配方:每个Provisioner有自己的一套参数,按需配置——不用背,查文档即可
实战中一个集群至少配两个StorageClass:一个高性能SSD给数据库、一个大容量HDD给日志和备份。再加一个NAS的SC(支持RWX)给需要多Pod共享的应用。
下一篇聊Local PV——当你对性能有极致追求,不想加任何网络存储的延迟,直接用Node本地盘做持久化存储。
上一篇【第37篇】PV和PVC——存储的"供需匹配"平台
下一篇【第39篇】本地持久化存储——Local PV和hostPath的正确使用姿势