3. Kubernetes存储架构:PV与PVC概念、StorageClass动态供给、CSI接口规范
聊到Kubernetes的存储,很多人第一反应就是PV和PVC。说实话,我刚接触那会儿也觉得这俩概念有点绕——明明就是存个数据,干嘛搞这么复杂?但后来在项目中踩过几次坑,才明白这套抽象层设计得有多巧妙。
咱们今天就把这块彻底讲透。我会从最基础的PV/PVC讲起,再到StorageClass动态供给,最后聊聊CSI这个“幕后英雄”。嗯,保证让你听完就能上手。
3.1 PV与PVC:存储的“供需分离”
先问个问题:在Kubernetes里,Pod怎么用存储?
最直接的方式——在Pod定义里直接写存储路径。但这样有个大问题:存储细节和业务逻辑耦合了。你想想看,如果今天用NFS,明天换成Ceph,是不是得改Pod配置?
Kubernetes的解法很聪明:把存储的“供给方”和“需求方”分开。
- PV(PersistentVolume):集群管理员预先准备好的存储资源。就像你买了一块硬盘,插在服务器上。
- PVC(PersistentVolumeClaim):用户对存储的需求声明。就像你说“我要一块100G的SSD硬盘”。
PVC和PV之间通过绑定(Bind)机制匹配。匹配条件包括存储大小、访问模式(ReadWriteOnce、ReadOnlyMany、ReadWriteMany)、存储类等。
核心要点:PVC是“我要什么”,PV是“我有什么”。Kubernetes负责把两者配对。
我在项目中遇到过一个问题:多个PVC同时申请同一个PV,结果只有第一个能绑定成功,后面的PVC一直Pending。排查了半天才发现——PV一旦被绑定,状态就变成Bound,不会再接受其他PVC。所以,PV和PVC是一对一绑定的。
3.1.1 访问模式(Access Modes)
这块容易搞混,我简单列个表:
| 访问模式 | 缩写 | 说明 | 常见场景 |
|---|---|---|---|
| ReadWriteOnce | RWO | 单节点读写 | 大多数数据库(MySQL、PostgreSQL) |
| ReadOnlyMany | ROX | 多节点只读 | 配置文件分发、静态资源 |
| ReadWriteMany | RWX | 多节点读写 | 共享文件系统、日志收集 |
注意:不是所有存储后端都支持RWX。比如大多数云厂商的块存储(AWS EBS、GCE PD)只支持RWO。如果你需要RWX,得用NFS、CephFS或者云厂商的文件存储。
我曾经踩过的坑:在StatefulSet里用了RWO的PVC,结果Pod副本数超过1时,第二个Pod一直Pending。因为RWO只允许一个节点挂载。后来改成NFS才解决。
3.2 StorageClass:动态供给的“魔法”
手动创建PV再绑定PVC,这种方式叫静态供给。在小规模环境还行,但生产环境里,你不可能每次都用存储都去找管理员申请PV吧?
StorageClass就是来解决这个问题的。它定义了存储的“模板”——比如用什么类型的存储、性能等级、回收策略等。当用户创建PVC时,如果PVC指定了StorageClass,Kubernetes会自动调用存储后端创建PV。
说白了,StorageClass让存储变成了“按需分配”。
3.2.1 一个典型的StorageClass定义
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
fsType: ext4
iopsPerGB: "10"
reclaimPolicy: Delete
allowVolumeExpansion: true
mountOptions:
- discard
这里的关键字段:
- provisioner:谁来创建PV?这里是AWS EBS的provisioner。
- parameters:存储后端的参数,比如磁盘类型、IOPS等。
- reclaimPolicy:PVC删除后,PV怎么处理?Delete(删除)、Retain(保留)、Recycle(已废弃)。
- allowVolumeExpansion:是否允许在线扩容。我建议生产环境都开启。
然后PVC只需要这样写:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: fast-ssd
Kubernetes看到storageClassName是fast-ssd,就会自动调用AWS的API创建一个100G的gp3卷,然后绑定给这个PVC。整个过程不需要管理员介入。
个人习惯:我一般会为不同场景定义多个StorageClass。比如“standard”用HDD,“premium”用SSD,“archive”用冷存储。这样开发人员只需要在PVC里指定名字,就能拿到合适的存储。
3.3 CSI接口规范:存储的“通用语言”
在CSI出现之前,Kubernetes的存储插件都是“内嵌”在kubelet里的。每个存储厂商都得改Kubernetes源码才能支持自己的设备。这显然不现实。
CSI(Container Storage Interface)的出现,彻底改变了这个局面。它定义了一套标准接口,让存储厂商可以独立开发插件,然后以DaemonSet的形式部署到Kubernetes集群里。
你可以把CSI想象成USB接口——不管你是U盘、移动硬盘还是打印机,只要插上USB口就能用。CSI就是Kubernetes的“USB口”。
3.3.1 CSI的核心组件
一个CSI插件通常包含三个部分:
- Driver Registrar:负责把CSI驱动注册到kubelet。
- CSI Controller:处理创建/删除/挂载/卸载等操作。通常以StatefulSet部署。
- CSI Node:运行在每个节点上,负责实际的挂载操作。
部署后,Kubernetes通过gRPC调用CSI驱动。调用流程大致如下:
1. 用户创建PVC(指定StorageClass)
2. Kubernetes Controller Manager 发现PVC需要动态供给
3. 调用CSI Controller的CreateVolume方法
4. CSI驱动调用存储后端API创建卷
5. 返回卷信息给Kubernetes
6. Pod调度到节点后,kubelet调用CSI Node的NodePublishVolume
7. 存储卷挂载到Pod的文件系统
整个过程对用户完全透明。你只需要定义PVC,剩下的CSI帮你搞定。
我建议:生产环境尽量使用CSI驱动,而不是Kubernetes内置的in-tree插件。因为CSI驱动更新更及时,bug修复也更快。比如AWS EBS的in-tree插件在1.23版本后就被标记为废弃了,官方推荐用CSI驱动。
3.3.2 常见的CSI驱动
| 存储后端 | CSI驱动名称 | 特点 |
|---|---|---|
| AWS EBS | ebs.csi.aws.com | 支持快照、扩容、加密 |
| GCE PD | pd.csi.storage.gke.io | 支持区域级持久盘 |
| Ceph RBD | rbd.csi.ceph.com | 高性能块存储 |
| CephFS | cephfs.csi.ceph.com | 共享文件系统 |
| NFS | nfs.csi.k8s.io | 简单易用,适合小规模 |
选型时注意:Ceph RBD和CephFS虽然都是Ceph,但用途完全不同。RBD是块设备,适合数据库;CephFS是文件系统,适合共享存储。别搞混了。
3.4 实战建议:如何设计存储方案
讲了这么多理论,最后给点实际建议。我在多个生产集群里折腾过存储,总结了几条经验:
- 有状态应用优先用StatefulSet + PVC:StatefulSet能保证Pod名称和PVC的绑定关系,重启后数据不丢。
- 数据库用本地SSD或高性能云盘:别用NFS跑MySQL,性能会哭的。我见过有人用NFS跑PostgreSQL,结果IO延迟飙到100ms以上。
- 日志和静态文件用RWX:多个Pod共享读写,方便日志收集和文件分发。
- 设置合理的reclaimPolicy:测试环境用Delete,生产环境用Retain。否则PVC一删,数据就没了。
- 开启allowVolumeExpansion:存储不够时在线扩容,不用重启Pod。
我曾经犯过的错:有一次在生产环境删了一个PVC,结果对应的PV也被自动删除了(reclaimPolicy=Delete)。那个PV里存着重要的业务数据,还好有备份。从那以后,我所有生产环境的PV都改成Retain策略。
好了,这一章的内容就到这里。PV/PVC是Kubernetes存储的基石,StorageClass让存储变得灵活,CSI则让存储生态变得开放。理解这三者的关系,你就能设计出靠谱的存储方案。
下一章我们会聊聊ConfigMap和Secret——这两个看似简单但坑不少的东西。到时候见。