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}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章