CSI

2024/11/04

Tags: k8s csi

关于 CSI

CSI 全称为 Container Storage Interface, 容器存储接口

要实现一个第三方的 csi driver 需要实现下面的 gRPC service csi spec

// 如果 NodeServer 和 ControllerServer 对应服务运行在不同 pod 中, 那么两个服务都要实现 IdentityServer
type IdentityServer interface {
    // 用来获取插件名称
    GetPluginInfo(context.Context, *GetPluginInfoRequest) (*GetPluginInfoResponse, error)
    GetPluginCapabilities(context.Context, *GetPluginCapabilitiesRequest) (*GetPluginCapabilitiesResponse, error)
    Probe(context.Context, *ProbeRequest) (*ProbeResponse, error)
    mustEmbedUnimplementedIdentityServer()
}

type ControllerServer interface {
    // 创建 volume, 如 ceph 创建一个 rbd 或者 hostpath 创建一个目录
    CreateVolume(context.Context, *CreateVolumeRequest) (*CreateVolumeResponse, error)
    // 删除 volume, 如 ceph 删除一个 rbd 或者 hostpath 删除一个目录
    DeleteVolume(context.Context, *DeleteVolumeRequest) (*DeleteVolumeResponse, error)
    // 将 volume attach 到 node 上, 如 rbd 通过 rbd map 命令 attach, 成功后 node 上会多出一个 rbdx 的 block 设备
    ControllerPublishVolume(context.Context, *ControllerPublishVolumeRequest) (*ControllerPublishVolumeResponse, error)
    // 将 volume 从 node 上 detach, 如 rbd 通过 rbd unmap 命令 detach
    ControllerUnpublishVolume(context.Context, *ControllerUnpublishVolumeRequest) (*ControllerUnpublishVolumeResponse, error)
    ValidateVolumeCapabilities(context.Context, *ValidateVolumeCapabilitiesRequest) (*ValidateVolumeCapabilitiesResponse, error)
    // 列出所有 volume
    ListVolumes(context.Context, *ListVolumesRequest) (*ListVolumesResponse, error)
    GetCapacity(context.Context, *GetCapacityRequest) (*GetCapacityResponse, error)
    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)
    ControllerGetVolume(context.Context, *ControllerGetVolumeRequest) (*ControllerGetVolumeResponse, error)
    ControllerModifyVolume(context.Context, *ControllerModifyVolumeRequest) (*ControllerModifyVolumeResponse, error)
    mustEmbedUnimplementedControllerServer()
}

// 这些会被 kubelet 调用
type NodeServer interface {
    // format (如果没format), mount 到 node 的 global directory
    NodeStageVolume(context.Context, *NodeStageVolumeRequest) (*NodeStageVolumeResponse, error)
    // umount
    NodeUnstageVolume(context.Context, *NodeUnstageVolumeRequest) (*NodeUnstageVolumeResponse, error)
    // mount --bind 到 pod directory
    NodePublishVolume(context.Context, *NodePublishVolumeRequest) (*NodePublishVolumeResponse, error)
    // umount --bind
    NodeUnpublishVolume(context.Context, *NodeUnpublishVolumeRequest) (*NodeUnpublishVolumeResponse, error)
    NodeGetVolumeStats(context.Context, *NodeGetVolumeStatsRequest) (*NodeGetVolumeStatsResponse, error)
    NodeExpandVolume(context.Context, *NodeExpandVolumeRequest) (*NodeExpandVolumeResponse, error)
    NodeGetCapabilities(context.Context, *NodeGetCapabilitiesRequest) (*NodeGetCapabilitiesResponse, error)
    NodeGetInfo(context.Context, *NodeGetInfoRequest) (*NodeGetInfoResponse, error)
    mustEmbedUnimplementedNodeServer()
}

关于 Sidecar Containers

Sidecar Containers 是一系列标准容器,用于简化 CSI 插件的开发和部署

它们都有共同的逻辑,watch k8s API,调用第三方 csi driver 执行操作,最后对应的更新 k8s API

这些容器一般作为sidecar和第三方 csi driver 一起部署在同一个 pod 中, 通过 unix socket 通信

容器仓库文档
node-driver-registrarkubernetes-csi/node-driver-registrarlink
external-provisionerkubernetes-csi/external-provisionerlink
external-attacherkubernetes-csi/external-attacherlink
external-snapshotterkubernetes-csi/external-snapshotterlink
external-resizerkubernetes-csi/external-resizerlink
livenessprobekubernetes-csi/livenessprobelink

