Kubernetes控制平面组件:Kubelet详解(八):容器存储接口 CSI

云原生学习路线导航页(持续更新中)

本文是 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 存储插件架构

Data Plane CSI Node Pod Control Plane CSI Controller Pod 监听 PVC/PV 管理 VolumeAttachment 触发流程 触发流程 gRPC over UDS 注册驱动 调用 Node 接口 挂载/卸载 gRPC over UDS 操作存储 操作存储 创建 PVC/Pod 存储对象同步 读写对象 读写对象 kubelet VolumeManager MOUNT node-driver-registrar CSI Driver Storage System PersistentVolumeController kube-controller-manager AttachDetachController CSI Sidecar Containers external-provisioner external-attacher external-resizer external-snapshotter livenessprobe CSI Driver 用户 API Server etcd

  • Control Plane
    • kube-controller-manager
      • PersistentVolumeController:监听 PVC,触发动态供给流程(不直接调用 CSI)
      • AttachDetachController:管理 VolumeAttachment 对象(不直接调用 CSI)
    • csi controller pod
      • csi 的控制平面pod,其中容器可以分成:csi sidecar、csi controller service driver
  • Data Plane
    • kubelet
      • VolumeManager:调用 CSI Node 接口(需先通过 node-driver-registrar 注册驱动)
      • 执行两阶段挂载: 挂载到全局目录、绑定挂载到pod文件系统
    • csi node pod
      • csi 的 node pod,其中容器可以分成:node-driver-register、csi node service driver
  • CSI 各团队职责分工:
    • 存储厂商需要提供的代码实体:csi controller service driver、csi node service driver
    • csi sidecar(包括node-driver-register)是 Kubernetes 社区官方维护的组件,代码托管在 Kubernetes SIG Storage 项目中
2.3.2.2.架构设计核心思想
  1. 解耦与扩展性
    • Kubernetes 核心组件仅管理存储对象(PVC/PV/VolumeAttachment)
    • Sidecar 代理将对象变更转换为 CSI 接口调用
  2. 高可用保障
    • Controller Sidecar 需单副本部署,避免存储操作冲突
    • 可通过 Leader Election 实现多副本选主(如 CubeFS 优化方案)
  3. 安全隔离
    • CSI Driver 以独立进程运行,故障不会影响 kubelet/kube-controller-manager
    • 卷操作权限由 StorageClass 中的 secretRef 动态注入

