Kubernetes CSI 详解
一、CSI 概述
Container Storage Interface(CSI) 是一个标准化接口,用于定义容器编排系统(如 Kubernetes)与存储供应商(Storage Providers)之间的交互方式。其目标是让存储供应商开发的插件能够兼容多个容器编排系统(如 Kubernetes、Mesos、Swarm 等),从而简化存储资源的管理和使用。
在 Kubernetes 中,CSI 提供了一种标准的方式,用于与第三方存储供应商(如 NFS、AWS EFS、Ceph、GlusterFS 等)对接,实现存储卷的创建、挂载、卸载等操作。CSI 的引入取代了旧的 FlexVolume 插件机制,提供了更灵活、更强大的存储管理能力。
二、CSI 系统架构
图源:https://kubernetes.feisky.xyz/extension/volume/csi#yuan-li
Controller Server(控制服务器)
- 功能:负责存储卷的生命周期管理,包括卷的创建、删除、挂载、卸载等操作。
- 部署方式:通常以 StatefulSet 或 Deployment 的形式运行,确保高可用性和唯一性。
- 组件:
- CSI 驱动容器:由第三方存储供应商提供,实现 CSI 接口的IdentityServer和ControllerServer。
- Sidecar 容器(如 external-provisioner、external-attacher 等):负责与 Kubernetes 的组件(如 kube-controller-manager)通信,触发 CSI 驱动的操作。
Node Server(节点服务器)
- 功能:负责节点级别的存储卷挂载和卸载操作。
- 部署方式:以 DaemonSet 的形式运行,确保每个节点上都有 CSI 插件实例。
- 组件:
- CSI 驱动容器:由第三方存储供应商提供,实现 CSI 接口的IdentityServer和NodeServer。
- Sidecar 容器(如 node-driver-registrar):负责与 kubelet 通信,注册 CSI 驱动。
三、CSI 组件构成及功能
(一)CSI Controller
CSI Controller 是 CSI 插件的控制端,负责存储卷的生命周期管理。其主要组件包括:
Sidecar 容器
- external-provisioner:
- 监听 PersistentVolumeClaim(PVC)的变化。
- 触发 CSI 驱动的 CreateVolume 和 DeleteVolume 操作,实现存储卷的创建和删除。
- external-attacher:
- 监听 VolumeAttachment 的变化。
- 触发 CSI 驱动的 ControllerPublish 和 ControllerUnpublish 操作,实现存储卷的挂载和卸载。
- external-resizer(可选):
- 监听 PersistentVolumeClaim 的大小变化。
- 触发 CSI 驱动的 ResizeVolume 操作,实现存储卷的容量调整(Beta 阶段)。
- externalsnapshotter(可选):
- 监听 VolumeSnapshot 的变化。
- 触发 CSI 驱动的 CreateSnapshot 和 DeleteSnapshot 操作,实现存储卷的快照管理(Alpha 阶段)。
CSI Driver 容器
- 由第三方存储供应商提供,实现 CSI 接口。
- 通过 Unix Domain Socket 与 Sidecar 容器通信,完成具体的存储卷操作。
部署建议
CSI Controller 通常以 StatefulSet 或 Deployment 的形式运行,副本数设为 1,以确保唯一性。
(二)CSI Node
CSI Node 是 CSI 插件的节点端,负责节点级别的存储卷管理。其主要组件包括:
Sidecar 容器
- node-driver-registrar:
- 将 CSI 驱动注册到 kubelet。
- 监听 kubelet 的操作请求,触发 CSI 驱动的节点端接口(如 NodePublishVolume、NodeUnpublishVolume 等)。
CSI Driver 容器
- 由第三方存储供应商提供,实现 CSI 接口。
- 接收 kubelet 的调用,完成存储卷的挂载、卸载等操作。
通信方式
- node-driver-registrar 与 kubelet 通过 Unix Domain Socket 通信。
- CSI Driver 容器与 kubelet 通过另一个 Unix Domain Socket 通信,并挂载 kubelet 的工作目录(默认为 /var/lib/kubelet),用于管理 Pod 的 Volume。
部署建议
CSI Node 以 DaemonSet 的形式运行,确保每个节点上都有 CSI 插件实例。
四、CSI 接口介绍
三方存储厂商需实现 CSI 插件的三大接口:IdentityServer、ControllerServer、NodeServer。
- IdentityServer
IdentityServer 主要用于认证 CSI 插件的身份信息。
// IdentityServer is the server API for Identity service.
type IdentityServer interface {
// 获取CSI插件的信息,比如名称、版本号
GetPluginInfo(context.Context, *GetPluginInfoRequest) (*GetPluginInfoResponse, error)
// 获取CSI插件提供的能力,比如是否提供ControllerService能力
GetPluginCapabilities(context.Context, *GetPluginCapabilitiesRequest) (*GetPluginCapabilitiesResponse, error)
// 获取CSI插件健康状况
Probe(context.Context, *ProbeRequest) (*ProbeResponse, error)
}
- ControllerServer
ControllerServer 主要负责存储卷及快照的创建/删除以及挂接/摘除操作。
// ControllerServer is the server API for Controller service.
type ControllerServer interface {
// 创建存储卷
CreateVolume(context.Context, *CreateVolumeRequest) (*CreateVolumeResponse, error)
// 删除存储卷
DeleteVolume(context.Context, *DeleteVolumeRequest) (*DeleteVolumeResponse, error)
// 挂接存储卷到特定节点
ControllerPublishVolume(context.Context, *ControllerPublishVolumeRequest) (*ControllerPublishVolumeResponse, error)
// 从特定节点摘除存储卷
ControllerUnpublishVolume(context.Context, *ControllerUnpublishVolumeRequest) (*ControllerUnpublishVolumeResponse, error)
// 验证存储卷能力是否满足要求,比如是否支持跨节点多读多写
ValidateVolumeCapabilities(context.Context, *ValidateVolumeCapabilitiesRequest) (*ValidateVolumeCapabilitiesResponse, error)
// 列举全部存储卷信息
ListVolumes(context.Context, *ListVolumesRequest) (*ListVolumesResponse, error)
// 获取存储资源池可用空间大小
GetCapacity(context.Context, *GetCapacityRequest) (*GetCapacityResponse, error)
// 获取ControllerServer支持功能点,比如是否支持快照能力
ControllerGetCapabilities(context.Context, *ControllerGetCapabilitiesRequest) (*ControllerGetCapabilitiesResponse, error)
// 创建快照
CreateSnapshot(context.Context, *CreateSnapshotRequest) (*CreateSnapshotResponse, error)
// 删除快照
DeleteSnapshot(context.Context, *DeleteSnapshotRequest) (*DeleteSnapshotResponse, error)
// 获取所有快照信息
ListSnapshots(context.Context, *ListSnapshotsRequest) (*ListSnapshotsResponse, error)
// 扩容存储卷
ControllerExpandVolume(context.Context, *ControllerExpandVolumeRequest) (*ControllerExpandVolumeResponse, error)
}
- NodeServer
NodeServer 主要负责存储卷挂载/卸载操作。
// NodeServer is the server API for Node service.
type NodeServer interface {
// 将存储卷格式化并挂载至临时全局目录
NodeStageVolume(context.Context, *NodeStageVolumeRequest) (*NodeStageVolumeResponse, error)
// 将存储卷从临时全局目录卸载
NodeUnstageVolume(context.Context, *NodeUnstageVolumeRequest) (*NodeUnstageVolumeResponse, error)
// 将存储卷从临时目录bind-mount到目标目录
NodePublishVolume(context.Context, *NodePublishVolumeRequest) (*NodePublishVolumeResponse, error)
// 将存储卷从目标目录卸载
NodeUnpublishVolume(context.Context, *NodeUnpublishVolumeRequest) (*NodeUnpublishVolumeResponse, error)
// 获取存储卷的容量信息
NodeGetVolumeStats(context.Context, *NodeGetVolumeStatsRequest) (*NodeGetVolumeStatsResponse, error)
// 存储卷扩容
NodeExpandVolume(context.Context, *NodeExpandVolumeRequest) (*NodeExpandVolumeResponse, error)
// 获取NodeServer支持功能点,比如是否支持获取存储卷容量信息
NodeGetCapabilities(context.Context, *NodeGetCapabilitiesRequest) (*NodeGetCapabilitiesResponse, error)
// 获取CSI节点信息,比如最大支持卷个数
NodeGetInfo(context.Context, *NodeGetInfoRequest) (*NodeGetInfoResponse, error)
}
五、CSI Sidecar 组件介绍
为使 K8s 适配 CSI 标准,社区将与 K8s 相关的存储流程逻辑放在了 CSI Sidecar 组件中。
1. Node Driver Registrar
功能
Node-Driver-Registrar 组件会将外部 CSI 插件注册到 Kubelet,从而使 Kubelet 通过特定的 Unix Domain Socket 来调用外部 CSI 插件函数(Kubelet 会调用外部 CSI 插件的 NodeGetInfo、NodeStageVolume、NodePublishVolume、NodeGetVolumeStats 等函数)。
原理
Node-Driver-Registrar 组件通过 Kubelet 外部插件注册机制实现注册,注册成功后:
- Kubelet 为本节点 Node 资源打 annotation:调用外部 CSI 插件的 NodeGetInfo 函数,其返回值
[nodeID]
、[driverName]
将作为值用于"csi.volume.kubernetes.io/nodeid"
键。 - Kubelet 更新 Node Label:将 NodeGetInfo 函数返回的
[AccessibleTopology]
值用于节点的 Label。 - Kubelet 更新 Node Status:将 NodeGetInfo 函数返回的
maxAttachLimit
(节点最大可挂载卷数量)更新到 Node 资源的Status.Allocatable: attachable-volumes-csi-[driverName]=[maxAttachLimit]
。 - Kubelet 更新 CSINode 资源(没有则创建):将
[driverName]
、[nodeID]
、[maxAttachLimit]
、[AccessibleTopology]
更新到 Spec 中(拓扑仅保留 Key 值)。
2. External Provisioner
功能
创建/删除实际的存储卷,以及代表存储卷的 PV 资源。
原理
External-Provisioner 在启动时需指定参数 --provisioner
,该参数指定 Provisioner 名称,与 StorageClass 中的 provisioner
字段对应。
External-Provisioner 启动后会 watch 集群中的 PVC 和 PV 资源。
对于集群中的 PVC 资源:
- 判断 PVC 是否需要动态创建存储卷,标准如下:
- PVC 的 annotation 中是否包含
"volume.beta.kubernetes.io/storage-provisioner"
键(由卷控制器创建),并且其值是否与 Provisioner 名称相等。 - PVC 对应 StorageClass 的
VolumeBindingMode
字段若为WaitForFirstConsumer
,则 PVC 的 annotation 中必须包含"volume.kubernetes.io/selected-node"
键(详见调度器如何处理WaitForFirstConsumer
),且其值不为空;若为Immediate
则表示需要 Provisioner 立即提供动态存储卷。
- PVC 的 annotation 中是否包含
- 通过特定的 Unix Domain Socket 调用外部 CSI 插件的
CreateVolume
函数。 - 创建 PV 资源,PV 名称为
[Provisioner 指定的 PV 前缀]-[PVC uuid]
。
对于集群中的 PV 资源:
- 判断 PV 是否需要删除,标准如下:
- 判断其
.Status.Phase
是否为Release
。 - 判断其
.Spec.PersistentVolumeReclaimPolicy
是否为Delete
。 - 判断其是否包含 annotation
pv.kubernetes.io/provisioned-by
,且其值是否为自己。
- 判断其
- 通过特定的 Unix Domain Socket 调用外部 CSI 插件的
DeleteVolume
接口。 - 删除集群中的 PV 资源。
3. External Attacher
功能
挂接/摘除存储卷。
原理
External-Attacher 内部会时刻 watch 集群中的 VolumeAttachment 资源和 PersistentVolume 资源。
对于 VolumeAttachment 资源:
- 从 VolumeAttachment 资源中获得 PV 的所有信息,如 volume ID、node ID、挂载 Secret 等。
- 判断 VolumeAttachment 的
DeletionTimestamp
字段是否为空来判断其为卷挂接或卷摘除:- 若为卷挂接,则通过特定的 Unix Domain Socket 调用外部 CSI 插件的
ControllerPublishVolume
接口。 - 若为卷摘除,则通过特定的 Unix Domain Socket 调用外部 CSI 插件的
ControllerUnpublishVolume
接口。
- 若为卷挂接,则通过特定的 Unix Domain Socket 调用外部 CSI 插件的
对于 PersistentVolume 资源:
- 在挂接时为相关 PV 打上 Finalizer:
external-attacher/[driver 名称]
。 - 当 PV 处于删除状态时(
DeletionTimestamp
非空),删除 Finalizer:external-attacher/[driver 名称]
。
4. External Resizer
功能
扩容存储卷。
原理
External-Resizer 内部会 watch 集群中的 PersistentVolumeClaim 资源。
对于 PersistentVolumeClaim 资源:
- 判断 PVC 是否需要扩容:PVC 状态需要是
Bound
且.Status.Capacity
与.Spec.Resources.Requests
不等。 - 更新 PVC 的
.Status.Conditions
,表明此时处于Resizing
状态。 - 通过特定的 Unix Domain Socket 调用外部 CSI 插件的
ControllerExpandVolume
接口。 - 更新 PV 的
.Spec.Capacity
。 - 若 CSI 支持文件系统在线扩容:
ControllerExpandVolume
接口返回值中NodeExpansionRequired
字段为true
,External-Resizer 更新 PVC 的.Status.Conditions
为FileSystemResizePending
状态。- 若不支持,则扩容成功,External-Resizer 更新 PVC 的
.Status.Conditions
为空,且更新 PVC 的.Status.Capacity
。
- Volume Manager(Kubelet 组件)观察到存储卷需在线扩容,于是通过特定的 Unix Domain Socket 调用外部 CSI 插件的
NodeExpandVolume
接口实现文件系统扩容。
5. Livenessprobe
功能
检查 CSI 插件是否正常。
原理
通过对外暴露一个 /healthz
HTTP 端口以服务 kubelet 的探针探测器,内部是通过特定的 Unix Domain Socket 调用外部 CSI 插件的 Probe
接口。
六、CSI 与 Kubernetes 组件的通信方式
CSI Controller 与 Kubernetes 的通信
- 组件:external-provisioner、external-attacher 等 Sidecar 容器。
- 通信方式:
- 通过 Kubernetes API 监听资源对象的变化(如 PersistentVolumeClaim、VolumeAttachment 等)。
- 通过 Unix Domain Socket 调用 CSI 驱动的接口。
CSI Node 与 Kubelet 的通信
- 组件:node-driver-registrar Sidecar 容器。
- 通信方式:通过 Unix Domain Socket 与 kubelet 通信。CSI Driver 容器通过 Unix Domain Socket 接收 kubelet 的调用,完成存储卷的挂载和卸载。
CSI 驱动与 Sidecar 的通信
- 通信方式:通过 Unix Domain Socket 使用 gRPC 协议进行通信。Sidecar 容器通过 gRPC 调用 CSI 驱动的接口,完成具体的存储操作。
七、CSI 工作原理
CSI 的工作流程围绕 Kubernetes 的存储资源(如 PersistentVolume、PersistentVolumeClaim)展开,主要包含以下步骤:
- 存储资源的创建:用户创建 PersistentVolumeClaim(PVC),指定存储需求(如大小、访问模式等)。external-provisioner 监听到 PVC 的变化,调用 CSI 驱动的 CreateVolume 接口,创建存储卷。
- 存储卷的挂载:external-attacher 监听到 VolumeAttachment 的变化,调用 CSI 驱动的 ControllerPublish 接口,将存储卷与目标节点绑定。kubelet 通过 node-driver-registrar 调用 CSI 驱动的 NodePublishVolume 接口,将存储卷挂载到容器的指定路径。
- 存储卷的卸载:用户删除 PersistentVolumeClaim 或 VolumeAttachment。external-attacher 调用 CSI 驱动的 ControllerUnpublish 接口,解除存储卷与节点的绑定。kubelet 调用 CSI 驱动的 NodeUnpublishVolume 接口,将存储卷从容器中卸载。
- 存储卷的删除:external-provisioner 监听到 PVC 的删除操作,调用 CSI 驱动的 DeleteVolume 接口,删除存储卷。
八、CSI 与 FlexVolume 的对比
CSI 是 FlexVolume 的升级版,主要区别如下:
- 接口标准化:CSI 提供了更标准化的接口,支持更复杂的存储操作(如快照、扩容等)。
- 组件分离:CSI 将功能分离为 Controller 和 Node 两个部分,支持更灵活的部署和扩展。
- 性能优化:CSI 使用 gRPC 协议,通信效率更高。
九、总结
CSI 是 Kubernetes 中存储管理的重要组成部分,通过标准化的接口和灵活的组件设计,为用户提供了强大的存储管理能力。掌握 CSI 的工作原理和组件构成,能够帮助用户更好地管理和优化 Kubernetes 集群中的存储资源。