K8S CSI 容器存储接口 (二):如何编写一个CSI插件

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里以","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/kubernetes-csi/csi-driver-host-path","title":""},"content":[{"type":"text","text":"csi-driver-host-path","attrs":{}}]},{"type":"text","text":"作为例子,来看看是如何实现一个csi插件的?","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"目标:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持PV动态创建,并且能够挂载在POD中","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"volume来自本地目录,主要是模拟volume产生的过程,这样就不依赖于某个特定的存储服务","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"预备知识","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在","attrs":{}},{"type":"link","attrs":{"href":"https://silenceper.com/kubernetes-book/csi/","title":""},"content":[{"type":"text","text":"上一篇文章","attrs":{}}]},{"type":"text","text":"中,已经对CSI概念有个了解,并且提出了CSI组件需要实现的RPC接口,那我们为什么需要这些接口,这需要从volume要被使用经过了以下流程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"volume创建","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"volume ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"attach","attrs":{}}],"attrs":{}},{"type":"text","text":"到节点(比如像EBS硬盘,NFS可能就直接下一步mount了)","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"volume 被","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mount","attrs":{}}],"attrs":{}},{"type":"text","text":"到指定目录(这个目录其实就被映射到容器中,由kubelet 中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"VolumeManager","attrs":{}}],"attrs":{}},{"type":"text","text":" 调用)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而当卸载时正好是相反的:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"unmount","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"detach","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"delete volume","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正好对应如下图:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":" CreateVolume +------------+ DeleteVolume\n +------------->| CREATED +--------------+\n | +---+----^---+ |\n | Controller | | Controller v\n+++ Publish | | Unpublish +++\n|X| Volume | | Volume | |\n+-+ +---v----+---+ +-+\n | NODE_READY |\n +---+----^---+\n Node | | Node\n Stage | | Unstage\n Volume | | Volume\n +---v----+---+\n | VOL_READY |\n +---+----^---+\n Node | | Node\n Publish | | Unpublish\n Volume | | Volume\n +---v----+---+\n | PUBLISHED |\n +------------+","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而为什么多个","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NodeStageVolume","attrs":{}}],"attrs":{}},{"type":"text","text":"的过程是因为:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于块存储来说,设备只能mount到一个目录上,所以","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NodeStageVolume","attrs":{}}],"attrs":{}},{"type":"text","text":"就是先mount到一个globalmount目录(类似:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"/var/lib/kubelet/plugins/kubernetes.io/csi/pv/pvc-bcfe33ed-e822-4b0e-954a-0f5c0468525e/globalmount","attrs":{}}],"attrs":{}},{"type":"text","text":"),然后再","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NodePublishVolume","attrs":{}}],"attrs":{}},{"type":"text","text":"这一步中通过","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mount bind","attrs":{}}],"attrs":{}},{"type":"text","text":"到pod的目录(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"/var/lib/kubelet/pods/9c5aa371-e5a7-4b67-8795-ec7013811363/volumes/kubernetes.io~csi/pvc-bcfe33ed-e822-4b0e-954a-0f5c0468525e/mount/hello-world","attrs":{}}],"attrs":{}},{"type":"text","text":"),这样就可以实现一个pv挂载在多个pod中使用。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"代码实现","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们并不一定要实现所有的接口,这个可以通过CSI中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Capabilities","attrs":{}}],"attrs":{}},{"type":"text","text":"能力标识出来,我们组件提供的能力,比如","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"IdentityServer","attrs":{}}],"attrs":{}},{"type":"text","text":"中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"GetPluginCapabilities","attrs":{}}],"attrs":{}},{"type":"text","text":"方法","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"ControllerServer","attrs":{}}],"attrs":{}},{"type":"text","text":"中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ControllerGetCapabilities","attrs":{}}],"attrs":{}},{"type":"text","text":"方法","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"NodeServer","attrs":{}}],"attrs":{}},{"type":"text","text":"中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NodeGetCapabilities","attrs":{}}],"attrs":{}},{"type":"text","text":" ","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这些方法都是在告诉调用方,我们的组件实现了哪些能力,未实现的方法就不会调用了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"IdentityServer","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"IdentityServer","attrs":{}}],"attrs":{}},{"type":"text","text":"包含了三个接口,这里我们主要实现","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// IdentityServer is the server API for Identity service.\ntype IdentityServer interface {\n\tGetPluginInfo(context.Context, *GetPluginInfoRequest) (*GetPluginInfoResponse, error)\n\tGetPluginCapabilities(context.Context, *GetPluginCapabilitiesRequest) (*GetPluginCapabilitiesResponse, error)\n\tProbe(context.Context, *ProbeRequest) (*ProbeResponse, error)\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主要看下","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"GetPluginCapabilities","attrs":{}}],"attrs":{}},{"type":"text","text":"这个方法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/kubernetes-csi/csi-driver-host-path/blob/master/pkg/hostpath/identityserver.go#L60","title":""},"content":[{"type":"text","text":"identityserver.go#L60","attrs":{}}]},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (ids *identityServer) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) {\n\treturn &csi.GetPluginCapabilitiesResponse{\n\t\tCapabilities: []*csi.PluginCapability{\n\t\t\t{\n\t\t\t\tType: &csi.PluginCapability_Service_{\n\t\t\t\t\tService: &csi.PluginCapability_Service{\n\t\t\t\t\t\tType: csi.PluginCapability_Service_CONTROLLER_SERVICE,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: &csi.PluginCapability_Service_{\n\t\t\t\t\tService: &csi.PluginCapability_Service{\n\t\t\t\t\t\tType: csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, nil\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上就告诉调用者我们提供了ControllerService的能力,以及volume访问限制的能力(CSI 处理时需要根据集群拓扑作调整)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"PS:其实在k8s还提供了一个包:github.com/kubernetes-csi/drivers/pkg/csi-common,里面提供了比如","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"DefaultIdentityServer","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"DefaultControllerServer","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"DefaultNodeServer","attrs":{}}],"attrs":{}},{"type":"text","text":"的struct,只要在我们自己的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"XXXServer struct","attrs":{}}],"attrs":{}},{"type":"text","text":"中继承这些","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"struct","attrs":{}}],"attrs":{}},{"type":"text","text":",我们的代码中就只要包含自己实现的方法就行了,可以参考","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/kubernetes-sigs/alibaba-cloud-csi-driver/blob/master/pkg/disk/identityserver.go#L26","title":""},"content":[{"type":"text","text":"alibaba-cloud-csi-driver","attrs":{}}]},{"type":"text","text":"中的。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ControllerServer","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"ControllerServer","attrs":{}}],"attrs":{}},{"type":"text","text":"我们主要关注","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CreateVolume","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"DeleteVolume","attrs":{}}],"attrs":{}},{"type":"text","text":",因为是hostpath volume,所以就没有attach的这个过程了,我们放在NodeServer中实现:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"CreateVolume","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/kubernetes-csi/csi-driver-host-path/blob/b1cfe85dd7bfffce2bbe5b1228c994a9bc3649fb/pkg/hostpath/controllerserver.go#L73","title":""},"content":[{"type":"text","text":"controllerserver.go#L73","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {\n //校验参数是否有CreateVolume的能力\n\tif err := cs.validateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); err != nil {\n\t\tglog.V(3).Infof(\"invalid create volume req: %v\", req)\n\t\treturn nil, err\n\t}\n\n //.....这里省略的校验参数的过程\n\n\n //这里根据volume name判断是否已经存在了,存在了就返回就行了\n\tif exVol, err := getVolumeByName(req.GetName()); err == nil {\n\t\t// volume已经存在,但是大小不符合\n\t\tif exVol.VolSize < capacity {\n\t\t\treturn nil, status.Errorf(codes.AlreadyExists, \"Volume with the same name: %s but with different size already exist\", req.GetName())\n\t\t}\n //这里判断是否设置了pvc.dataSource,就表示是一个restore过程\n\t\tif req.GetVolumeContentSource() != nil {\n\t\t\tvolumeSource := req.VolumeContentSource\n\t\t\tswitch volumeSource.Type.(type) {\n //校验:从快照中恢复\n\t\t\tcase *csi.VolumeContentSource_Snapshot:\n\t\t\t\tif volumeSource.GetSnapshot() != nil && exVol.ParentSnapID != \"\" && exVol.ParentSnapID != volumeSource.GetSnapshot().GetSnapshotId() {\n\t\t\t\t\treturn nil, status.Error(codes.AlreadyExists, \"existing volume source snapshot id not matching\")\n\t\t\t\t}\n //校验:clone过程\n\t\t\tcase *csi.VolumeContentSource_Volume:\n\t\t\t\tif volumeSource.GetVolume() != nil && exVol.ParentVolID != volumeSource.GetVolume().GetVolumeId() {\n\t\t\t\t\treturn nil, status.Error(codes.AlreadyExists, \"existing volume source volume id not matching\")\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"%v not a proper volume source\", volumeSource)\n\t\t\t}\n\t\t}\n\t\t// TODO (sbezverk) Do I need to make sure that volume still exists?\n\t\treturn &csi.CreateVolumeResponse{\n\t\t\tVolume: &csi.Volume{\n\t\t\t\tVolumeId: exVol.VolID,\n\t\t\t\tCapacityBytes: int64(exVol.VolSize),\n\t\t\t\tVolumeContext: req.GetParameters(),\n\t\t\t\tContentSource: req.GetVolumeContentSource(),\n\t\t\t},\n\t\t}, nil\n\t}\n\n //创建volume\n\tvolumeID := uuid.NewUUID().String()\n //创建hostpath的volume\n\tvol, err := createHostpathVolume(volumeID, req.GetName(), capacity, requestedAccessType, false /* ephemeral */)\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.Internal, \"failed to create volume %v: %v\", volumeID, err)\n\t}\n\tglog.V(4).Infof(\"created volume %s at path %s\", vol.VolID, vol.VolPath)\n \n //判断是从快照恢复,还是clone\n\tif req.GetVolumeContentSource() != nil {\n\t\tpath := getVolumePath(volumeID)\n\t\tvolumeSource := req.VolumeContentSource\n\t\tswitch volumeSource.Type.(type) {\n //从快照恢复\n\t\tcase *csi.VolumeContentSource_Snapshot:\n\t\t\tif snapshot := volumeSource.GetSnapshot(); snapshot != nil {\n\t\t\t\terr = loadFromSnapshot(capacity, snapshot.GetSnapshotId(), path, requestedAccessType)\n\t\t\t\tvol.ParentSnapID = snapshot.GetSnapshotId()\n\t\t\t}\n //clone\n\t\tcase *csi.VolumeContentSource_Volume:\n\t\t\tif srcVolume := volumeSource.GetVolume(); srcVolume != nil {\n\t\t\t\terr = loadFromVolume(capacity, srcVolume.GetVolumeId(), path, requestedAccessType)\n\t\t\t\tvol.ParentVolID = srcVolume.GetVolumeId()\n\t\t\t}\n\t\tdefault:\n\t\t\terr = status.Errorf(codes.InvalidArgument, \"%v not a proper volume source\", volumeSource)\n\t\t}\n\t\tif err != nil {\n\t\t\tif delErr := deleteHostpathVolume(volumeID); delErr != nil {\n\t\t\t\tglog.V(2).Infof(\"deleting hostpath volume %v failed: %v\", volumeID, delErr)\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tglog.V(4).Infof(\"successfully populated volume %s\", vol.VolID)\n\t}\n \n //Topology表示volume能够部署在哪些节点(生产情况可能就对应可用区)\n\ttopologies := []*csi.Topology{&csi.Topology{\n\t\tSegments: map[string]string{TopologyKeyNode: cs.nodeID},\n\t}}\n\n\treturn &csi.CreateVolumeResponse{\n\t\tVolume: &csi.Volume{\n\t\t\tVolumeId: volumeID,\n\t\t\tCapacityBytes: req.GetCapacityRange().GetRequiredBytes(),\n\t\t\tVolumeContext: req.GetParameters(),\n\t\t\tContentSource: req.GetVolumeContentSource(),\n\t\t\tAccessibleTopology: topologies,\n\t\t},\n\t}, nil\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"createHostpathVolume","attrs":{}},{"type":"text","text":" ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再来看下","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"createHostpathVolume","attrs":{}}],"attrs":{}},{"type":"text","text":"方法,这里","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"accessType","attrs":{}}],"attrs":{}},{"type":"text","text":"有两个选项,是创建文件系统,还是创建块,其实就是对应pvc中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"volumeMode","attrs":{}}],"attrs":{}},{"type":"text","text":"字段:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/kubernetes-csi/csi-driver-host-path/blob/b1cfe85dd7bfffce2bbe5b1228c994a9bc3649fb/pkg/hostpath/hostpath.go#L208","title":""},"content":[{"type":"text","text":"pkg/hostpath/hostpath.go#L208","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"\n// createVolume create the directory for the hostpath volume.\n// It returns the volume path or err if one occurs.\nfunc createHostpathVolume(volID, name string, cap int64, volAccessType accessType, ephemeral bool) (*hostPathVolume, error) {\n\tpath := getVolumePath(volID)\n\n\tswitch volAccessType {\n\tcase mountAccess:\n //创建文件\n\t\terr := os.MkdirAll(path, 0777)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase blockAccess:\n //创建块\n\t\texecutor := utilexec.New()\n\t\tsize := fmt.Sprintf(\"%dM\", cap/mib)\n\t\t// Create a block file.\n\t\t_, err := os.Stat(path)\n\t\tif err != nil {\n\t\t\tif os.IsNotExist(err) {\n\t\t\t\tout, err := executor.Command(\"fallocate\", \"-l\", size, path).CombinedOutput()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"failed to create block device: %v, %v\", err, string(out))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to stat block device: %v, %v\", path, err)\n\t\t\t}\n\t\t}\n\n // 通过losetup将文件虚拟成块设备\n\t\t// Associate block file with the loop device.\n\t\tvolPathHandler := volumepathhandler.VolumePathHandler{}\n\t\t_, err = volPathHandler.AttachFileDevice(path)\n\t\tif err != nil {\n\t\t\t// Remove the block file because it'll no longer be used again.\n\t\t\tif err2 := os.Remove(path); err2 != nil {\n\t\t\t\tglog.Errorf(\"failed to cleanup block file %s: %v\", path, err2)\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"failed to attach device %v: %v\", path, err)\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported access type %v\", volAccessType)\n\t}\n\n\thostpathVol := hostPathVolume{\n\t\tVolID: volID,\n\t\tVolName: name,\n\t\tVolSize: cap,\n\t\tVolPath: path,\n\t\tVolAccessType: volAccessType,\n\t\tEphemeral: ephemeral,\n\t}\n\thostPathVolumes[volID] = hostpathVol\n\treturn &hostpathVol, nil\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"DeleteVolume","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在DeleteVolume这里主要是删除volume:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/kubernetes-csi/csi-driver-host-path/blob/b1cfe85dd7bfffce2bbe5b1228c994a9bc3649fb/pkg/hostpath/controllerserver.go#L208","title":""},"content":[{"type":"text","text":"pkg/hostpath/controllerserver.go#L2","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {\n\t// Check arguments\n\tif len(req.GetVolumeId()) == 0 {\n\t\treturn nil, status.Error(codes.InvalidArgument, \"Volume ID missing in request\")\n\t}\n\n\tif err := cs.validateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); err != nil {\n\t\tglog.V(3).Infof(\"invalid delete volume req: %v\", req)\n\t\treturn nil, err\n\t}\n\n\tvolId := req.GetVolumeId()\n\tif err := deleteHostpathVolume(volId); err != nil {\n\t\treturn nil, status.Errorf(codes.Internal, \"failed to delete volume %v: %v\", volId, err)\n\t}\n\n\tglog.V(4).Infof(\"volume %v successfully deleted\", volId)\n\n\treturn &csi.DeleteVolumeResponse{}, nil\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ControllerService","attrs":{}}],"attrs":{}},{"type":"text","text":"中还有一些其他接口,比如","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CreateSnapshot","attrs":{}}],"attrs":{}},{"type":"text","text":"创建快照,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"DeleteSnapshot","attrs":{}}],"attrs":{}},{"type":"text","text":"删除快照,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"扩容","attrs":{}}],"attrs":{}},{"type":"text","text":"等,其实都会依赖于我们存储服务端的提供的能力,调用相应的接口就行了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"NodeServer","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nodeServer","attrs":{}}],"attrs":{}},{"type":"text","text":"中就是实现我们的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mount","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"unmount","attrs":{}}],"attrs":{}},{"type":"text","text":"过程了,分别对应","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NodePublishVolume","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NodeUnpublishVolume","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"NodePublishVolume","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/kubernetes-csi/csi-driver-host-path/blob/b1cfe85dd7bfffce2bbe5b1228c994a9bc3649fb/pkg/hostpath/nodeserver.go#L50","title":""},"content":[{"type":"text","text":"pkg/hostpath/nodeserver.go#L5","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {\n\t//......这里省略校验参数代码\n\n\t\n\n\tvol, err := getVolumeByID(req.GetVolumeId())\n\tif err != nil {\n\t\treturn nil, status.Error(codes.NotFound, err.Error())\n\t}\n //对应pvc.volumeBind字段是block的情况\n\tif req.GetVolumeCapability().GetBlock() != nil {\n\t\tif vol.VolAccessType != blockAccess {\n\t\t\treturn nil, status.Error(codes.InvalidArgument, \"cannot publish a non-block volume as block volume\")\n\t\t}\n\n\t\tvolPathHandler := volumepathhandler.VolumePathHandler{}\n\n //获取device地址(通过loopset -l命令,因为是通过文件虚拟出来的块设备)\n\t\t// Get loop device from the volume path.\n\t\tloopDevice, err := volPathHandler.GetLoopDevice(vol.VolPath)\n\t\tif err != nil {\n\t\t\treturn nil, status.Error(codes.Internal, fmt.Sprintf(\"failed to get the loop device: %v\", err))\n\t\t}\n\n\t\tmounter := mount.New(\"\")\n\n\t\t// Check if the target path exists. Create if not present.\n\t\t_, err = os.Lstat(targetPath)\n\t\tif os.IsNotExist(err) {\n\t\t\tif err = mounter.MakeFile(targetPath); err != nil {\n\t\t\t\treturn nil, status.Error(codes.Internal, fmt.Sprintf(\"failed to create target path: %s: %v\", targetPath, err))\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, status.Errorf(codes.Internal, \"failed to check if the target block file exists: %v\", err)\n\t\t}\n\n\t\t// Check if the target path is already mounted. Prevent remounting.\n\t\tnotMount, err := mounter.IsNotMountPoint(targetPath)\n\t\tif err != nil {\n\t\t\tif !os.IsNotExist(err) {\n\t\t\t\treturn nil, status.Errorf(codes.Internal, \"error checking path %s for mount: %s\", targetPath, err)\n\t\t\t}\n\t\t\tnotMount = true\n\t\t}\n\t\tif !notMount {\n\t\t\t// It's already mounted.\n\t\t\tglog.V(5).Infof(\"Skipping bind-mounting subpath %s: already mounted\", targetPath)\n\t\t\treturn &csi.NodePublishVolumeResponse{}, nil\n\t\t}\n\n //进行绑定挂载(mount bind),将块设备绑定到容器目录(targetpath类似这种:/var/lib/kubelet/pods/9c5aa371-e5a7-4b67-8795-ec7013811363/volumes/kubernetes.io~csi/pvc-bcfe33ed-e822-4b0e-954a-0f5c0468525e/mount)\n\t\toptions := []string{\"bind\"}\n\t\tif err := mount.New(\"\").Mount(loopDevice, targetPath, \"\", options); err != nil {\n\t\t\treturn nil, status.Error(codes.Internal, fmt.Sprintf(\"failed to mount block device: %s at %s: %v\", loopDevice, targetPath, err))\n\t\t}\n //对应pvc.volumeBind字段是filesystem的情况\n\t} else if req.GetVolumeCapability().GetMount() != nil {\n\t\t//....这里省略,因为跟上面类似也是mount bind过程\n\t}\n\n\treturn &csi.NodePublishVolumeResponse{}, nil\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"####NodeUnpublishVolume","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"NodeUnpublishVolume","attrs":{}}],"attrs":{}},{"type":"text","text":"过程就是unmount过程,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/kubernetes-csi/csi-driver-host-path/blob/b1cfe85dd7bfffce2bbe5b1228c994a9bc3649fb/pkg/hostpath/nodeserver.go#L191","title":""},"content":[{"type":"text","text":"pkg/hostpath/nodeserver.go#L191","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {\n\n\t// Check arguments\n\tif len(req.GetVolumeId()) == 0 {\n\t\treturn nil, status.Error(codes.InvalidArgument, \"Volume ID missing in request\")\n\t}\n\tif len(req.GetTargetPath()) == 0 {\n\t\treturn nil, status.Error(codes.InvalidArgument, \"Target path missing in request\")\n\t}\n\ttargetPath := req.GetTargetPath()\n\tvolumeID := req.GetVolumeId()\n\n\tvol, err := getVolumeByID(volumeID)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.NotFound, err.Error())\n\t}\n\n\t// Unmount only if the target path is really a mount point.\n\tif notMnt, err := mount.IsNotMountPoint(mount.New(\"\"), targetPath); err != nil {\n\t\tif !os.IsNotExist(err) {\n\t\t\treturn nil, status.Error(codes.Internal, err.Error())\n\t\t}\n\t} else if !notMnt {\n\t\t// Unmounting the image or filesystem.\n\t\terr = mount.New(\"\").Unmount(targetPath)\n\t\tif err != nil {\n\t\t\treturn nil, status.Error(codes.Internal, err.Error())\n\t\t}\n\t}\n\t// Delete the mount point.\n\t// Does not return error for non-existent path, repeated calls OK for idempotency.\n\tif err = os.RemoveAll(targetPath); err != nil {\n\t\treturn nil, status.Error(codes.Internal, err.Error())\n\t}\n\tglog.V(4).Infof(\"hostpath: volume %s has been unpublished.\", targetPath)\n\n\tif vol.Ephemeral {\n\t\tglog.V(4).Infof(\"deleting volume %s\", volumeID)\n\t\tif err := deleteHostpathVolume(volumeID); err != nil && !os.IsNotExist(err) {\n\t\t\treturn nil, status.Error(codes.Internal, fmt.Sprintf(\"failed to delete volume: %s\", err))\n\t\t}\n\t}\n\n\treturn &csi.NodeUnpublishVolumeResponse{}, nil\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"启动grpc server","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/kubernetes-csi/csi-driver-host-path/blob/b1cfe85dd7bfffce2bbe5b1228c994a9bc3649fb/pkg/hostpath/hostpath.go#L164","title":""},"content":[{"type":"text","text":"pkg/hostpath/hostpath.go#L164","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (hp *hostPath) Run() {\n\t// Create GRPC servers\n\thp.ids = NewIdentityServer(hp.name, hp.version)\n\thp.ns = NewNodeServer(hp.nodeID, hp.ephemeral, hp.maxVolumesPerNode)\n\thp.cs = NewControllerServer(hp.ephemeral, hp.nodeID)\n\n \n\ts := NewNonBlockingGRPCServer()\n\ts.Start(hp.endpoint, hp.ids, hp.cs, hp.ns)\n\ts.Wait()\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"##测试","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们可以通过","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/rexray/gocsi/tree/master/csc","title":""},"content":[{"type":"text","text":"csc","attrs":{}}]},{"type":"text","text":"工具来进行grpc接口的测试:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"sh"},"content":[{"type":"text","text":"$ GO111MODULE=off go get -u github.com/rexray/gocsi/csc","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Get plugin info","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"$ csc identity plugin-info --endpoint tcp://127.0.0.1:10000\n\"csi-hostpath\" \"0.1.0\"","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Create a volume","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"$ csc controller new --endpoint tcp://127.0.0.1:10000 --cap 1,block CSIVolumeName\nCSIVolumeID","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Delete a volume","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"$ csc controller del --endpoint tcp://127.0.0.1:10000 CSIVolumeID\nCSIVolumeID","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Validate volume capabilities","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"$ csc controller validate-volume-capabilities --endpoint tcp://127.0.0.1:10000 --cap 1,block CSIVolumeID\nCSIVolumeID true","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"NodePublish a volume","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"$ csc node publish --endpoint tcp://127.0.0.1:10000 --cap 1,block --target-path /mnt/hostpath CSIVolumeID\nCSIVolumeID","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"NodeUnpublish a volume","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"$ csc node unpublish --endpoint tcp://127.0.0.1:10000 --target-path /mnt/hostpath CSIVolumeID\nCSIVolumeID","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Get Nodeinfo","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"$ csc node get-info --endpoint tcp://127.0.0.1:10000\nCSINode","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"部署","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从上一篇文章中我们可以看到,CSI真正运行起来,其实还需要一些官方提供的组件进行配合,比如","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"node-driver-registrar","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"csi-provision","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"csi-attacher","attrs":{}}],"attrs":{}},{"type":"text","text":",我们将这些container作为我们的sidecar容器,通过volume共享socket连接,方便调用,部署在一起。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们把服务分为两个部分:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"controller :以Deployment或者Statefulset方式部署,通过leader selector,控制只有一个在工作。 ","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"node:以DaemonSet方式部署,在每个节点上都调度","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"hostpath因为只有在单个节点上测试用,所以它的都使用了Statefulset,因为只是测试。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在生产部署的话可以参考csi-driver-nfs 服务的部署,这个服务比较完整。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/deploy/csi-nfs-node.yaml","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/deploy/csi-nfs-controller.yaml","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当然还有一些rbac,CSIDriver的创建,这里就不贴出来了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"总结","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回顾下整个组件是怎么协调工作的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"csi-provisioner","attrs":{}}],"attrs":{}},{"type":"text","text":"组件监听pvc的创建,从而通过 CSI socket 创建 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/container-storage-interface/spec/blob/master/spec.md#createvolume","title":""},"content":[{"type":"text","text":"CreateVolumeRequest","attrs":{}}]},{"type":"text","text":" 请求至","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CreateVolume","attrs":{}}],"attrs":{}},{"type":"text","text":"方法","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"csi-provisioner","attrs":{}}],"attrs":{}},{"type":"text","text":"创建 PV 以及更新 PVC状态至 bound ,从而由 controller-manager创建","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"VolumeAttachment","attrs":{}}],"attrs":{}},{"type":"text","text":"对象","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"csi-attacher","attrs":{}}],"attrs":{}},{"type":"text","text":" 监听","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"VolumeAttachments","attrs":{}}],"attrs":{}},{"type":"text","text":" 对象创建,从而调用","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/container-storage-interface/spec/blob/master/spec.md#controllerpublishvolume","title":""},"content":[{"type":"text","text":" ControllerPublishVolume","attrs":{}}]},{"type":"text","text":" 方法。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"kubelet","attrs":{}}],"attrs":{}},{"type":"text","text":"一直都在等待volume attach, 从而调用 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/container-storage-interface/spec/blob/master/spec.md#nodestagevolume","title":""},"content":[{"type":"text","text":"NodeStageVolume","attrs":{}}]},{"type":"text","text":" (主要做格式化以及mount到节点上一个全局目录) 方法 - ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"这一步可选","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CSI Driver在 在 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/container-storage-interface/spec/blob/master/spec.md#nodestagevolume","title":""},"content":[{"type":"text","text":"NodeStageVolume","attrs":{}}]},{"type":"text","text":" 方法中将volumemount到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"/var/lib/kubelet/plugins/kubernetes.io/csi/pv//globalmount","attrs":{}}],"attrs":{}},{"type":"text","text":"这个目录并返回给kubelet - ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"这一步可选","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"kubelet","attrs":{}}],"attrs":{}},{"type":"text","text":"调用","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/container-storage-interface/spec/blob/master/spec.md#nodepublishvolume","title":""},"content":[{"type":"text","text":"NodePublishVolume","attrs":{}}]},{"type":"text","text":" (挂载到pod目录通过","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mount bind","attrs":{}}],"attrs":{}},{"type":"text","text":")","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CSI Driver相应","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/container-storage-interface/spec/blob/master/spec.md#nodepublishvolume","title":""},"content":[{"type":"text","text":" NodePublishVolume","attrs":{}}]},{"type":"text","text":" 请求,将volume挂载到pod目录 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"/var/lib/kubelet/pods//volumes/[kubernetes.io](http://kubernetes.io/)~csi//mount","attrs":{}}],"attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,kubelet启动容器","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"参考","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://medium.com/velotio-perspectives/kubernetes-csi-in-action-explained-with-features-and-use-cases-4f966b910774","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://kubernetes-csi.github.io/docs/developing.html","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关注公众号,获取最新文章推送:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5d/5deb67509e4bddb6d90e87cd91a03562.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章