此架构严格遵循 CSI 规范,已被 AWS EBS、Google Persistent Disk、Ceph RBD 等主流存储系统采用。实际部署时需注意 Sidecar 版本与 Kubernetes 的兼容性(详见 CSI 官方文档

2.3.2.3.存储卷生命周期流程示例(动态供给 + Pod 挂载)

用户 API Server PersistentVolumeController external-provisioner CSI Driver AttachDetachController external-attacher kubelet node-driver-registrar etcd Storage Scheduler 创建 PVC 存储 PVC 触发 CreateVolume gRPC: CreateVolume() 创建存储卷 返回卷 ID 创建 PV 并绑定 PVC 卷供应完成 创建 Pod(引用 PVC) 调度 Pod 到节点 创建 VolumeAttachment gRPC: ControllerPublishVolume() 附加卷到节点 注册 CSI Driver NodeStageVolume() 挂载到全局目录 NodePublishVolume() 绑定挂载到 Pod 路径 容器启动,使用存储卷 用户 API Server PersistentVolumeController external-provisioner CSI Driver AttachDetachController external-attacher kubelet node-driver-registrar etcd Storage Scheduler

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 使用示例

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          volumeMounts:
          - mountPath: /cache
            name: cache-volume
      volumes:
      - name: cache-volume
        emptyDir: {}

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 使用示例

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          volumeMounts:
          - mountPath: /log
            name: logs-dir
      volumes:
      - name: logs-dir
        hostPath: 
          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,绑定成功后,代表存储资源满足了需求,应用就可以正常使用了

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 引用 包含 namespacename,用于预绑定特定 PVC(防止自动绑定)
mountOptions []string 挂载选项 底层存储的挂载参数(如 NFS 的 nfsvers=4.1 或 Ext4 的 noatime
nodeAffinity Object 卷的节点亲和性约束 限制可使用该卷的节点(常用于 local 卷类型),需定义 required 节点选择器
awsElasticBlockStore Object AWS EBS 磁盘配置 需指定 volumeIDfsType(如 ext4),仅适用于 AWS 环境
azureDisk Object Azure 数据磁盘配置 需指定 diskNamediskURI,适用于 Azure 托管磁盘
azureFile Object Azure 文件服务配置 需提供 secretName(存储账号密钥)和 shareName(文件共享名)
cephfs Object Ceph FS 存储配置 需配置 monitors(Ceph 监控节点)和 path,支持 secretRef 认证
cinder Object OpenStack Cinder 卷配置 需指定 volumeIDfsType,适用于 OpenStack 环境
csi Object 外部 CSI 驱动存储配置 现代存储扩展方案,需配置 driver 名称和 volumeHandle(存储系统唯一标识)
fc Object 光纤通道 (Fibre Channel) 配置 需指定 targetWWNs(目标全球端口名)和 lun 号,用于 SAN 存储
flexVolume Object 基于 exec 插件的通用卷配置 需指定 driver 路径和自定义参数,支持第三方存储驱动
flocker Object Flocker 存储配置 需配置 datasetNamedatasetUUID(已弃用),用于 Docker 集群存储
gcePersistentDisk Object GCP 持久化磁盘配置 需指定 pdNamefsType,仅适用于 Google Cloud
glusterfs Object GlusterFS 卷配置 需配置 endpoints(Gluster 集群端点)和 path(卷路径)
hostPath Object 主机目录配置 仅开发测试使用,需指定 pathtype(如 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 块设备配置 需配置 monitorspoolimage,支持 secretRef 认证
scaleIO Object ScaleIO 存储配置 需指定 gatewaysystemprotectionDomain,适用于 Dell EMC ScaleIO
storageos Object StorageOS 卷配置 需配置 volumeNamesecretRef,适用于 StorageOS 容器存储
vsphereVolume Object vSphere 卷配置 需指定 volumePath(VMDK 路径)和 fsType,仅适用于 vSphere 环境
  • 关键说明
    1. 必填字段 :创建 PV 时必须指定 accessModes, capacity, persistentVolumeReclaimPolicy
    2. 存储后端互斥:25+ 种存储插件配置(如 AWS/GCP/Azure/Ceph/NFS 等),只能启用一种
    3. 动态绑定storageClassName 用于关联 StorageClass 实现动态供给
    4. 安全约束nodeAffinity 确保 Pod 调度到有本地存储的节点
    5. 卷模式volumeMode=Block 时需应用直接操作块设备
    6. 弃用警告Recycle 回收策略已被废弃,建议使用动态供给的 Delete 或静态 Retain
    7. 生产环境 :避免使用 hostPath,优先选择云存储或分布式存储(如 Ceph/Portworx)
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 阶段) 需存储后端支持
  • 关键注意事项
    1. 核心必填字段accessModesresources.requests.storage 是创建 PVC 的必要条件
    2. 动态供给流程
      • storageClassName 指定有效存储类时,自动创建符合要求的 PV
      • 若存储类不存在或后端不支持,PVC 将处于 Pending 状态
    3. 数据源克隆
      • 卷快照:从 VolumeSnapshot 恢复数据(需 CSI 驱动支持)
      • PVC 克隆:复制另一个 PVC 的数据(需同一命名空间且 storageClass 相同)
    4. 绑定限制
      • volumeName 优先级最高(显式指定 PV)
      • selector 次之(标签匹配 PV)
      • 最后按 storageClassName + resources + accessModes 自动匹配
    5. 容量验证
      • PVC 请求的容量必须 ≤ PV 的 capacity.storage
      • 动态供给时实际 PV 容量可能略大于请求值(取决于存储系统分配策略)

3.3.4.PV/PVC 的绑定规则

3.3.4.1.绑定流程概览

是 否 是 否 匹配 不匹配 创建PVC 是否指定volumeName? 直接绑定指定PV 是否配置selector? 筛选匹配标签的PV 按StorageClass筛选 容量/访问模式/卷模式检查 完成绑定 触发动态供给 创建新PV

  • 注意:
    • 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
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)
yaml 复制代码
# PVC 强制绑定特定 PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  volumeName: my-pv-001  # 直接指定 PV 名称
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  • 规则:绕过自动选择器,直接绑定指定 PV
  • 要求:PV 必须存在且未被绑定
  • 风险:PV 不可用时 PVC 将处于 Pending 状态
3.3.4.3.2.标签选择器(Selector)
yaml 复制代码
# PVC 使用标签选择 PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ssd-claim
spec:
  selector:
    matchLabels:
      storage-tier: "ssd"  # 选择含此标签的 PV
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Gi
  • 匹配逻辑:AND 操作(必须满足所有标签)
  • 优先级:高于 StorageClass 匹配
  • 典型用例:区分不同性能级别的存储(SSD/HDD)
3.3.4.3.3.延迟绑定(VolumeBindingMode)
yaml 复制代码
# 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.生产环境配置
yaml 复制代码
# 推荐的安全配置
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: safe-claim
spec:
  storageClassName: encrypted-ssd
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
    limits:
      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 创建新卷

    yaml 复制代码
    dataSource:
      kind: PersistentVolumeClaim
      name: 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的动态供应。

管理员 创建 StorageClass 定义存储类型模板 用户 创建 PVC 指定 StorageClass 自动创建匹配的 PV
User K8s Control Plane CSI Driver Cloud Storage 创建 PVC (指定 StorageClass) 调用 CreateVolume 创建存储卷 返回卷ID 创建 PV 对象 绑定 PVC/PV User K8s Control Plane CSI Driver Cloud Storage

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) 无特殊标注
优先级 当存在默认类时,优先级最高 需显式指定才生效

