容器存储接口CSI是独立于Kubernetes的,作为一个独立project,在Kubernetes v1.9版本的时候引入。CSI为Kubernetes提供了一个插件系统,像云厂商的AWS EBS, Azure File这些都是作为CSI的一个插件被编进去Kubernetes中。
CSI是作为一个标准开发的,用于将任意块和文件存储存储系统暴露给容器编排系统(如Kubernetes)上的容器化工作负载。通过采用容器存储接口,Kubernetes卷层变得真正可扩展。使用CSI,第三方存储提供商可以编写和部署Kubernetes中公开新存储系统的插件,而无需接触Kubernetes的核心代码。这为Kubernetes用户提供了更多的存储选项,并使系统更加安全和可靠。
代码库:https://github.com/container-storage-interface/spec.git
代码位置: https://github.com/container-storage-interface/spec/blob/master/csi.proto
跟CRI类似,CSI也是定义了gRPC接口,云厂商的存储插件需要实现以下接口。
该接口主要是用来获取插件信息,查询插件状态
service Identity {
// 返回插件信息
rpc GetPluginInfo(GetPluginInfoRequest)
returns (GetPluginInfoResponse) {}
// 返回插件提供的能力
rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
returns (GetPluginCapabilitiesResponse) {}
// 健康探针
rpc Probe (ProbeRequest)
returns (ProbeResponse) {}
}
从存储服务端来进行操作卷,包括创建卷,删除卷等
service Controller {
// 创建卷
rpc CreateVolume (CreateVolumeRequest)
returns (CreateVolumeResponse) {}
// 删除卷
rpc DeleteVolume (DeleteVolumeRequest)
returns (DeleteVolumeResponse) {}
// attach 卷
rpc ControllerPublishVolume (ControllerPublishVolumeRequest)
returns (ControllerPublishVolumeResponse) {}
// deattach 卷
rpc ControllerUnpublishVolume (ControllerUnpublishVolumeRequest)
returns (ControllerUnpublishVolumeResponse) {}
...
}
对Node节点上的卷进行操作
service Node {
// 格式化卷并且挂载到一个临时目录
rpc NodeStageVolume (NodeStageVolumeRequest)
returns (NodeStageVolumeResponse) {}
// 把卷从临时目录卸载
rpc NodeUnstageVolume (NodeUnstageVolumeRequest)
returns (NodeUnstageVolumeResponse) {}
// 把卷从临时目录挂载到目标目录
rpc NodePublishVolume (NodePublishVolumeRequest)
returns (NodePublishVolumeResponse) {}
// 把卷从目标目录卸载
rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
returns (NodeUnpublishVolumeResponse) {}
...
}
这篇文章由于很多地方涉及到卷的操作:Attach, Detach, Mount和Unmount,一般来说,卷都需要Attach--> Mount-->Unmount--> Detach这四个操作,只有EmptyDir跟HostPath这两种才不需要Attach和Detach操作。
当用户创建卷的时候,首先调用CreateVolume接口,去创建卷,然后调用ControllerPublishVolume将卷attch到主机上,接下来会调用NodeStageVolume来进行格式化最后调用NodeStageVolume来mount到指定目录下。
CreateVolume-->ControllerPublishVolume-->NodeStageVolume-->NodePublishVolume
Kubernetes CSI Sidecar容器是一组标准容器,旨在简化在Kubernetes上CSI驱动程序的开发和部署。
这些容器包含公共逻辑,用于监视Kubernetes API,触发针对“CSI卷驱动程序”容器的适当操作,并适当地更新Kubernetes API。
这些容器打算与第三方CSI驱动程序容器捆绑在一起,并作为pod部署在一起。
一般来说,CSI驱动程序应该和以下sidecar (helper)容器一起部署在Kubernetes上:
监视由controller-manager创建的VolumeAttachment对象,并将卷附加/卸载到/从节点上(即调用ControllerPublish/ controllererunpublish)。
(ControllerPublish是让一个node节点不需要运行任何代码就可以attach卷)
(Detach就是反向操作,从一个node节点中detach卷)
代码库: https://github.com/kubernetes-csi/external-attacher.git
func NewCSIAttachController(client kubernetes.Interface, attacherName string, handler Handler, volumeAttachmentInformer storageinformers.VolumeAttachmentInformer, pvInformer coreinformers.PersistentVolumeInformer, vaRateLimiter, paRateLimiter workqueue.RateLimiter, shouldReconcileVolumeAttachment bool, reconcileSync time.Duration) *CSIAttachController {
...
ctrl := &CSIAttachController{
client: client,
attacherName: attacherName,
handler: handler,
eventRecorder: eventRecorder,
vaQueue: workqueue.NewNamedRateLimitingQueue(vaRateLimiter, "csi-attacher-va"),
pvQueue: workqueue.NewNamedRateLimitingQueue(paRateLimiter, "csi-attacher-pv"),
shouldReconcileVolumeAttachment: shouldReconcileVolumeAttachment,
reconcileSync: reconcileSync,
}
// 监听volumeAttachment的增删改,触发写入队列
volumeAttachmentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ctrl.vaAdded,
UpdateFunc: ctrl.vaUpdated,
DeleteFunc: ctrl.vaDeleted,
})
...
pvInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ctrl.pvAdded,
UpdateFunc: ctrl.pvUpdated,
//DeleteFunc: ctrl.pvDeleted, TODO: do we need this?
})
...
return ctrl
}
然后执行ControllerPublishVolume
func (a *attacher) Attach(ctx context.Context, volumeID string, readOnly bool, nodeID string, caps *csi.VolumeCapability, context, secrets map[string]string) (metadata map[string]string, detached bool, err error) {
client := csi.NewControllerClient(a.conn)
req := csi.ControllerPublishVolumeRequest{
VolumeId: volumeID,
NodeId: nodeID,
VolumeCapability: caps,
Readonly: readOnly,
VolumeContext: context,
Secrets: secrets,
}
rsp, err := client.ControllerPublishVolume(ctx, &req)
...
return rsp.PublishContext, false, nil
}
监视Kubernetes PersistentVolumeClaim对象,并针对CSI端点触CreateVolume和DeleteVolume操作。
例如当我们创建一个PersistentVolumeClaim
对象的时候,如果PVC指定了一个特定的StorageClass
,并且存储类的provisioner字段中的名称与GetPluginInfo调用中指定的CSI端点返回的名称相匹配,则通过创建新的Kubernetes PersistentVolumeClaim对象触发卷供应。
成功地配给新卷之后,sidecar容器创建一个Kubernetes PersistentVolume对象来表示卷。
使用删除回收策略删除绑定到与此驱动程序对应的PersistentVolumeClaim
对象,将导致sidecar容器对指定的CSI端点触发DeleteVolume操作以删除卷。成功删除卷之后,sidecar容器还删除表示卷的PersistentVolume对象。
代码库:https://github.com/kubernetes-csi/external-provisioner.git
使用kubelet设备插件机制向kubelet注册CSI驱动程序。
通过创建CSIDriver对象向Kubernetes集群注册CSI驱动程序,该对象使驱动程序能够自定义Kubernetes与它的交互方式。
监视Kubernetes VolumeSnapshot CRD对象,并针对CSI端点触发CreateSnapshot和DeleteSnapshot操作。
可能包含在CSI插件pod中,以启用Kubernetes的活性探测机制。
CSI驱动程序通常作为两个组件部署在Kubernetes中: 一个controller plugin和per-node plugin。
controller组件可以作为Deployment或StatefulSet部署在集群中的任何节点上。它由实现CSI控制器服务的CSI驱动程序和一个或多个sidecar容器组成。这些控制器sidecar容器通常与Kubernetes对象交互,并调用驱动程序的CSI控制器服务。
它通常不需要直接访问主机,可以通过Kubernetes API和外部控制平面服务执行所有操作。可以为HA部署controller组件的多个副本,但是建议使用leader选举,以确保一次只有一个活动控制器。
controller sidecar包括external-provisioner, external-attacher, external-snapshotter, and external-resizer。在部署中包括一个sidecar可能是可选的
Node插件需要以DaemonSet来给每一个node节点部署。
如下图所示,Kubernetes kubelet运行在每个节点上,负责进行CSI节点服务调用。这些调用从存储系统挂载和卸载存储卷,使Pod可以使用它。Kubelet通过主机上通过HostPath卷共享的UNIX socket调用CSI驱动程序。还有第二个UNIX socket, node-driver-registrar 使用它将CSI驱动程序注册到kubelet。
这里建议读者阅读kubelet/pluginmanager.md理解Kubelet是如何发现并且注册CSI插件。
使用CSI sanity的包可以用来测试CSI驱动。
https://github.com/kubernetes-csi/csi-test/tree/master/pkg/sanity
这里说的功能测试,是在功能上进行“端到端”测试。