Kubernetes控制平面组件:Kubelet详解(八):容器存储接口 CSI
云原生学习路线导航页(持续更新中)
- kubernetes学习系列快捷链接
- Kubernetes架构原则和对象设计(一)
- Kubernetes架构原则和对象设计(二)
- Kubernetes架构原则和对象设计(三)
- Kubernetes控制平面组件:etcd(一)
- Kubernetes控制平面组件:etcd(二)
- Kubernetes控制平面组件:API Server详解(一)
- Kubernetes控制平面组件:API Server详解(二)
- Kubernetes控制平面组件:调度器Scheduler(一)
- Kubernetes控制平面组件:调度器Scheduler(二)
- Kubernetes控制平面组件:Controller Manager 之 内置Controller详解
- Kubernetes控制平面组件:Controller Manager 之 NamespaceController 全方位讲解
- Kubernetes控制平面组件:Kubelet详解(一):架构 及 API接口层介绍
- Kubernetes控制平面组件:Kubelet详解(二):核心功能层
- Kubernetes控制平面组件:Kubelet详解(三):CRI 容器运行时接口层
- Kubernetes控制平面组件:Kubelet详解(四):gRPC 与 CRI gRPC实现
- Kubernetes控制平面组件:Kubelet详解(五):切换docker运行时为containerd
- Kubernetes控制平面组件:Kubelet详解(六):pod sandbox(pause)容器
- Kubernetes控制平面组件:Kubelet详解(七):容器网络接口 CNI
- Kubernetes控制平面组件:Kubelet 之 Static 静态 Pod
本文是 kubernetes 的控制面组件 kubelet 系列文章第八篇,主要对 容器存储接口CSI 进行讲解,包括:容器运行时存储的局限性,kubernetes存储卷插件的类型,每种插件的原理、优缺点、适用场景等,并详细介绍了kubernetes支持的常用存储卷emptyDir、hostPath、pv、pvc、storageClass等,重点介绍了pv、pvc、storageClass设计和使用上的差异
- 希望大家多多 点赞 关注 评论 收藏,作者会更有动力继续编写技术文章
1.容器运行时存储
- 在学习docker时,我们在 Docker核心技术:Docker原理之Union文件系统 中学习了docker的文件系统,容器文件系统中,overlayFS已经成为事实标准,不二选择,所以基本也不怎么存在容器文件系统的选型问题。
- 容器运行时存储(容器运行时的文件系统)是存储什么?
- 存储镜像的层。尽量不要在容器运行时写数据,因为性能很差,一般就是用于把应用拉起来就好。日志也不要写在里面
- 那么应用运行中产生的数据,怎么存储呢?
- 使用容器的各种数据卷。下面就一一讲解。
2.kubernetes存储卷插件
- kubernetes支持多种多样的存储卷,为了方便管理和扩展,kubernetes以插件方式支持。
- 随着不断迭代演进,kubernetes存储卷插件可以分成3种类型:
- in-tree插件
- out-of-tree FlexVolume插件
- out-of-tree CSI插件
2.1.In-Tree 存储插件
2.1.1.原理
- 代码直接集成在Kubernetes核心组件(如kube-controller-manager、kubelet)中,与Kubernetes同进程运行。
- 存储驱动(如NFS、iSCSI)通过Kubernetes原生API实现卷的生命周期管理。
2.1.2.优点
- 开箱即用:无需额外部署组件,安装Kubernetes即支持。
- 兼容性高:早期版本中功能稳定,与Kubernetes核心深度集成。
2.1.3.缺点
- 强耦合性:存储驱动的更新需跟随Kubernetes版本发布周期,灵活性差。
- 稳定性风险:插件崩溃可能导致Kubernetes核心组件异常。
- 社区维护弱化:Kubernetes已停止接受新的in-tree插件,逐步迁移至CSI。
2.1.4.适用场景
- 旧集群兼容性维护,或无需频繁更新的简单存储需求(如测试环境使用NFS)。
2.2.Out-of-Tree FlexVolume 插件
2.2.1.原理
- 通过节点上的可执行文件(Shell/Python脚本)与存储驱动交互,由kubelet调用脚本实现卷操作(如挂载/卸载)。
- 与 Kubernetes控制平面组件:Kubelet详解(七):容器网络接口 CNI 中讲的CNI插件的使用方式一样,在node的某个固定目录下安装了很多可执行文件,供kubelet调用执行这些插件,来实现存储的mount/unmount等功能
- 需在所有节点预安装驱动文件,并赋予执行权限。
2.2.2.优点
- 解耦核心代码:独立于Kubernetes发布周期,存储商可自主更新驱动。
- 兼容多种存储:支持云存储(如阿里云云盘)、分布式存储(如Ceph)。
2.2.3.缺点
- 部署复杂:需手动在所有节点安装驱动,运维成本高。
- 功能有限:仅支持基础操作(Attach/Detach、Mount/Unmount),缺乏快照、扩容等高级功能。
- 权限要求高:需root权限运行脚本,安全风险提升。
2.2.4.适用场景
- CSI成熟前的过渡方案,或对存储功能要求简单的环境
2.3.Out-of-Tree CSI 插件
为了更好的扩展存储插件,并做到kubernetes与存储的解藕合,设计了CSI标准接口。接口通过gRPC协议定义了存储生命周期的管理操作(如创建卷、快照、扩容)
2.3.1.CSI 接口类型
2.3.1.1.CSI Controller Service 接口
- 功能:实现Controller Service 接口,负责集群级别的存储资源管理
- 核心操作包括:
- 卷的创建(CreateVolume)、删除(DeleteVolume)
- 卷的附加(ControllerPublishVolume)与分离(ControllerUnpublishVolume)
- 快照管理(CreateSnapshot/DeleteSnapshot)
- 卷扩容(ControllerExpandVolume)
- 部署形式:
- 通常以 Deployment/StatefulSet(单副本) 部署在控制平面节点
- CSI Controller Pod还会包含 一些CSI Sidecar 组件,以多容器的方式加入:
- external-provisioner(动态卷供给)
- external-attacher(卷附加管理)
- external-resizer(卷扩容)
- external-snapshotter(快照管理)
- CSI Sidecar 组件是由kubernetes社区维护的,方便存储厂商接入CSI的一组组件,不需要存储厂商自己开发,只需要部署的时候带上即可
2.3.1.2.CSI Node Service 接口
- 功能:实现 Node Service 接口,负责节点级别的存储操作
- 核心操作包括:
- 卷的挂载(NodePublishVolume)与卸载(NodeUnpublishVolume)
- 节点全局目录准备(NodeStageVolume)与清理(NodeUnstageVolume)
- 卷状态监控(NodeGetVolumeStats)
- 部署形式:
- 以 DaemonSet(每节点一个 Pod) 部署在工作节点
- CSI Node Pod 也会包含一些 CSI Sidecar 组件:比如node-driver-registrar,用于向 kubelet 注册驱动,以多容器方式加入Pod。
- node-driver-registrar 向kubelet注册CSI Driver的方式,其实就是告知 kubelet 本地CSI Driver socket的路径
2.3.1.3.CSI Identity Service 接口
- 功能:实现 Identity Service 接口,提供驱动元数据与能力声明
- 核心操作:
- 返回驱动名称、版本(GetPluginInfo)
- 声明支持的 CSI 功能(GetPluginCapabilities)
- 部署形式:
- 非独立 Pod,而是内置于 CSI Controller Pod 和 CSI Node Pod 中- 例如:同一 CSI Driver 容器在 Controller 和 Node 模式下均需响应 Identity 接口
2.3.2.CSI 存储插件架构
CSI 存储插件架构主要包括3个部分:CSI Sidecar、CSI Driver、Kubernetes 原生组件
2.3.2.1.CSI 存储插件架构
- Control Plane:
- kube-controller-manager:
- PersistentVolumeController:监听 PVC,触发动态供给流程(不直接调用 CSI)
- AttachDetachController:管理 VolumeAttachment 对象(不直接调用 CSI)
- csi controller pod:
- csi 的控制平面pod,其中容器可以分成:csi sidecar、csi controller service driver
- kube-controller-manager:
- Data Plane:
- kubelet:
- VolumeManager:调用 CSI Node 接口(需先通过 node-driver-registrar 注册驱动)
- 执行两阶段挂载: 挂载到全局目录、绑定挂载到pod文件系统
- csi node pod:
- csi 的 node pod,其中容器可以分成:node-driver-register、csi node service driver
- kubelet:
- CSI 各团队职责分工:
- 存储厂商需要提供的代码实体:csi controller service driver、csi node service driver
- csi sidecar(包括node-driver-register)是 Kubernetes 社区官方维护的组件,代码托管在 Kubernetes SIG Storage 项目中
2.3.2.2.架构设计核心思想
- 解耦与扩展性:
- Kubernetes 核心组件仅管理存储对象(PVC/PV/VolumeAttachment)
- Sidecar 代理将对象变更转换为 CSI 接口调用
- 高可用保障:
- Controller Sidecar 需单副本部署,避免存储操作冲突
- 可通过 Leader Election 实现多副本选主(如 CubeFS 优化方案)
- 安全隔离:
- CSI Driver 以独立进程运行,故障不会影响 kubelet/kube-controller-manager
- 卷操作权限由 StorageClass 中的
secretRef
动态注入
此架构严格遵循 CSI 规范,已被 AWS EBS、Google Persistent Disk、Ceph RBD 等主流存储系统采用。实际部署时需注意 Sidecar 版本与 Kubernetes 的兼容性(详见 CSI 官方文档)
2.3.2.3.存储卷生命周期流程示例(动态供给 + Pod 挂载)
2.3.3.CSI Sidecar
- CSI Sidecar 是 Kubernetes 中实现容器存储接口(CSI)的核心辅助组件,由 Kubernetes 社区官方维护(SIG Storage),用于桥接 Kubernetes 存储管理逻辑与第三方存储驱动的具体实现。
- CSI Sidecar 本质是一组独立容器,与存储厂商提供的 CSI Driver 容器协同部署,负责监听 Kubernetes API 对象变更并触发对应的 CSI 接口调用。
- 根据职责,可将CSI Sidecar分为两类:Controller Sidecar、Node Sidecar
2.3.3.1.Controller Sidecar
- 部署于 CSI Controller Pod(Deployment/StatefulSet,单副本),处理集群级存储操作:
组件 | 功能 | 监听对象 | 触发的 CSI 接口 |
---|---|---|---|
external-provisioner | 动态创建/删除存储卷 | PVC | CreateVolume /DeleteVolume |
external-attacher | 管理存储卷与节点的附加(Attach)/分离(Detach) | VolumeAttachment | ControllerPublishVolume |
external-resizer | 扩容存储卷容量 | PVC(容量变更) | ControllerExpandVolume |
external-snapshotter | 管理卷快照生命周期 | VolumeSnapshot | CreateSnapshot /DeleteSnapshot |
livenessprobe | 监控 CSI Driver 健康状态,向 kubelet 上报存活状态 | - | - |
external-health-monitor | 监控卷健康状态(如文件系统损坏) | PersistentVolume | NodeGetVolumeStats (扩展) |
2.3.3.2.Node Sidecar
- 部署于 CSI Node Pod(DaemonSet,每节点),处理节点级卷操作:
组件 | 功能 | 通信对象 |
---|---|---|
node-driver-registrar | 向 kubelet 注册 CSI Driver,写入驱动信息到 /var/lib/kubelet/plugins_registry | kubelet |
external-health-monitor-agent | 节点级卷健康监控(如 I/O 错误) | kubelet |
2.4.三种存储插件对比
特性 | In-Tree插件 | FlexVolume插件 | CSI插件 |
---|---|---|---|
耦合性 | 强耦合(K8s核心代码) | 中等解耦(节点脚本) | 完全解耦(独立进程) |
部署复杂度 | 无需部署 | 高(需节点预安装) | 中(容器化部署) |
功能支持 | 基础卷操作 | 基础卷操作 | 全生命周期管理+高级功能 |
安全性 | 低(共享进程) | 低(需root权限) | 高(独立进程) |
社区趋势 | 逐步废弃 | 维护减少 | 主流标准方案 |
典型用例 | NFS、iSCSI | 阿里云早期云盘 | AWS EBS、Ceph RBD |
注:Kubernetes 1.13+ 后CSI进入GA阶段,成为默认推荐方案,FlexVolume仅用于兼容旧驱动
- 如果要深入学习CSI插件,可以去看CNCF的一个开源项目:Rook
3.kubernetes存储卷类型
3.1.临时存储 emptyDir
3.1.1.emptyDir 介绍
- emptydir的真实存储路径:
- kubelet会在node上为每个pod都创建一个数据目录,用来存储一些运行时文件。该目录不属于容器的overlayFS文件系统,而是记录在主机上的。
- kubelet的数据目录一般为:
/var/lib/kubelet
,可以通过--root-dir
参数配置。该路径下会有个pods的子目录,所有pod的临时数据都记录在这里。 - pod使用emptydir,默认会为pod创建目录
/var/lib/kubelet/pods/<Pod-UID>/volumes/kubernetes.io~empty-dir/<Volume-Name>
,作为emptydir的存储目录 - 也可以通过 docker inspect/crictl inspect 等命令查看容器的挂载信息,会记录数据卷的真实目录
- emptydir 其实是最常用的存储卷类型,因为用起来最简单,一些临时数据、中间数据都会直接用
- 注意:pod crash 重启,不会导致emptydir数据清理
3.1.2.emptyDir 参数列表
volumes[0].emptyDir 下面属性
字段 | 可选值 | 默认值 | 核心作用 |
---|---|---|---|
medium | "" 、Memory | "" | 选择磁盘或内存存储介质 |
sizeLimit | 字符串(如 "1Gi" ) | nil | 限制卷容量,内存介质必须设置 |
3.1.2.emptyDir 使用示例
apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-deployment
spec:replicas: 1selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: nginximage: nginxvolumeMounts:- mountPath: /cachename: cache-volumevolumes:- name: cache-volumeemptyDir: {}
3.2.半持久化存储 hostPath
3.2.1.hostPath 介绍
- hostpath 还有种使用场景:当要拷贝一些文件或可执行程序到主机上的时候,可以使用hostpath,比如 cni 插件,就是通过daemonset往主机cni插件目录拷贝可执行文件的
- emptyDir 与 hostPath 对比
- emptyDir 其实也是一种hostPath,但pod删除时kubelet会自动清理emptyDir的数据,emptyDir使用的更广泛
3.2.2.hostPath 使用注意事项
- pod漂移到其他node,就无法找到原node的hostpath数据
- 脏数据问题
- hostPath的数据,与pod生命周期解耦,pod被干掉后数据依旧保留在node主机上,如果忘记手动清理的话,残留脏数据会占用空间,也可能影响后续pod挂载,如果后续pod挂载该目录就会看到脏数据
- 权限敏感
- 使用hostpath时一定要确保目录存在,或使用hostpath.type保证能自动创建目录,
- 还要确保pod有该主机目录的操作权限
- 主机目录开放权限是很危险的,要慎用
3.2.3.hostPath 参数列表
volumes[0].hostPath 下面属性
字段名 | 是否必需 | 数据类型 | 默认值 | 描述与行为 | 注意事项 |
---|---|---|---|---|---|
path | 必需 | string | 无 | 指定宿主机(节点)上的文件或目录路径。例如:/data 或 /var/logs 。(1)若路径是符号链接,则自动解析到真实路径。 (2)路径必须预先存在(除非使用 *OrCreate 类型) | • 路径需有足够权限(通常需 root 或调整目录权限)• 不同节点路径内容可能不一致,影响 Pod 可移植性 |
type | 可选 | string | "" | 定义路径类型及创建行为,可选值如下: (1) "" (空):默认值,不检查路径类型(兼容旧版本)(2) DirectoryOrCreate :路径不存在时自动创建目录(权限 0755 ,属主 kubelet )(3) Directory :路径必须是已存在的目录(4) FileOrCreate :路径不存在时创建空文件(权限 0644 ,属主 kubelet )(5) File :路径必须是已存在的文件(6) Socket :路径必须是已存在的 Socket 文件(7) CharDevice :路径必须是已存在的字符设备(8) BlockDevice :路径必须是已存在的块设备 | • FileOrCreate 不创建父目录,需确保父目录存在(可配合 DirectoryOrCreate 使用)• 非默认类型需显式声明,否则路径校验可能失败 |
3.2.4.hostPath 使用示例
apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-deployment
spec:replicas: 1selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: nginximage: nginxvolumeMounts:- mountPath: /logname: logs-dirvolumes:- name: logs-dirhostPath: path: /var/log
3.3.持久化存储 PV/PVC
3.3.1.为什么需要 PV/PVC
- 云存储是非常大的一个领域,大部分业务应用同学其实都不太熟悉,让他们部署的时候自行查询集群支持哪些存储,决定要使用什么存储,把控各种存储的使用差异和细节,是不现实的。
- 那怎么办呢?设计理念:职责分离,专业的事情交给专业的人做。
- 为了实现存储过程的职责分离,kubernetes中提供了两种类型的资源:PV/PVC
- PV:供集群管理员声明集群存储
- PVC:供应用向集群申请存储资源
- 运维大佬、集群管理员们:
事先把集群内支持的所有存储,通过PV的方式声明出来
。比如 当前环境有ssd 500Gi,有hssd 100Gi,就会事先创建一些PV。- PV代表集群内的存储资源,自然也就和命名空间无关,属于集群级别。
- 应用程序员:
- 应用程序员们更了解自己的应用,清楚应用运行过程需要多少存储资源(类型、容量、性能等)
- 他们不需要关心集群有什么资源,
只需要编写PVC,向集群申请自己需要的存储类型、容量等信息,集群会自行寻找合适的存储。
- 应用的 PVC 创建后,kubernetes 会自动为其寻找合适的PV进行绑定,绑定关系是1对1,绑定成功后,代表存储资源满足了需求,应用就可以正常使用了
- 为了实现存储过程的职责分离,kubernetes中提供了两种类型的资源:PV/PVC
3.3.2.PV/PVC 介绍
3.3.2.1.PV(PersistentVolume)持久化数据卷
- PV 是什么
- 每一个 PV 都是集群中的一块存储资源。它是由集群管理员预先配置好的,或者通过 StorageClass 动态供应出来的。
- 你可以把它想象成集群中的一个“物理磁盘”或“网络存储挂载点”(虽然底层可能是云盘、NFS、Ceph 等)。
- PV 的生命周期
- PV 是集群级别的资源,独立于任何 Pod 存在。
- 即使使用它的 Pod 被删除,PV 及其数据通常仍然保留(除非配置为自动删除)。它的生命周期由 Kubernetes 管理。
- PV 的供应方
- 静态供应:通常由集群管理员创建
- 动态供应:由 StorageClass 自动动态创建。后面StorageClass部分会详细讲解
3.3.2.2.PVC(PersistentVolumeClaim)持久化数据卷声明
- PVC 是什么
- PVC是用户(应用开发者)对存储的请求。它表达了应用需要什么样的存储资源(多大容量、什么访问模式)。
- 你可以把它想象成用户提交的一份“存储需求申请单”。
- PVC 的生命周期
- PVC 是命名空间级别的资源。它存在于特定的命名空间中。
- PVC 首先向集群申请一块存储,然后 Pod 通过引用 同命名空间 下的 PVC 来使用这块存储。
- PVC 的供应方
- 由应用开发者在部署应用的 YAML 清单中 直接定义 PVC
- 或者 在 StatefulSet 等控制器中编写 volumeClaimTemplates,控制器根据模板自行创建PVC
3.3.3.PV/PVC 参数列表
3.3.3.1.PV 参数列表
pv.spec 下面字段
字段名称 | 类型 | 必填 | 描述 | 备注说明 |
---|---|---|---|---|
accessModes | []string | ✓ | 卷的挂载方式 | 定义卷的访问权限: - ReadWriteOnce (RWO):单节点读写(如数据库)- ReadOnlyMany (ROX):多节点只读(如配置文件)- ReadWriteMany (RWX):多节点读写(如共享文件系统) |
capacity | map[string]string | ✓ | 存储容量 | 必须包含 storage 键值对(如 storage: 10Gi ),决定 PVC 可请求的最大空间 |
persistentVolumeReclaimPolicy | string | ✓ | 释放策略 | PVC 释放后处理方式: - Retain :保留数据(需手动清理)- Delete :自动删除存储资源- Recycle :废弃策略(不安全擦除) |
storageClassName | string | ✗ | 所属 StorageClass 名称 | 为空表示静态供给;指定名称则关联 StorageClass 实现动态供给 |
volumeMode | string | ✗ | 卷模式 | Filesystem (默认):创建文件系统Block :原始块设备(需应用直接操作块设备) |
claimRef | Object | ✗ | 绑定的 PVC 引用 | 包含 namespace 和 name ,用于预绑定特定 PVC(防止自动绑定) |
mountOptions | []string | ✗ | 挂载选项 | 底层存储的挂载参数(如 NFS 的 nfsvers=4.1 或 Ext4 的 noatime ) |
nodeAffinity | Object | ✗ | 卷的节点亲和性约束 | 限制可使用该卷的节点(常用于 local 卷类型),需定义 required 节点选择器 |
awsElasticBlockStore | Object | ✗ | AWS EBS 磁盘配置 | 需指定 volumeID 和 fsType (如 ext4),仅适用于 AWS 环境 |
azureDisk | Object | ✗ | Azure 数据磁盘配置 | 需指定 diskName 和 diskURI ,适用于 Azure 托管磁盘 |
azureFile | Object | ✗ | Azure 文件服务配置 | 需提供 secretName (存储账号密钥)和 shareName (文件共享名) |
cephfs | Object | ✗ | Ceph FS 存储配置 | 需配置 monitors (Ceph 监控节点)和 path ,支持 secretRef 认证 |
cinder | Object | ✗ | OpenStack Cinder 卷配置 | 需指定 volumeID 和 fsType ,适用于 OpenStack 环境 |
csi | Object | ✗ | 外部 CSI 驱动存储配置 | 现代存储扩展方案,需配置 driver 名称和 volumeHandle (存储系统唯一标识) |
fc | Object | ✗ | 光纤通道 (Fibre Channel) 配置 | 需指定 targetWWNs (目标全球端口名)和 lun 号,用于 SAN 存储 |
flexVolume | Object | ✗ | 基于 exec 插件的通用卷配置 | 需指定 driver 路径和自定义参数,支持第三方存储驱动 |
flocker | Object | ✗ | Flocker 存储配置 | 需配置 datasetName 或 datasetUUID (已弃用),用于 Docker 集群存储 |
gcePersistentDisk | Object | ✗ | GCP 持久化磁盘配置 | 需指定 pdName 和 fsType ,仅适用于 Google Cloud |
glusterfs | Object | ✗ | GlusterFS 卷配置 | 需配置 endpoints (Gluster 集群端点)和 path (卷路径) |
hostPath | Object | ✗ | 主机目录配置 | 仅开发测试使用,需指定 path 和 type (如 DirectoryOrCreate)。不适用于生产集群 |
iscsi | Object | ✗ | iSCSI 存储配置 | 需指定 targetPortal (IP:port)、iqn (目标名称)和 lun 号 |
local | Object | ✗ | 本地直接附加存储配置 | 需配合 nodeAffinity 使用,指定 path (节点本地路径),数据与节点生命周期绑定 |
nfs | Object | ✗ | NFS 共享配置 | 需指定 server (NFS 服务器)和 path (导出路径),支持 readOnly 模式 |
photonPersistentDisk | Object | ✗ | Photon Controller 持久化磁盘 | 需指定 pdID ,仅适用于 VMware Photon 平台 |
portworxVolume | Object | ✗ | Portworx 卷配置 | 需指定 volumeID (Portworx 卷 ID),适用于 Portworx 分布式存储 |
quobyte | Object | ✗ | Quobyte 存储配置 | 需指定 registry (注册服务地址)和 volume (卷名),支持 readOnly 模式 |
rbd | Object | ✗ | Ceph RBD 块设备配置 | 需配置 monitors 、pool 和 image ,支持 secretRef 认证 |
scaleIO | Object | ✗ | ScaleIO 存储配置 | 需指定 gateway 、system 和 protectionDomain ,适用于 Dell EMC ScaleIO |
storageos | Object | ✗ | StorageOS 卷配置 | 需配置 volumeName 和 secretRef ,适用于 StorageOS 容器存储 |
vsphereVolume | Object | ✗ | vSphere 卷配置 | 需指定 volumePath (VMDK 路径)和 fsType ,仅适用于 vSphere 环境 |
- 关键说明
- 必填字段:创建 PV 时必须指定
accessModes
,capacity
,persistentVolumeReclaimPolicy
- 存储后端互斥:25+ 种存储插件配置(如 AWS/GCP/Azure/Ceph/NFS 等),只能启用一种
- 动态绑定:
storageClassName
用于关联 StorageClass 实现动态供给 - 安全约束:
nodeAffinity
确保 Pod 调度到有本地存储的节点 - 卷模式:
volumeMode=Block
时需应用直接操作块设备 - 弃用警告:
Recycle
回收策略已被废弃,建议使用动态供给的Delete
或静态Retain
- 生产环境:避免使用
hostPath
,优先选择云存储或分布式存储(如 Ceph/Portworx)
- 必填字段:创建 PV 时必须指定
3.3.3.2.PVC 参数列表
pvc.spec 下面字段
字段名称 | 类型 | 必填 | 描述 | 备注说明 |
---|---|---|---|---|
accessModes | []string | ✓ | 期望的卷访问模式 | 定义应用需要的访问权限: - ReadWriteOnce (RWO):单节点读写- ReadOnlyMany (ROX):多节点只读- ReadWriteMany (RWX):多节点读写必须与目标 PV 的访问模式兼容 |
resources | Object | ✓ | 资源请求 | 必须包含 requests.storage 字段(如 storage: 5Gi ),表示最小存储需求。也可设置 limits.storage 限制最大容量 |
storageClassName | string | ✗ | 所需 StorageClass 名称 | 1.为空时使用默认 StorageClass(集群管理员需要事先注明哪个是default sc);2.设为 "" 显式禁止动态供给;3.指定名称则要求匹配特定存储类 |
volumeMode | string | ✗ | 卷模式 | Filesystem (默认):需要文件系统Block :需要原始块设备必须与目标 PV 的 volumeMode 一致 |
volumeName | string | ✗ | 直接绑定目标 PV 名称 | 强制绑定特定 PV(绕过自动绑定机制),需确保 PV 存在且未被绑定 |
selector | Object | ✗ | PV 标签选择器 | 通过标签选择匹配的 PV(如 matchLabels: { tier: ssd } ),与 storageClassName 结合使用 |
dataSource | Object | ✗ | 数据源(快照/已有 PVC) | 支持三种类型: 1. VolumeSnapshot (快照恢复,需启用 VolumeSnapshotDataSource)2. 已有 PVC(克隆卷) 3. 自定义资源(Alpha 阶段) 需存储后端支持 |
- 关键注意事项
- 核心必填字段:
accessModes
和resources.requests.storage
是创建 PVC 的必要条件 - 动态供给流程:
- 当
storageClassName
指定有效存储类时,自动创建符合要求的 PV - 若存储类不存在或后端不支持,PVC 将处于
Pending
状态
- 当
- 数据源克隆:
- 卷快照:从 VolumeSnapshot 恢复数据(需 CSI 驱动支持)
- PVC 克隆:复制另一个 PVC 的数据(需同一命名空间且 storageClass 相同)
- 绑定限制:
volumeName
优先级最高(显式指定 PV)selector
次之(标签匹配 PV)- 最后按
storageClassName
+resources
+accessModes
自动匹配
- 容量验证:
- PVC 请求的容量必须 ≤ PV 的
capacity.storage
- 动态供给时实际 PV 容量可能略大于请求值(取决于存储系统分配策略)
- PVC 请求的容量必须 ≤ PV 的
- 核心必填字段:
3.3.4.PV/PVC 的绑定规则
3.3.4.1.绑定流程概览
- 注意:
- PV、PVC 的 写权限绑定 是严格1对1的。
- PV、PVC 的 容量匹配规则:就近向上匹配规则
- 比如当前有1Gi、5Gi、10Gi三个PV,现在有一个PVC 想要申请2Gi。那么就会匹配到最小能满足容量需求的5Gi PV,与之绑定
- 只读权限对绑定关系有所放宽,当PV 的 accessModes 包含 ReadOnlyMany 时,Kubernetes 允许该 PV 同时绑定多个 PVC
3.3.4.2.核心匹配规则
3.3.4.2.1. 存储类(StorageClass)匹配
PVC 配置 | 匹配的 PV 要求 | 动态供给行为 |
---|---|---|
未设置 storageClassName | PV 未设置 storageClassName | 使用默认 StorageClass |
storageClassName: “” | PV 的 storageClassName 为 “” | 禁止动态供给 |
storageClassName: “gold” | PV 的 storageClassName 为 “gold” | 使用 “gold” StorageClass |
3.3.4.2.2.访问模式(Access Modes)
- 严格匹配:PVC 请求的访问模式必须是 PV 支持模式的子集
- 优先级:
ReadWriteOnce
<ReadOnlyMany
<ReadWriteMany
- 示例:
- PVC 请求
[ReadWriteOnce]
→ 可绑定支持[ReadWriteOnce, ReadOnlyMany]
的 PV - PVC 请求
[ReadWriteMany]
→ 不可绑定仅支持[ReadWriteOnce]
的 PV
- PVC 请求
3.3.4.2.3.存储容量(Capacity)
情况 | 绑定规则 |
---|---|
静态 PV | PVC 请求 ≤ PV 容量 |
动态供给 | PV 容量 ≥ PVC 请求(可能略大) |
PVC 设置 limits.storage | PV 容量必须在 PVC 请求范围内 |
3.3.4.2.4.卷模式(VolumeMode)
类型 | 要求 | 使用场景 |
---|---|---|
Filesystem | PV/PVC 必须同为 Filesystem | 常规文件系统(默认) |
Block | PV/PVC 必须同为 Block | 数据库/裸设备访问 |
3.3.4.3.特殊绑定机制
3.3.4.3.1.直接绑定(volumeName)
# PVC 强制绑定特定 PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: myclaim
spec:volumeName: my-pv-001 # 直接指定 PV 名称accessModes:- ReadWriteOnceresources:requests:storage: 5Gi
- 规则:绕过自动选择器,直接绑定指定 PV
- 要求:PV 必须存在且未被绑定
- 风险:PV 不可用时 PVC 将处于 Pending 状态
3.3.4.3.2.标签选择器(Selector)
# PVC 使用标签选择 PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: ssd-claim
spec:selector:matchLabels:storage-tier: "ssd" # 选择含此标签的 PVaccessModes:- ReadWriteOnceresources:requests:storage: 100Gi
- 匹配逻辑:AND 操作(必须满足所有标签)
- 优先级:高于 StorageClass 匹配
- 典型用例:区分不同性能级别的存储(SSD/HDD)
3.3.4.3.3.延迟绑定(VolumeBindingMode)
# StorageClass 配置延迟绑定
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer # 延迟绑定
- 工作原理:等待 Pod 调度后,在目标节点创建/绑定 PV
- 适用场景:
- 本地存储(Local PV)
- 拓扑受限的存储(如区域化云存储)
- 优势:避免 PV 与 Pod 节点不匹配的问题
3.3.4.4.回收策略影响
回收策略 | PVC 删除后 PV 状态 | 数据处置方式 | 重新绑定要求 |
---|---|---|---|
Retain | Released | 保留数据(需手动清理) | 管理员手动重置为 Available |
Delete | 被删除 | 自动删除存储资源 | 需创建新 PV |
Recycle | Available | 擦除数据(已废弃) | 自动可用(不再推荐) |
3.3.4.5.最佳实践
3.3.4.5.1.生产环境配置
# 推荐的安全配置
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: safe-claim
spec:storageClassName: encrypted-ssdaccessModes:- ReadWriteOnceresources:requests:storage: 50Gilimits:storage: 55Gi # 防止过大 PV 造成资源浪费volumeMode: Filesystem
3.3.4.5.2.故障排查指南
问题现象 | 可能原因 | 解决方案 |
---|---|---|
PVC 长期 Pending | 无匹配 PV/StorageClass 错误 | 检查 PV 可用性/SC 配置 |
绑定后 Pod 无法挂载 | 访问模式不兼容 | 验证 PV/PVC accessModes |
数据意外丢失 | 回收策略为 Delete | 改为 Retain 并设置备份 |
多节点 Pod 挂载失败 | PV 仅支持 ReadWriteOnce | 改用支持 ReadWriteMany 的存储 |
3.3.4.5.3.高级策略
- 跨命名空间共享:使用 ReadOnlyMany PV + 多个命名空间的 PVC
- 容量扩展:v1.24+ 支持在线调整 PVC 容量(需 StorageClass 允许)
- 克隆卷:通过 dataSource 从现有 PVC 创建新卷
dataSource:kind: PersistentVolumeClaimname: source-pvc
3.4.StorageClass
3.4.1.为什么需要 StorageClass
3.4.1.1.直接使用 PV/PVC 存在什么问题
- 持久化存储已经有个PV/PVC,把 集群存储管理 和 应用存储请求 做了职责分离,为什么又多出一个StorageClass?
- 从前面我们知道,PV、PVC 的 容量匹配规则:就近向上匹配规则。这样就可能产生存储碎片。比如:
- 当下有3个ReadWriteOnce的PV,容量分别是1Gi、5Gi、10Gi
- 然后当下有4个应用都有自己的PVC,都创建了访问模式为ReadWriteOnce类型的PVC,申请容量为1Gi、2Gi、3Gi、5G
- 比如此时绑定关系形成这种:1Gi<–>1Gi、2Gi<–>5Gi、3Gi<–>10Gi,那么申请5Gi的PVC就无法找到可用PV,会一直Pending
- 而申请到5Gi、10Gi的两个应用,压根用不到那么多存储,存储资源就被浪费了
3.4.1.2.StorageClass动态供应PV
- 怎么解决这种问题?
- 问题根音:PV是集群管理员手动静态创建的,每个都代表集群的一块真实存储,集群管理员只能把容量设置为一个固定的大小,无法动态调整
- 那么 如果PV能够根据PVC自动创建就好了。PVC需要1Gi资源,就创建一个1Gi资源大小的PV,给他绑定,这样就 避免一个非常大的PV被一个小需求PVC占用,导致资源浪费的问题
- 于是就需要有个角色,能够根据用户的PVC,自动创建PV。StorageClass正是这样的角色。
3.4.1.3.StorageClass、PV、PVC之间的关系
- 集群管理员 根据集群支持的存储类型,创建对应的StorageClass,并且要把每一种 StorageClass 对应的 CSIDriver 安装好。CSIDriver才是真正用于动态创建PV的程序
- 用户 根据需求创建PVC,指定StorageClass,或不指定时由默认storageClass负责动态创建PV,并完成PV/PVC绑定
- 应用 通过引用对应的PVC,来引用存储。对应的PV被attach到相应主机,然后被mount进pod
3.4.2.StorageClass 介绍
- StorageClass 是 Kubernetes 中定义存储类型模板的 API 对象,它抽象了底层存储系统的细节,允许管理员提供不同类型的存储(如 SSD/HDD/高速网络存储)供用户按需选择。
- 每一种 StorageClass 都代表了 一种底层存储,有对应的存储厂商为其编写provisioner。应用编写的PVC指定了对应了StorageClass之后,对应的 StorageClass 控制器就负责为之创建 PV,实现PV的动态供应。
3.4.3.默认StorageClass
- StorageClass通过annotation:
storageclass.kubernetes.io/is-default-class: "true"
标识一个sc是否为默认sc
特性 | 默认 StorageClass | 非默认 StorageClass |
---|---|---|
定义方式 | 通过注解标记 is-default-class: "true" | 无特殊注解 |
数量限制 | 集群中最多只能有一个生效 | 可创建任意数量 |
PVC 默认行为 | 未指定 storageClassName 的 PVC 自动使用 | PVC 必须显式指定名称才能使用 |
创建必要性 | 强烈推荐为集群设置默认类 | 按需创建 |
Kubernetes 内置 | 部分发行版预置(如 EKS 的 gp2) | 无预置 |
查看命令 | kubectl get sc 中标注 (default) | 无特殊标注 |
优先级 | 当存在默认类时,优先级最高 | 需显式指定才生效 |
3.4.4.StorageClass 参数列表
- StorageClass没有spec字段,所有属性与apiVersion同级
字段名称 | 类型 | 必填 | 默认值 | 描述 | 实际应用示例 |
---|---|---|---|---|---|
provisioner | string | ✓ | 无 | 存储供应驱动名称,决定如何创建 PV | ebs.csi.aws.com (AWS EBS CSI 驱动)disk.csi.azure.com (Azure Disk) |
parameters | map[string]string | ✗ | {} | 供应驱动的配置参数,特定于每种存储类型 | AWS EBS:type: gp3 iops: "4000" NFS: server: nfs.example.com |
reclaimPolicy | string | ✗ | Delete | 动态 PV 的回收策略 | Retain (保留存储资源)Delete (自动删除) |
allowVolumeExpansion | boolean | ✗ | false | 是否允许 PVC 创建后扩展容量 | true (启用在线扩容功能) |
volumeBindingMode | string | ✗ | Immediate | PV 绑定时机策略 | Immediate 立即创建,WaitForFirstConsumer (延迟到 Pod 调度时绑定,适合本地存储) |
allowedTopologies | []Object | ✗ | [] | 限制 PV 创建的拓扑域(需启用 VolumeScheduling 特性) | 限制在特定区域创建:matchLabelExpressions: - key: topology.kubernetes.io/zone values: [us-east-1a] |
mountOptions | []string | ✗ | [] | 自动创建的 PV 的挂载选项 | ["discard", "noatime"] (SSD 优化)["nfsvers=4.1"] (NFS 版本指定) |
- 关键字段深度说明:
-
provisioner:
- 内置驱动:
kubernetes.io/
前缀(如kubernetes.io/gce-pd
) - CSI 驱动:
<driver-name>
(如ebs.csi.aws.com
) - 本地存储:
kubernetes.io/no-provisioner
- 内置驱动:
-
volumeBindingMode:
Immediate
:PVC 创建后立即绑定(适合网络存储)WaitForFirstConsumer
:延迟到 Pod 调度时绑定(适合本地存储)
-
reclaimPolicy:
Delete
:删除 PVC 时自动删除存储资源Retain
:删除 PVC 后保留存储资源(需手动清理)
-
3.4.5.StorageClass 典型配置示例
3.4.5.1.AWS gp3 存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: aws-gp3annotations:storageclass.kubernetes.io/is-default-class: "true" # 设为默认
provisioner: ebs.csi.aws.com
parameters:type: gp3iops: "4000"throughput: "250"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete
3.4.5.2.本地 SSD 存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: local-ssd
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:- key: topology.kubernetes.io/zonevalues:- us-west1-a
3.4.5.3.NFS 共享存储
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: nfs-shared
provisioner: nfs.csi.k8s.io
parameters:server: nfs-server.example.comshare: /exports
mountOptions:- nfsvers=4.1- noresvport
3.4.6.高级特性
3.4.6.1.卷扩展(Volume Expansion)
# 1. StorageClass 启用扩展
allowVolumeExpansion: true# 2. 编辑 PVC 请求更大容量
kubectl patch pvc mypvc -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}'
要求:
- 存储后端支持在线扩容
- 文件系统支持调整(如 ext4/XFS)
3.4.6.2.拓扑感知(Topology)
allowedTopologies:
- matchLabelExpressions:- key: topology.kubernetes.io/zonevalues: [ "us-east-1a", "us-east-1b" ]
- 确保存储卷与 Pod 在同一故障域
- 配合
WaitForFirstConsumer
使用
3.4.6.3.快照管理
# 创建 VolumeSnapshotClass
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:name: aws-snapshot-class
driver: ebs.csi.aws.com
deletionPolicy: Delete
3.4.7.常见Volume使用场景
3.4.7.1.独占Local Volume使用过程
- 对于高 IO p/s的应用,可以通过创建独占本地存储,把主机自带的一些硬盘分配给他,提高读写性能,减少网络io
3.4.7.2.动态 Dynamic Local Volume
- Dynamic Local 可以将底层多个硬盘组合成一个逻辑盘,满足一些特别大存储容量的需求。比如需要100T,现在有10块10T的硬盘,单独都满足不了,但是组合在一起就可以
- 但是Dynamic Local会有下面的一些问题:
3.5.8.StorageClass 生产化实践经验
配置项 | 推荐值 | 原因 |
---|---|---|
默认 StorageClass | 必须设置一个 | 避免未指定类的 PVC 失败 |
回收策略 | 关键数据用 Retain | 防止误删数据 |
卷绑定模式 | 本地存储用 WaitForFirstConsumer | 确保 PV 创建在 Pod 调度节点 |
容量扩展 | 按需启用 | 避免存储后端不支持导致故障 |
3.4.9.常见存储驱动比较
驱动类型 | 代表实现 | 适用场景 | 特点 |
---|---|---|---|
云厂商 CSI | AWS EBS, GCE PD, Azure Disk | 公有云环境 | 深度集成云平台功能,支持快照/扩容 |
网络存储 CSI | NFS, Ceph RBD, GlusterFS | 混合云/私有云 | 跨平台兼容,需自建存储集群 |
本地存储 | Local PV | 高性能需求场景 | 低延迟,但数据持久性与节点绑定 |
容器原生存储 | Portworx, Longhorn | 容器化存储方案 | 专为 Kubernetes 设计,支持高级功能(如加密/压缩) |
4.问题辨析
4.1.为什么 CRI、CNI、CSI 插件的机制没有统一呢?
Kubernetes 中的 CRI、CNI、CSI 插件机制之所以未采用统一实现方式(如 CRI/CSI 使用 gRPC 远程调用,而 CNI 使用本地可执行文件),核心原因在于它们解决不同领域的扩展问题,且在接口设计时考虑了技术场景的差异、历史演进路径以及性能与复杂度平衡。
🔧 一、接口定位与技术场景的差异
-
CRI(容器运行时接口)与 CSI(容器存储接口)
- 跨节点协调需求:
CRI 管理容器生命周期(如创建/销毁容器),CSI 管理存储卷的创建/挂载等操作,这些操作通常需要跨节点协作(例如存储卷需在多个节点间挂载/卸载)。gRPC 的跨进程通信能力天然适合此类分布式场景 - 复杂操作与状态管理:
容器运行时需支持镜像拉取、资源隔离等复杂操作;存储卷需支持快照、扩容等高级功能。gRPC 的结构化数据封装(Protocol Buffers)和双向流式通信能更好地处理此类复杂交互
- 跨节点协调需求:
-
CNI(容器网络接口)
- 单节点本地操作:
CNI 的核心操作(如为 Pod 添加/删除网卡、配置 IP 地址)仅在当前节点完成,无需跨节点协调。本地可执行文件(如bridge
、flannel
插件)通过标准输入/输出(STDIO)传递 JSON 配置,执行效率更高,延迟更低 - 轻量化与快速响应:
网络配置需在 Pod 启动/销毁时实时生效,本地命令调用(如 shell 脚本)的低开销更适合这种高频、短时操作
- 单节点本地操作:
⏳ 二、历史演进与生态兼容性
-
CNI 的早期设计背景
- CNI 最初由 CoreOS 为 rkt 容器引擎设计,后由 Kubernetes 采纳。其基于命令行的设计源于 Unix 哲学“小而专的工具”,便于与现有网络工具(如
iproute2
、iptables
)集成 - 简单性优先:
早期 Kubernetes 网络需求聚焦基础连通性(如 Pod 间通信),命令式插件已足够满足,无需引入 gRPC 的复杂度
- CNI 最初由 CoreOS 为 rkt 容器引擎设计,后由 Kubernetes 采纳。其基于命令行的设计源于 Unix 哲学“小而专的工具”,便于与现有网络工具(如
-
CRI/CSI 的后期标准化
- CRI 和 CSI 诞生于 Kubernetes 解耦核心组件的阶段:
- CRI(Kubernetes 1.5+)解决 Docker 运行时与 kubelet 的强耦合问题,gRPC 实现运行时热插拔(如从 Docker 切换为 containerd)
- CSI(Kubernetes 1.9+)替代 in-tree 存储插件,通过 gRPC 支持厂商自定义驱动(如 AWS EBS、Ceph),避免修改 Kubernetes 核心代码
- CRI 和 CSI 诞生于 Kubernetes 解耦核心组件的阶段:
⚖️ 三、性能与复杂度的权衡
维度 | CNI(本地命令) | CRI/CSI(gRPC) |
---|---|---|
延迟 | 微秒级(直接调用系统工具) | 毫秒级(跨进程通信) |
部署复杂度 | 低(只需二进制文件) | 高(需部署 gRPC Server 和 Sidecar) |
功能扩展性 | 弱(仅支持基础网络配置) | 强(支持异步调用、双向流、高级特性) |
跨节点通信 | 不适用 | 原生支持 |
🔮 四、未来可能的演进方向
尽管机制不同,但社区正尝试部分统一:
- CNI 的 gRPC 化尝试:
部分项目(如 Cilium)通过 CNI gRPC 库(如cni-service
)将插件封装为 gRPC 服务,兼顾性能与跨节点能力,但尚未成为主流 - Sidecar 模式的补充:
CSI 已通过 Node Plugin(DaemonSet 形式)解决部分本地操作问题,平衡了 gRPC 的分布式优势与节点级操作需求
💎 总结:差异存在的必然性
- CNI 的本地命令机制是性能敏感型单节点操作的最优解。
- CRI/CSI 的 gRPC 机制是复杂状态管理与跨节点协调的必要选择。
三者设计差异本质上是 Kubernetes 在不同领域权衡性能、复杂度与扩展性的结果,而非技术缺陷。未来若出现统一框架,需同时解决网络配置的实时性、运行时/存储的分布式一致性难题。