未设置 存在 不存在 显式设置名称 设置为 '' 创建 PVC 是否设置 storageClassName? 检查是否存在默认 StorageClass 使用默认类创建 PV PVC 保持 Pending 使用指定存储类 禁止动态供给 仅绑定静态PV

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 版本指定)
  • 关键字段深度说明:
    1. provisioner

      • 内置驱动:kubernetes.io/ 前缀(如 kubernetes.io/gce-pd
      • CSI 驱动:<driver-name>(如 ebs.csi.aws.com
      • 本地存储:kubernetes.io/no-provisioner
    2. volumeBindingMode

      • Immediate:PVC 创建后立即绑定(适合网络存储)
      • WaitForFirstConsumer:延迟到 Pod 调度时绑定(适合本地存储)
    3. reclaimPolicy

      • Delete:删除 PVC 时自动删除存储资源
      • Retain:删除 PVC 后保留存储资源(需手动清理)

3.4.5.StorageClass 典型配置示例

3.4.5.1.AWS gp3 存储类
yaml 复制代码
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: aws-gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true" # 设为默认
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "4000"
  throughput: "250"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete
3.4.5.2.本地 SSD 存储类
yaml 复制代码
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-ssd
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
  - key: topology.kubernetes.io/zone
    values:
    - us-west1-a
3.4.5.3.NFS 共享存储
yaml 复制代码
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-shared
provisioner: nfs.csi.k8s.io
parameters:
  server: nfs-server.example.com
  share: /exports
mountOptions:
  - nfsvers=4.1
  - noresvport

3.4.6.高级特性

3.4.6.1.卷扩展(Volume Expansion)
yaml 复制代码
# 1. StorageClass 启用扩展
allowVolumeExpansion: true

# 2. 编辑 PVC 请求更大容量
kubectl patch pvc mypvc -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}'

要求

  • 存储后端支持在线扩容
  • 文件系统支持调整(如 ext4/XFS)
3.4.6.2.拓扑感知(Topology)
yaml 复制代码
allowedTopologies:
- matchLabelExpressions:
  - key: topology.kubernetes.io/zone
    values: [ "us-east-1a", "us-east-1b" ]
  • 确保存储卷与 Pod 在同一故障域
  • 配合 WaitForFirstConsumer 使用
3.4.6.3.快照管理
yaml 复制代码
# 创建 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 使用本地可执行文件 ),核心原因在于它们解决不同领域的扩展问题,且在接口设计时考虑了技术场景的差异历史演进路径 以及性能与复杂度平衡

🔧 一、接口定位与技术场景的差异

  1. CRI(容器运行时接口)与 CSI(容器存储接口)

    • 跨节点协调需求
      CRI 管理容器生命周期(如创建/销毁容器),CSI 管理存储卷的创建/挂载等操作,这些操作通常需要跨节点协作 (例如存储卷需在多个节点间挂载/卸载)。gRPC 的跨进程通信能力天然适合此类分布式场景
    • 复杂操作与状态管理
      容器运行时需支持镜像拉取、资源隔离等复杂操作;存储卷需支持快照、扩容等高级功能。gRPC 的结构化数据封装 (Protocol Buffers)和双向流式通信能更好地处理此类复杂交互
  2. CNI(容器网络接口)

    • 单节点本地操作
      CNI 的核心操作(如为 Pod 添加/删除网卡、配置 IP 地址)仅在当前节点完成 ,无需跨节点协调。本地可执行文件(如 bridgeflannel 插件)通过标准输入/输出(STDIO)传递 JSON 配置,执行效率更高,延迟更低
    • 轻量化与快速响应
      网络配置需在 Pod 启动/销毁时实时生效,本地命令调用(如 shell 脚本)的低开销更适合这种高频、短时操作

⏳ 二、历史演进与生态兼容性

  1. CNI 的早期设计背景

    • CNI 最初由 CoreOS 为 rkt 容器引擎 设计,后由 Kubernetes 采纳。其基于命令行的设计 源于 Unix 哲学"小而专的工具",便于与现有网络工具(如 iproute2iptables)集成
    • 简单性优先
      早期 Kubernetes 网络需求聚焦基础连通性(如 Pod 间通信),命令式插件已足够满足,无需引入 gRPC 的复杂度
  2. 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 核心代码

⚖️ 三、性能与复杂度的权衡

维度 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 在不同领域权衡性能、复杂度与扩展性的结果,而非技术缺陷。未来若出现统一框架,需同时解决网络配置的实时性、运行时/存储的分布式一致性难题。
相关推荐
蚊子不吸吸3 小时前
在Docker、KVM、K8S常见主要命令以及在Centos7.9中部署的关键步骤学习备存
linux·学习·docker·kubernetes·centos·k8s·kvm
编码如写诗1 天前
【国产化-k8s】超混合架构-x86+arm64+欧拉+麒麟V10部署k8s1.32+kubesphere4.1
容器·架构·kubernetes
明月看潮生1 天前
青少年编程与数学 01-011 系统软件简介 24 Kubernetes 容器编排系统
青少年编程·容器·kubernetes·系统软件·编程与数学
紫神1 天前
使用sealos安装k8s
云原生·kubernetes
955.1 天前
k8s从入门到放弃之数据存储
云原生·容器·kubernetes
Fireworkitte1 天前
Docker Swarm 与 Kubernetes 在集群管理上的主要区别
docker·容器·kubernetes
ThisIsClark1 天前
【Kubernetes】以LOL的视角打开K8s
游戏·云原生·容器·kubernetes·lol
tswddd1 天前
记录:注册k8s cluster账号
kubernetes
彼将取而代之1 天前
从头搭建环境安装k8s遇到的问题
云原生·容器·kubernetes