和K8S的网络不太一样,K8S的网络只有CNI一种接口暴露方式,所有的网络实现基于第三方进行开发实现,但存储的内置实现就多达20多种,#K8S目前支持的插件类型#。但内置的往往不满足定制化的需求,所以和CNI 一样,K8S 也暴露了对外的存储接口,和CNI 一样,通过实现对应的接口方法,即可创建属于自己的存储插件,但和CNI 有点区别的是,K8S的存储插件的自定义实现方式,有FlexVolume 和 CSI 两种,两者的差别可以看做是新老功能的差异,但目前为止,FlexVolume 同样也有用武之地。
熟悉CNI的编写方法的,对FlexVolume的编写一定不陌生,CNI编写完成后,是会拆分为2个二进制文件(CNI,IPAM)和一个配置文件,放在每个Node节点上,kubelet在创建Pod 时候,会调用对应的二进制文件进行网络的创建,同样也可以使用daemonset的方式容器化部署,FlexVolume 也一样,编写完成后一样以二进制的方式进行部署,和CNI 一样,FlexVolume需要实现类似CNI的cmdadd,cmddel的方法,具体需要实现以下几个方法:
基本返回格式:
{
"status": "" ,
"message": "" ,
"device": ""
"volumeName": ""
"attached": -out)>
"capabilities": >
{
"attach": >
}
}
那么kublet和他的调用关系是啥?看一下kubelet调用的FlexVolume的一段代码(pod mount dir):
// SetUpAt creates new directory.
func (f *flexVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
...
call := f.plugin.NewDriverCall(mountCmd)
// Interface parameters
call.Append(dir)
extraOptions := make(map[string]string)
// pod metadata
extraOptions[optionKeyPodName] = f.podName
extraOptions[optionKeyPodNamespace] = f.podNamespace
...
call.AppendSpec(f.spec, f.plugin.host, extraOptions)
_, err = call.Run()
...
return nil
}
再看下一个PV的yml的栗子对比一下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-flex-nfs
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
flexVolume:
driver: "k8s/nfs"
fsType: "nfs"
options:
server: "1.1.1.1"
share: "export"
先只看FlexVolume, dirver这里的k8s/nfs 就是FlexVolume的具体位置,注意k8s~nfs解析出来的就是k8s/nfs, FlexVolume的默认位置在/usr/libexec/kubernetes/kubelet-plugins/volume/exec/
,所以上述yml用的FlexVolume插件具体位置在/usr/libexec/kubernetes/kubelet-plugins/volume/exec/k8s~nfs/nfs
。
pv的yml 里面有对应的options, 就是kubelet代码里的extraOptions := make(map[string]string)
,是一个map类型,kubelet解析yml里面的option参数,传入该map变量,然后执行FlexVolume的具体方法,比如栗子里的mount,将options的参数传入,实现了如下的效果:
/usr/libexec/kubernetes/kubelet-plugins/volume/exec/k8s~nfs/nfs mount <mount dir> <json param>
git 上fork的几个demo,方便查询。
可以看到FlexVolume实现逻辑非常简单,但解决的问题也非常有限,用户还是需要手动创建PV,且每次调用插件执行的都是偏原子操作的,即单次只执行attach,mount,umount等动作,所以,其作用,只是一个针对不同存储自定义创建PV的插件,假设我需要动态生成PV 并动态绑定PVC,FlexVolume就不具备可操作性,所以CSI来了。
先说下CSI和FlexVolume的基本差异,FlexVolume实现了Attach(挂载存储到Node)和Mount(Node的目录挂载到Pod), 缺少了PV的动态生成(需要运维手动在存储上配置然后再创建手动创建PV),而CSI就是在FlexVolume的基础上,实现了PV的动态生成。
CSI的调用和FlexVolume不一样,在调用的时候,需要有一个注册的过程,找个CSI的代码看下:
简单先描述一下调用顺序:
上面只是描述了CSI插件的调用顺序,那么问题来了,每一步,分别是谁去调用的呢?
回过头先看下CSI的架构图:
发现CSI的架构实际分了3块,第一块K8S-Core,即K8S的核心组件,第二块Kubernetes External Component, 这是Kubernetes支持CSI的扩展组件,第三块External Component:传统意义上的CSI,即上面的那个demo代码。从官网架构图的箭头可以看到整体的调用关系:
第二块External Component的下载地址
从上面的调用逻辑可以看出,出了CSI本体(这里暂称为CSI Driver),还需要部署External Component里的三个container,且这3个container里只有driver-registrar需要和kubelet调用,所以在实际部署中,需要以daemonset的方式,将driver-registrar和CSI Driver作为side-car模式的部署方式进行部署,其他2个External provisioner和 External attacher以statefulset的方式,和CSI Driver一起作为side-car模式的部署方式进行部署。
简单画个部署图: