<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何編寫一個 CSI 插件

          共 3849字,需瀏覽 8分鐘

           ·

          2021-01-27 17:43

          這里以csi-driver-host-path作為例子,來看看是如何實現(xiàn)一個csi插件的?

          目標(biāo):

          • 支持PV動態(tài)創(chuàng)建,并且能夠掛載在POD中
          • volume來自本地目錄,主要是模擬volume產(chǎn)生的過程,這樣就不依賴于某個特定的存儲服務(wù)

          預(yù)備知識

          上一篇文章中,已經(jīng)對CSI概念有個了解,并且提出了CSI組件需要實現(xiàn)的RPC接口,那我們?yōu)槭裁葱枰@些接口,這需要從volume要被使用經(jīng)過了以下流程:

          • volume創(chuàng)建
          • volume attach到節(jié)點(比如像EBS硬盤,NFS可能就直接下一步mount了)
          • volume 被mount到指定目錄(這個目錄其實就被映射到容器中,由kubelet 中的VolumeManager 調(diào)用)

          而當(dāng)卸載時正好是相反的:unmount,detach,delete volume

          正好對應(yīng)如下圖:

          ???CreateVolume?+------------+?DeleteVolume
          ?+------------->|??CREATED???+--------------+
          ?|??????????????+---+----^---+??????????????|
          ?|???????Controller?|????|?Controller???????v
          +++?????????Publish?|????|?Unpublish???????+++
          |X|??????????Volume?|????|?Volume??????????|?|
          +-+?????????????+---v----+---+?????????????+-+
          ????????????????|?NODE_READY?|
          ????????????????+---+----^---+
          ???????????????Node?|????|?Node
          ??????????????Stage?|????|?Unstage
          ?????????????Volume?|????|?Volume
          ????????????????+---v----+---+
          ????????????????|??VOL_READY?|
          ????????????????+---+----^---+
          ???????????????Node?|????|?Node
          ????????????Publish?|????|?Unpublish
          ?????????????Volume?|????|?Volume
          ????????????????+---v----+---+
          ????????????????|?PUBLISHED??|
          ????????????????+------------+

          而為什么多個NodeStageVolume的過程是因為:

          對于塊存儲來說,設(shè)備只能mount到一個目錄上,所以NodeStageVolume就是先mount到一個globalmount目錄(類似:/var/lib/kubelet/plugins/kubernetes.io/csi/pv/pvc-bcfe33ed-e822-4b0e-954a-0f5c0468525e/globalmount),然后再NodePublishVolume這一步中通過mount bind到pod的目錄(/var/lib/kubelet/pods/9c5aa371-e5a7-4b67-8795-ec7013811363/volumes/kubernetes.io~csi/pvc-bcfe33ed-e822-4b0e-954a-0f5c0468525e/mount/hello-world),這樣就可以實現(xiàn)一個pv掛載在多個pod中使用。

          代碼實現(xiàn)

          我們并不一定要實現(xiàn)所有的接口,這個可以通過CSI中Capabilities能力標(biāo)識出來,我們組件提供的能力,比如

          • IdentityServer中的GetPluginCapabilities方法

          • ControllerServer中的ControllerGetCapabilities方法

          • NodeServer中的NodeGetCapabilities

          這些方法都是在告訴調(diào)用方,我們的組件實現(xiàn)了哪些能力,未實現(xiàn)的方法就不會調(diào)用了。

          IdentityServer

          IdentityServer包含了三個接口,這里我們主要實現(xiàn)

          //?IdentityServer?is?the?server?API?for?Identity?service.
          type?IdentityServer?interface?{
          ?GetPluginInfo(context.Context,?*GetPluginInfoRequest)?(*GetPluginInfoResponse,?error)
          ?GetPluginCapabilities(context.Context,?*GetPluginCapabilitiesRequest)?(*GetPluginCapabilitiesResponse,?error)
          ?Probe(context.Context,?*ProbeRequest)?(*ProbeResponse,?error)
          }

          主要看下GetPluginCapabilities這個方法:

          identityserver.go#L60:

          func?(ids?*identityServer)?GetPluginCapabilities(ctx?context.Context,?req?*csi.GetPluginCapabilitiesRequest)?(*csi.GetPluginCapabilitiesResponse,?error)?{
          ?return?&csi.GetPluginCapabilitiesResponse{
          ??Capabilities:?[]*csi.PluginCapability{
          ???{
          ????Type:?&csi.PluginCapability_Service_{
          ?????Service:?&csi.PluginCapability_Service{
          ??????Type:?csi.PluginCapability_Service_CONTROLLER_SERVICE,
          ?????},
          ????},
          ???},
          ???{
          ????Type:?&csi.PluginCapability_Service_{
          ?????Service:?&csi.PluginCapability_Service{
          ??????Type:?csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS,
          ?????},
          ????},
          ???},
          ??},
          ?},?nil
          }

          以上就告訴調(diào)用者我們提供了ControllerService的能力,以及volume訪問限制的能力(CSI 處理時需要根據(jù)集群拓?fù)渥髡{(diào)整)

          PS:其實在k8s還提供了一個包:github.com/kubernetes-csi/drivers/pkg/csi-common,里面提供了比如DefaultIdentityServerDefaultControllerServer,DefaultNodeServer的struct,只要在我們自己的XXXServer struct中繼承這些struct,我們的代碼中就只要包含自己實現(xiàn)的方法就行了,可以參考alibaba-cloud-csi-driver中的。

          ###ControllerServer

          ControllerServer我們主要關(guān)注CreateVolume,DeleteVolume,因為是hostpath volume,所以就沒有attach的這個過程了,我們放在NodeServer中實現(xiàn):

          CreateVolume

          controllerserver.go#L73

          func?(cs?*controllerServer)?CreateVolume(ctx?context.Context,?req?*csi.CreateVolumeRequest)?(*csi.CreateVolumeResponse,?error)?{
          ??//校驗參數(shù)是否有CreateVolume的能力
          ?if?err?:=?cs.validateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME);?err?!=?nil?{
          ??glog.V(3).Infof("invalid?create?volume?req:?%v",?req)
          ??return?nil,?err
          ?}

          ??//.....這里省略的校驗參數(shù)的過程


          ??//這里根據(jù)volume?name判斷是否已經(jīng)存在了,存在了就返回就行了
          ?if?exVol,?err?:=?getVolumeByName(req.GetName());?err?==?nil?{
          ??//?volume已經(jīng)存在,但是大小不符合
          ??if?exVol.VolSize????return?nil,?status.Errorf(codes.AlreadyExists,?"Volume?with?the?same?name:?%s?but?with?different?size?already?exist",?req.GetName())
          ??}
          ????//這里判斷是否設(shè)置了pvc.dataSource,就表示是一個restore過程
          ??if?req.GetVolumeContentSource()?!=?nil?{
          ???volumeSource?:=?req.VolumeContentSource
          ???switch?volumeSource.Type.(type)?{
          ????????//校驗:從快照中恢復(fù)
          ???case?*csi.VolumeContentSource_Snapshot:
          ????if?volumeSource.GetSnapshot()?!=?nil?&&?exVol.ParentSnapID?!=?""?&&?exVol.ParentSnapID?!=?volumeSource.GetSnapshot().GetSnapshotId()?{
          ?????return?nil,?status.Error(codes.AlreadyExists,?"existing?volume?source?snapshot?id?not?matching")
          ????}
          ????????//校驗:clone過程
          ???case?*csi.VolumeContentSource_Volume:
          ????if?volumeSource.GetVolume()?!=?nil?&&?exVol.ParentVolID?!=?volumeSource.GetVolume().GetVolumeId()?{
          ?????return?nil,?status.Error(codes.AlreadyExists,?"existing?volume?source?volume?id?not?matching")
          ????}
          ???default:
          ????return?nil,?status.Errorf(codes.InvalidArgument,?"%v?not?a?proper?volume?source",?volumeSource)
          ???}
          ??}
          ??//?TODO?(sbezverk)?Do?I?need?to?make?sure?that?volume?still?exists?
          ??return?&csi.CreateVolumeResponse{
          ???Volume:?&csi.Volume{
          ????VolumeId:??????exVol.VolID,
          ????CapacityBytes:?int64(exVol.VolSize),
          ????VolumeContext:?req.GetParameters(),
          ????ContentSource:?req.GetVolumeContentSource(),
          ???},
          ??},?nil
          ?}

          ??//創(chuàng)建volume
          ?volumeID?:=?uuid.NewUUID().String()
          ??//創(chuàng)建hostpath的volume
          ?vol,?err?:=?createHostpathVolume(volumeID,?req.GetName(),?capacity,?requestedAccessType,?false?/*?ephemeral?*/)
          ?if?err?!=?nil?{
          ??return?nil,?status.Errorf(codes.Internal,?"failed?to?create?volume?%v:?%v",?volumeID,?err)
          ?}
          ?glog.V(4).Infof("created?volume?%s?at?path?%s",?vol.VolID,?vol.VolPath)
          ??
          ??//判斷是從快照恢復(fù),還是clone
          ?if?req.GetVolumeContentSource()?!=?nil?{
          ??path?:=?getVolumePath(volumeID)
          ??volumeSource?:=?req.VolumeContentSource
          ??switch?volumeSource.Type.(type)?{
          ??????//從快照恢復(fù)
          ??case?*csi.VolumeContentSource_Snapshot:
          ???if?snapshot?:=?volumeSource.GetSnapshot();?snapshot?!=?nil?{
          ????err?=?loadFromSnapshot(capacity,?snapshot.GetSnapshotId(),?path,?requestedAccessType)
          ????vol.ParentSnapID?=?snapshot.GetSnapshotId()
          ???}
          ??????//clone
          ??case?*csi.VolumeContentSource_Volume:
          ???if?srcVolume?:=?volumeSource.GetVolume();?srcVolume?!=?nil?{
          ????err?=?loadFromVolume(capacity,?srcVolume.GetVolumeId(),?path,?requestedAccessType)
          ????vol.ParentVolID?=?srcVolume.GetVolumeId()
          ???}
          ??default:
          ???err?=?status.Errorf(codes.InvalidArgument,?"%v?not?a?proper?volume?source",?volumeSource)
          ??}
          ??if?err?!=?nil?{
          ???if?delErr?:=?deleteHostpathVolume(volumeID);?delErr?!=?nil?{
          ????glog.V(2).Infof("deleting?hostpath?volume?%v?failed:?%v",?volumeID,?delErr)
          ???}
          ???return?nil,?err
          ??}
          ??glog.V(4).Infof("successfully?populated?volume?%s",?vol.VolID)
          ?}
          ??
          ??//Topology表示volume能夠部署在哪些節(jié)點(生產(chǎn)情況可能就對應(yīng)可用區(qū))
          ?topologies?:=?[]*csi.Topology{&csi.Topology{
          ??Segments:?map[string]string{TopologyKeyNode:?cs.nodeID},
          ?}}

          ?return?&csi.CreateVolumeResponse{
          ??Volume:?&csi.Volume{
          ???VolumeId:???????????volumeID,
          ???CapacityBytes:??????req.GetCapacityRange().GetRequiredBytes(),
          ???VolumeContext:??????req.GetParameters(),
          ???ContentSource:??????req.GetVolumeContentSource(),
          ???AccessibleTopology:?topologies,
          ??},
          ?},?nil
          }

          createHostpathVolume

          再來看下createHostpathVolume方法,這里accessType有兩個選項,是創(chuàng)建文件系統(tǒng),還是創(chuàng)建塊,其實就是對應(yīng)pvc中volumeMode字段:

          pkg/hostpath/hostpath.go#L208


          //?createVolume?create?the?directory?for?the?hostpath?volume.
          //?It?returns?the?volume?path?or?err?if?one?occurs.
          func?createHostpathVolume(volID,?name?string,?cap?int64,?volAccessType?accessType,?ephemeral?bool)?(*hostPathVolume,?error)?{
          ?path?:=?getVolumePath(volID)

          ?switch?volAccessType?{
          ?case?mountAccess:
          ????//創(chuàng)建文件
          ??err?:=?os.MkdirAll(path,?0777)
          ??if?err?!=?nil?{
          ???return?nil,?err
          ??}
          ?case?blockAccess:
          ????//創(chuàng)建塊
          ??executor?:=?utilexec.New()
          ??size?:=?fmt.Sprintf("%dM",?cap/mib)
          ??//?Create?a?block?file.
          ??_,?err?:=?os.Stat(path)
          ??if?err?!=?nil?{
          ???if?os.IsNotExist(err)?{
          ????out,?err?:=?executor.Command("fallocate",?"-l",?size,?path).CombinedOutput()
          ????if?err?!=?nil?{
          ?????return?nil,?fmt.Errorf("failed?to?create?block?device:?%v,?%v",?err,?string(out))
          ????}
          ???}?else?{
          ????return?nil,?fmt.Errorf("failed?to?stat?block?device:?%v,?%v",?path,?err)
          ???}
          ??}

          ????//?通過losetup將文件虛擬成塊設(shè)備
          ??//?Associate?block?file?with?the?loop?device.
          ??volPathHandler?:=?volumepathhandler.VolumePathHandler{}
          ??_,?err?=?volPathHandler.AttachFileDevice(path)
          ??if?err?!=?nil?{
          ???//?Remove?the?block?file?because?it'll?no?longer?be?used?again.
          ???if?err2?:=?os.Remove(path);?err2?!=?nil?{
          ????glog.Errorf("failed?to?cleanup?block?file?%s:?%v",?path,?err2)
          ???}
          ???return?nil,?fmt.Errorf("failed?to?attach?device?%v:?%v",?path,?err)
          ??}
          ?default:
          ??return?nil,?fmt.Errorf("unsupported?access?type?%v",?volAccessType)
          ?}

          ?hostpathVol?:=?hostPathVolume{
          ??VolID:?????????volID,
          ??VolName:???????name,
          ??VolSize:???????cap,
          ??VolPath:???????path,
          ??VolAccessType:?volAccessType,
          ??Ephemeral:?????ephemeral,
          ?}
          ?hostPathVolumes[volID]?=?hostpathVol
          ?return?&hostpathVol,?nil
          }

          DeleteVolume

          在DeleteVolume這里主要是刪除volume:

          pkg/hostpath/controllerserver.go#L2

          func?(cs?*controllerServer)?DeleteVolume(ctx?context.Context,?req?*csi.DeleteVolumeRequest)?(*csi.DeleteVolumeResponse,?error)?{
          ?//?Check?arguments
          ?if?len(req.GetVolumeId())?==?0?{
          ??return?nil,?status.Error(codes.InvalidArgument,?"Volume?ID?missing?in?request")
          ?}

          ?if?err?:=?cs.validateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME);?err?!=?nil?{
          ??glog.V(3).Infof("invalid?delete?volume?req:?%v",?req)
          ??return?nil,?err
          ?}

          ?volId?:=?req.GetVolumeId()
          ?if?err?:=?deleteHostpathVolume(volId);?err?!=?nil?{
          ??return?nil,?status.Errorf(codes.Internal,?"failed?to?delete?volume?%v:?%v",?volId,?err)
          ?}

          ?glog.V(4).Infof("volume?%v?successfully?deleted",?volId)

          ?return?&csi.DeleteVolumeResponse{},?nil
          }

          ControllerService中還有一些其他接口,比如CreateSnapshot創(chuàng)建快照,DeleteSnapshot刪除快照,擴(kuò)容等,其實都會依賴于我們存儲服務(wù)端的提供的能力,調(diào)用相應(yīng)的接口就行了。

          NodeServer

          nodeServer中就是實現(xiàn)我們的mount,unmount過程了,分別對應(yīng)NodePublishVolumeNodeUnpublishVolume

          NodePublishVolume

          pkg/hostpath/nodeserver.go#L5

          func?(ns?*nodeServer)?NodePublishVolume(ctx?context.Context,?req?*csi.NodePublishVolumeRequest)?(*csi.NodePublishVolumeResponse,?error)?{
          ?//......這里省略校驗參數(shù)代碼

          ?

          ?vol,?err?:=?getVolumeByID(req.GetVolumeId())
          ?if?err?!=?nil?{
          ??return?nil,?status.Error(codes.NotFound,?err.Error())
          ?}
          ??//對應(yīng)pvc.volumeBind字段是block的情況
          ?if?req.GetVolumeCapability().GetBlock()?!=?nil?{
          ??if?vol.VolAccessType?!=?blockAccess?{
          ???return?nil,?status.Error(codes.InvalidArgument,?"cannot?publish?a?non-block?volume?as?block?volume")
          ??}

          ??volPathHandler?:=?volumepathhandler.VolumePathHandler{}

          ????//獲取device地址(通過loopset?-l命令,因為是通過文件虛擬出來的塊設(shè)備)
          ??//?Get?loop?device?from?the?volume?path.
          ??loopDevice,?err?:=?volPathHandler.GetLoopDevice(vol.VolPath)
          ??if?err?!=?nil?{
          ???return?nil,?status.Error(codes.Internal,?fmt.Sprintf("failed?to?get?the?loop?device:?%v",?err))
          ??}

          ??mounter?:=?mount.New("")

          ??//?Check?if?the?target?path?exists.?Create?if?not?present.
          ??_,?err?=?os.Lstat(targetPath)
          ??if?os.IsNotExist(err)?{
          ???if?err?=?mounter.MakeFile(targetPath);?err?!=?nil?{
          ????return?nil,?status.Error(codes.Internal,?fmt.Sprintf("failed?to?create?target?path:?%s:?%v",?targetPath,?err))
          ???}
          ??}
          ??if?err?!=?nil?{
          ???return?nil,?status.Errorf(codes.Internal,?"failed?to?check?if?the?target?block?file?exists:?%v",?err)
          ??}

          ??//?Check?if?the?target?path?is?already?mounted.?Prevent?remounting.
          ??notMount,?err?:=?mounter.IsNotMountPoint(targetPath)
          ??if?err?!=?nil?{
          ???if?!os.IsNotExist(err)?{
          ????return?nil,?status.Errorf(codes.Internal,?"error?checking?path?%s?for?mount:?%s",?targetPath,?err)
          ???}
          ???notMount?=?true
          ??}
          ??if?!notMount?{
          ???//?It's?already?mounted.
          ???glog.V(5).Infof("Skipping?bind-mounting?subpath?%s:?already?mounted",?targetPath)
          ???return?&csi.NodePublishVolumeResponse{},?nil
          ??}

          ????//進(jìn)行綁定掛載(mount bind),將塊設(shè)備綁定到容器目錄(targetpath類似這種:/var/lib/kubelet/pods/9c5aa371-e5a7-4b67-8795-ec7013811363/volumes/kubernetes.io~csi/pvc-bcfe33ed-e822-4b0e-954a-0f5c0468525e/mount)
          ??options?:=?[]string{"bind"}
          ??if?err?:=?mount.New("").Mount(loopDevice,?targetPath,?"",?options);?err?!=?nil?{
          ???return?nil,?status.Error(codes.Internal,?fmt.Sprintf("failed?to?mount?block?device:?%s?at?%s:?%v",?loopDevice,?targetPath,?err))
          ??}
          ????//對應(yīng)pvc.volumeBind字段是filesystem的情況
          ?}?else?if?req.GetVolumeCapability().GetMount()?!=?nil?{
          ??//....這里省略,因為跟上面類似也是mount?bind過程
          ?}

          ?return?&csi.NodePublishVolumeResponse{},?nil
          }

          ####NodeUnpublishVolume

          NodeUnpublishVolume過程就是unmount過程,如下:

          pkg/hostpath/nodeserver.go#L191

          func?(ns?*nodeServer)?NodeUnpublishVolume(ctx?context.Context,?req?*csi.NodeUnpublishVolumeRequest)?(*csi.NodeUnpublishVolumeResponse,?error)?{

          ?//?Check?arguments
          ?if?len(req.GetVolumeId())?==?0?{
          ??return?nil,?status.Error(codes.InvalidArgument,?"Volume?ID?missing?in?request")
          ?}
          ?if?len(req.GetTargetPath())?==?0?{
          ??return?nil,?status.Error(codes.InvalidArgument,?"Target?path?missing?in?request")
          ?}
          ?targetPath?:=?req.GetTargetPath()
          ?volumeID?:=?req.GetVolumeId()

          ?vol,?err?:=?getVolumeByID(volumeID)
          ?if?err?!=?nil?{
          ??return?nil,?status.Error(codes.NotFound,?err.Error())
          ?}

          ?//?Unmount?only?if?the?target?path?is?really?a?mount?point.
          ?if?notMnt,?err?:=?mount.IsNotMountPoint(mount.New(""),?targetPath);?err?!=?nil?{
          ??if?!os.IsNotExist(err)?{
          ???return?nil,?status.Error(codes.Internal,?err.Error())
          ??}
          ?}?else?if?!notMnt?{
          ??//?Unmounting?the?image?or?filesystem.
          ??err?=?mount.New("").Unmount(targetPath)
          ??if?err?!=?nil?{
          ???return?nil,?status.Error(codes.Internal,?err.Error())
          ??}
          ?}
          ?//?Delete?the?mount?point.
          ?//?Does?not?return?error?for?non-existent?path,?repeated?calls?OK?for?idempotency.
          ?if?err?=?os.RemoveAll(targetPath);?err?!=?nil?{
          ??return?nil,?status.Error(codes.Internal,?err.Error())
          ?}
          ?glog.V(4).Infof("hostpath:?volume?%s?has?been?unpublished.",?targetPath)

          ?if?vol.Ephemeral?{
          ??glog.V(4).Infof("deleting?volume?%s",?volumeID)
          ??if?err?:=?deleteHostpathVolume(volumeID);?err?!=?nil?&&?!os.IsNotExist(err)?{
          ???return?nil,?status.Error(codes.Internal,?fmt.Sprintf("failed?to?delete?volume:?%s",?err))
          ??}
          ?}

          ?return?&csi.NodeUnpublishVolumeResponse{},?nil
          }

          啟動grpc server

          pkg/hostpath/hostpath.go#L164

          func?(hp?*hostPath)?Run()?{
          ?//?Create?GRPC?servers
          ?hp.ids?=?NewIdentityServer(hp.name,?hp.version)
          ?hp.ns?=?NewNodeServer(hp.nodeID,?hp.ephemeral,?hp.maxVolumesPerNode)
          ?hp.cs?=?NewControllerServer(hp.ephemeral,?hp.nodeID)

          ??
          ?s?:=?NewNonBlockingGRPCServer()
          ?s.Start(hp.endpoint,?hp.ids,?hp.cs,?hp.ns)
          ?s.Wait()
          }

          ##測試

          我們可以通過csc工具來進(jìn)行g(shù)rpc接口的測試:

          $?GO111MODULE=off?go?get?-u?github.com/rexray/gocsi/csc

          Get plugin info

          $?csc?identity?plugin-info?--endpoint?tcp://127.0.0.1:10000
          "csi-hostpath"??"0.1.0"

          Create a volume

          $?csc?controller?new?--endpoint?tcp://127.0.0.1:10000?--cap?1,block?CSIVolumeName
          CSIVolumeID

          Delete a volume

          $?csc?controller?del?--endpoint?tcp://127.0.0.1:10000?CSIVolumeID
          CSIVolumeID

          Validate volume capabilities

          $?csc?controller?validate-volume-capabilities?--endpoint?tcp://127.0.0.1:10000?--cap?1,block?CSIVolumeID
          CSIVolumeID??true

          NodePublish a volume

          $?csc?node?publish?--endpoint?tcp://127.0.0.1:10000?--cap?1,block?--target-path?/mnt/hostpath?CSIVolumeID
          CSIVolumeID

          NodeUnpublish a volume

          $?csc?node?unpublish?--endpoint?tcp://127.0.0.1:10000?--target-path?/mnt/hostpath?CSIVolumeID
          CSIVolumeID

          Get Nodeinfo

          $?csc?node?get-info?--endpoint?tcp://127.0.0.1:10000
          CSINode

          部署

          從上一篇文章中我們可以看到,CSI真正運行起來,其實還需要一些官方提供的組件進(jìn)行配合,比如node-driver-registrarcsi-provisioncsi-attacher,我們將這些container作為我們的sidecar容器,通過volume共享socket連接,方便調(diào)用,部署在一起。

          我們把服務(wù)分為兩個部分:

          • controller :以Deployment或者Statefulset方式部署,通過leader selector,控制只有一個在工作。
          • node:以DaemonSet方式部署,在每個節(jié)點上都調(diào)度

          hostpath因為只有在單個節(jié)點上測試用,所以它的都使用了Statefulset,因為只是測試。

          在生產(chǎn)部署的話可以參考csi-driver-nfs 服務(wù)的部署,這個服務(wù)比較完整。

          • https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/deploy/csi-nfs-node.yaml
          • https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/deploy/csi-nfs-controller.yaml

          當(dāng)然還有一些rbac,CSIDriver的創(chuàng)建,這里就不貼出來了。

          總結(jié)

          回顧下整個組件是怎么協(xié)調(diào)工作的:

          • csi-provisioner組件監(jiān)聽pvc的創(chuàng)建,從而通過 CSI socket 創(chuàng)建 CreateVolumeRequest 請求至CreateVolume方法
          • csi-provisioner創(chuàng)建 PV 以及更新 PVC狀態(tài)至 ?bound ,從而由 controller-manager創(chuàng)建VolumeAttachment對象
          • csi-attacher 監(jiān)聽VolumeAttachments 對象創(chuàng)建,從而調(diào)用 ControllerPublishVolume 方法。
          • kubelet一直都在等待volume attach, 從而調(diào)用 NodeStageVolume (主要做格式化以及mount到節(jié)點上一個全局目錄) 方法 - 這一步可選
          • CSI Driver在 在 NodeStageVolume 方法中將volumemount到 /var/lib/kubelet/plugins/kubernetes.io/csi/pv//globalmount這個目錄并返回給kubelet - 這一步可選
          • kubelet調(diào)用NodePublishVolume (掛載到pod目錄通過mount bind)
          • CSI Driver相應(yīng) NodePublishVolume 請求,將volume掛載到pod目錄 /var/lib/kubelet/pods//volumes/[kubernetes.io](http://kubernetes.io/)~csi//mount
          • 最后,kubelet啟動容器

          參考

          • https://medium.com/velotio-perspectives/kubernetes-csi-in-action-explained-with-features-and-use-cases-4f966b910774

          • https://kubernetes-csi.github.io/docs/developing.html


          ?點擊屏末?|??|?即刻學(xué)習(xí)

          瀏覽 214
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品熟妇一区二区三区四区 | 亚洲视频黄色 | 国产操操逼视频 | 成人毛片女人AAA久久 | 国产无码专区 |