node-driver-registrar

从 CSI endpoint 拉取 driver 信息(使用 NodeGetInfo), 然后通过 kubelet plugin registration mechanism 注册到对应节点的 kubelet 中

表现形式为

/var/lib/kubelet/plugins/csi-hostpath/csi.sock
/var/lib/kubelet/plugins_registry/kubevirt.io.hostpath-provisioner-reg.sock

external-provisioner

watch PersistentVolumeClaim 对象, 如果一个 pvc 引用了一个 StorageClass 并且 StorageClassprovisioner 字段 和从 CSI endpoint 调用 GetPluginInfo 获取到的一致,则执行下面逻辑

当 pvc 对应的 sc 的 volumeBindingMode 为 WaitForFirstConsumer 时, 只有使用此 pvc 的 pod 被调度之后才会去创建 pv, kube-schedule 调度 pod 后会在 pvc 上增加一个注解 volume.kubernetes.io/selected-node={scheduleResult.SuggestedHost} , 通过 pvc 是否包含此注解并不为空来判断是否 provision, 如果 volumeBindingMode 为 Immediate 则表示不用等待 pod 调度立即 provision

external-attacher

watch VolumeAttachment 对象, 如果 attacher 字段和从 CSI endpoint 调用 GetPluginInfo 获取到的一致, 则触发调用 CSI endpoint 执行 Controller[Publish|Unpublish]Volume

一般块存储才会需要 attach/detach 操作, 比如 ceph 的 rbd

VolumeAttachment 对象是由 ADController(AttachDetach Controller) 创建, ADController 会不断的检查每一个 pod 对应的 pv 和这个 pod 所调度到的宿主机之间的挂载情况(node.status.volumesAttached), 针对没有挂载的 pv 创建的 VolumeAttachment 中存储以下三个信息

external-snapshotter

TODO

external-resizer

TODO

livenessprobe

TODO

csi demo

kubernetes-csi/csi-driver-host-path

kubevirt/hostpath-provisioner 是官方提供的 demo

kubevirt/hostpath-provisioner

kubernetes-csi/csi-driver-host-path 是 kubevirt 基于 kubernetes-csi/csi-driver-host-path 开发的, 改动不多, 适合学习和使用

csi 测试工具 csc

csc 是 Container Storage Client

identity

identity service 相关的

GetPluginInfo

$ csc -e /var/lib/kubelet/plugins/csi-hostpath/csi.sock identity plugin-info
"kubevirt.io.hostpath-provisioner"	"latest"

node

node service 相关的

NodeGetInfo

node-driver-registrarkubelet 注册 CSI plugin 时会调用

$ csc -e /var/lib/kubelet/plugins/csi-hostpath/csi.sock node get-info
test	0	&csi.Topology{Segments:map[string]string{"topology.hostpath.csi/node":"test"}, XXX_NoUnkeyedLiteral:struct {}{}, XXX_unrecognized:[]uint8(nil), XXX_sizecache:0}

controller

controller service 相关的

CreateVolume

external-provisioner 监听到有 pvc 创建时会调用

$ csc -e /var/lib/kubelet/plugins/csi-hostpath/csi.sock controller create-volume --params storagePool=local --cap MULTI_NODE_MULTI_WRITER,mount,xfs,uid=500,gid=500 pvc-466a771a-a8c7-473e-bca6-780f7663a6cd
"pvc-466a771a-a8c7-473e-bca6-780f7663a6cd"	105226698752	"storagePool"="local"

ListVolumes

可以看到刚才创建的

$ csc -e /var/lib/kubelet/plugins/csi-hostpath/csi.sock controller list-volumes
"pvc-466a771a-a8c7-473e-bca6-780f7663a6cd"	105226698752

NodePublishVolume

kubelet 针对 好像不会调用这个 ?

$ csc -e /var/lib/kubelet/plugins/csi-hostpath/csi.sock node publish xxx

DeleteVolume

external-provisioner 监听到有 pvc 被删除时会调用

$ csc -e /var/lib/kubelet/plugins/csi-hostpath/csi.sock controller delete-volume pvc-466a771a-a8c7-473e-bca6-780f7663a6cd
pvc-466a771a-a8c7-473e-bca6-780f7663a6cd

参考