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插件通常包含三个部分:

  1. Driver Registrar:负责把CSI驱动注册到kubelet。
  2. CSI Controller:处理创建/删除/挂载/卸载等操作。通常以StatefulSet部署。
  3. 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——这两个看似简单但坑不少的东西。到时候见。