国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

CSI 工作原理與JuiceFS CSI Driver 的架構(gòu)設(shè)計(jì)詳解

這篇具有很好參考價(jià)值的文章主要介紹了CSI 工作原理與JuiceFS CSI Driver 的架構(gòu)設(shè)計(jì)詳解。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

容器存儲(chǔ)接口(Container Storage Interface)簡(jiǎn)稱 CSI,CSI 建立了行業(yè)標(biāo)準(zhǔn)接口的規(guī)范,借助 CSI 容器編排系統(tǒng)(CO)可以將任意存儲(chǔ)系統(tǒng)暴露給自己的容器工作負(fù)載。JuiceFS CSI Driver 通過(guò)實(shí)現(xiàn) CSI 接口使得 Kubernetes 上的應(yīng)用可以通過(guò) PVC(PersistentVolumeClaim)使用 JuiceFS。本文將詳細(xì)介紹 CSI 的工作原理以及 JuiceFS CSI Driver 的架構(gòu)設(shè)計(jì)。

CSI 的基本組件

CSI 的 cloud providers 有兩種類型,一種為 in-tree 類型,一種為 out-of-tree 類型。前者是指運(yùn)行在 K8s 核心組件內(nèi)部的存儲(chǔ)插件;后者是指獨(dú)立在 K8s 組件之外運(yùn)行的存儲(chǔ)插件。本文主要介紹 out-of-tree 類型的插件。

out-of-tree 類型的插件主要是通過(guò) gRPC 接口跟 K8s 組件交互,并且 K8s 提供了大量的 SideCar 組件來(lái)配合 CSI 插件實(shí)現(xiàn)豐富的功能。對(duì)于 out-of-tree 類型的插件來(lái)說(shuō),所用到的組件分為 SideCar 組件和第三方需要實(shí)現(xiàn)的插件。

SideCar 組件

external-attacher

監(jiān)聽(tīng) VolumeAttachment 對(duì)象,并調(diào)用 CSI driver Controller 服務(wù)的 ControllerPublishVolumeControllerUnpublishVolume 接口,用來(lái)將 volume 附著到 node 上,或從 node 上刪除。

如果存儲(chǔ)系統(tǒng)需要 attach/detach 這一步,就需要使用到這個(gè)組件,因?yàn)?K8s 內(nèi)部的 Attach/Detach Controller 不會(huì)直接調(diào)用 CSI driver 的接口。

external-provisioner

監(jiān)聽(tīng) PVC 對(duì)象,并調(diào)用 CSI driver Controller 服務(wù)的 CreateVolumeDeleteVolume 接口,用來(lái)提供一個(gè)新的 volume。前提是 PVC 中指定的 StorageClass 的 provisioner 字段和 CSI driver Identity 服務(wù)的 GetPluginInfo 接口的返回值一樣。一旦新的 volume 提供出來(lái),K8s 就會(huì)創(chuàng)建對(duì)應(yīng)的 PV。

而如果 PVC 綁定的 PV 的回收策略是 delete,那么 external-provisioner 組件監(jiān)聽(tīng)到 PVC 的刪除后,會(huì)調(diào)用 CSI driver Controller 服務(wù)的 DeleteVolume 接口。一旦 volume 刪除成功,該組件也會(huì)刪除相應(yīng)的 PV。

該組件還支持從快照創(chuàng)建數(shù)據(jù)源。如果在 PVC 中指定了 Snapshot CRD 的數(shù)據(jù)源,那么該組件會(huì)通過(guò) SnapshotContent 對(duì)象獲取有關(guān)快照的信息,并將此內(nèi)容在調(diào)用 CreateVolume 接口的時(shí)候傳給 CSI driver,CSI driver 需要根據(jù)數(shù)據(jù)源快照來(lái)創(chuàng)建 volume。

external-resizer

監(jiān)聽(tīng) PVC 對(duì)象,如果用戶請(qǐng)求在 PVC 對(duì)象上請(qǐng)求更多存儲(chǔ),該組件會(huì)調(diào)用 CSI driver Controller 服務(wù)的 NodeExpandVolume 接口,用來(lái)對(duì) volume 進(jìn)行擴(kuò)容。

external-snapshotter

該組件需要與 Snapshot Controller 配合使用。Snapshot Controller 會(huì)根據(jù)集群中創(chuàng)建的 Snapshot 對(duì)象創(chuàng)建對(duì)應(yīng)的 VolumeSnapshotContent,而 external-snapshotter 負(fù)責(zé)監(jiān)聽(tīng) VolumeSnapshotContent 對(duì)象。當(dāng)監(jiān)聽(tīng)到 VolumeSnapshotContent 時(shí),將其對(duì)應(yīng)參數(shù)通過(guò) CreateSnapshotRequest 傳給 CSI driver Controller 服務(wù),調(diào)用其 CreateSnapshot 接口。該組件還負(fù)責(zé)調(diào)用 DeleteSnapshotListSnapshots 接口。

livenessprobe

負(fù)責(zé)監(jiān)測(cè) CSI driver 的健康情況,并通過(guò) Liveness Probe 機(jī)制匯報(bào)給 K8s,當(dāng)監(jiān)測(cè)到 CSI driver 有異常時(shí)負(fù)責(zé)重啟 pod。

node-driver-registrar

通過(guò)直接調(diào)用 CSI driver Node 服務(wù)的 NodeGetInfo 接口,將 CSI driver 的信息通過(guò) kubelet 的插件注冊(cè)機(jī)制在對(duì)應(yīng)節(jié)點(diǎn)的 kubelet 上進(jìn)行注冊(cè)。

external-health-monitor-controller

通過(guò)調(diào)用 CSI driver Controller 服務(wù)的 ListVolumes 或者 ControllerGetVolume 接口,來(lái)檢查 CSI volume 的健康情況,并上報(bào)在 PVC 的 event 中。

external-health-monitor-agent

通過(guò)調(diào)用 CSI driver Node 服務(wù)的 NodeGetVolumeStats 接口,來(lái)檢查 CSI volume 的健康情況,并上報(bào)在 pod 的 event 中。

第三方插件

第三方存儲(chǔ)提供方(即 SP,Storage Provider)需要實(shí)現(xiàn) Controller 和 Node 兩個(gè)插件,其中 Controller 負(fù)責(zé) Volume 的管理,以 StatefulSet 形式部署;Node 負(fù)責(zé)將 Volume mount 到 pod 中,以 DaemonSet 形式部署在每個(gè) node 中。

CSI 插件與 kubelet 以及 K8s 外部組件是通過(guò) Unix Domani Socket gRPC 來(lái)進(jìn)行交互調(diào)用的。CSI 定義了三套 RPC 接口,SP 需要實(shí)現(xiàn)這三組接口,以便與 K8s 外部組件進(jìn)行通信。三組接口分別是:CSI Identity、CSI Controller 和 CSI Node,下面詳細(xì)看看這些接口定義。

CSI Identity

用于提供 CSI driver 的身份信息,Controller 和 Node 都需要實(shí)現(xiàn)。接口如下:

service Identity {
  rpc GetPluginInfo(GetPluginInfoRequest)
    returns (GetPluginInfoResponse) {}

  rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
    returns (GetPluginCapabilitiesResponse) {}

  rpc Probe (ProbeRequest)
    returns (ProbeResponse) {}
}

GetPluginInfo 是必須要實(shí)現(xiàn)的,node-driver-registrar 組件會(huì)調(diào)用這個(gè)接口將 CSI driver 注冊(cè)到 kubelet;GetPluginCapabilities 是用來(lái)表明該 CSI driver 主要提供了哪些功能。

CSI Controller

用于實(shí)現(xiàn)創(chuàng)建/刪除 volume、attach/detach volume、volume 快照、volume 擴(kuò)縮容等功能,Controller 插件需要實(shí)現(xiàn)這組接口。接口如下:

service Controller {
  rpc CreateVolume (CreateVolumeRequest)
    returns (CreateVolumeResponse) {}

  rpc DeleteVolume (DeleteVolumeRequest)
    returns (DeleteVolumeResponse) {}

  rpc ControllerPublishVolume (ControllerPublishVolumeRequest)
    returns (ControllerPublishVolumeResponse) {}

  rpc ControllerUnpublishVolume (ControllerUnpublishVolumeRequest)
    returns (ControllerUnpublishVolumeResponse) {}

  rpc ValidateVolumeCapabilities (ValidateVolumeCapabilitiesRequest)
    returns (ValidateVolumeCapabilitiesResponse) {}

  rpc ListVolumes (ListVolumesRequest)
    returns (ListVolumesResponse) {}

  rpc GetCapacity (GetCapacityRequest)
    returns (GetCapacityResponse) {}

  rpc ControllerGetCapabilities (ControllerGetCapabilitiesRequest)
    returns (ControllerGetCapabilitiesResponse) {}

  rpc CreateSnapshot (CreateSnapshotRequest)
    returns (CreateSnapshotResponse) {}

  rpc DeleteSnapshot (DeleteSnapshotRequest)
    returns (DeleteSnapshotResponse) {}

  rpc ListSnapshots (ListSnapshotsRequest)
    returns (ListSnapshotsResponse) {}

  rpc ControllerExpandVolume (ControllerExpandVolumeRequest)
    returns (ControllerExpandVolumeResponse) {}

  rpc ControllerGetVolume (ControllerGetVolumeRequest)
    returns (ControllerGetVolumeResponse) {
        option (alpha_method) = true;
    }
}

在上面介紹 K8s 外部組件的時(shí)候已經(jīng)提到,不同的接口分別提供給不同的組件調(diào)用,用于配合實(shí)現(xiàn)不同的功能。比如 CreateVolume/DeleteVolume 配合 external-provisioner 實(shí)現(xiàn)創(chuàng)建/刪除 volume 的功能;ControllerPublishVolume/ControllerUnpublishVolume 配合 external-attacher 實(shí)現(xiàn) volume 的 attach/detach 功能等。

CSI Node

用于實(shí)現(xiàn) mount/umount volume、檢查 volume 狀態(tài)等功能,Node 插件需要實(shí)現(xiàn)這組接口。接口如下:

service Node {
  rpc NodeStageVolume (NodeStageVolumeRequest)
    returns (NodeStageVolumeResponse) {}

  rpc NodeUnstageVolume (NodeUnstageVolumeRequest)
    returns (NodeUnstageVolumeResponse) {}

  rpc NodePublishVolume (NodePublishVolumeRequest)
    returns (NodePublishVolumeResponse) {}

  rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
    returns (NodeUnpublishVolumeResponse) {}

  rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest)
    returns (NodeGetVolumeStatsResponse) {}

  rpc NodeExpandVolume(NodeExpandVolumeRequest)
    returns (NodeExpandVolumeResponse) {}

  rpc NodeGetCapabilities (NodeGetCapabilitiesRequest)
    returns (NodeGetCapabilitiesResponse) {}

  rpc NodeGetInfo (NodeGetInfoRequest)
    returns (NodeGetInfoResponse) {}
}

NodeStageVolume 用來(lái)實(shí)現(xiàn)多個(gè) pod 共享一個(gè) volume 的功能,支持先將 volume 掛載到一個(gè)臨時(shí)目錄,然后通過(guò) NodePublishVolume 將其掛載到 pod 中;NodeUnstageVolume 為其反操作。

工作流程

下面來(lái)看看 pod 掛載 volume 的整個(gè)工作流程。整個(gè)流程流程分別三個(gè)階段:Provision/Delete、Attach/Detach、Mount/Unmount,不過(guò)不是每個(gè)存儲(chǔ)方案都會(huì)經(jīng)歷這三個(gè)階段,比如 NFS 就沒(méi)有 Attach/Detach 階段。

整個(gè)過(guò)程不僅僅涉及到上面介紹的組件的工作,還涉及 ControllerManager 的 AttachDetachController 組件和 PVController 組件以及 kubelet。下面分別詳細(xì)分析一下 Provision、Attach、Mount 三個(gè)階段。

Provision

[外鏈圖片轉(zhuǎn)存中…(img-wTvIpczo-1647940296774)]

先來(lái)看 Provision 階段,整個(gè)過(guò)程如上圖所示。其中 extenal-provisioner 和 PVController 均 watch PVC 資源。

  1. 當(dāng) PVController watch 到集群中有 PVC 創(chuàng)建時(shí),會(huì)判斷當(dāng)前是否有 in-tree plugin 與之相符,如果沒(méi)有則判斷其存儲(chǔ)類型為 out-of-tree 類型,于是給 PVC 打上注解 volume.beta.kubernetes.io/storage-provisioner={csi driver name};
  2. 當(dāng) extenal-provisioner watch 到 PVC 的注解 csi driver 與自己的 csi driver 一致時(shí),調(diào)用 CSI Controller 的 CreateVolume 接口;
  3. 當(dāng) CSI Controller 的 CreateVolume 接口返回成功時(shí),extenal-provisioner 會(huì)在集群中創(chuàng)建對(duì)應(yīng)的 PV;
  4. PVController watch 到集群中有 PV 創(chuàng)建時(shí),將 PV 與 PVC 進(jìn)行綁定。

Attach

CSI 工作原理與JuiceFS CSI Driver 的架構(gòu)設(shè)計(jì)詳解

Attach 階段是指將 volume 附著到節(jié)點(diǎn)上,整個(gè)過(guò)程如上圖所示。

  1. ADController 監(jiān)聽(tīng)到 pod 被調(diào)度到某節(jié)點(diǎn),并且使用的是 CSI 類型的 PV,會(huì)調(diào)用內(nèi)部的 in-tree CSI 插件的接口,該接口會(huì)在集群中創(chuàng)建一個(gè) VolumeAttachment 資源;
  2. external-attacher 組件 watch 到有 VolumeAttachment 資源創(chuàng)建出來(lái)時(shí),會(huì)調(diào)用 CSI Controller 的 ControllerPublishVolume 接口;
  3. 當(dāng) CSI Controller 的 ControllerPublishVolume 接口調(diào)用成功后,external-attacher 將對(duì)應(yīng)的 VolumeAttachment 對(duì)象的 Attached 狀態(tài)設(shè)為 true;
  4. ADController watch 到 VolumeAttachment 對(duì)象的 Attached 狀態(tài)為 true 時(shí),更新 ADController 內(nèi)部的狀態(tài) ActualStateOfWorld。

Mount

[外鏈圖片轉(zhuǎn)存中…(img-4Rk7q1CM-1647940296775)]

最后一步將 volume 掛載到 pod 里的過(guò)程涉及到 kubelet。整個(gè)流程簡(jiǎn)單地說(shuō)是,對(duì)應(yīng)節(jié)點(diǎn)上的 kubelet 在創(chuàng)建 pod 的過(guò)程中,會(huì)調(diào)用 CSI Node 插件,執(zhí)行 mount 操作。下面再針對(duì) kubelet 內(nèi)部的組件細(xì)分進(jìn)行分析。

首先 kubelet 創(chuàng)建 pod 的主函數(shù) syncPod 中,kubelet 會(huì)調(diào)用其子組件 volumeManager 的 WaitForAttachAndMount 方法,等待 volume mount 完成:

func (kl *Kubelet) syncPod(o syncPodOptions) error {
...
	// Volume manager will not mount volumes for terminated pods
	if !kl.podIsTerminated(pod) {
		// Wait for volumes to attach/mount
		if err := kl.volumeManager.WaitForAttachAndMount(pod); err != nil {
			kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedMountVolume, "Unable to attach or mount volumes: %v", err)
			klog.Errorf("Unable to attach or mount volumes for pod %q: %v; skipping pod", format.Pod(pod), err)
			return err
		}
	}
...
}

volumeManager 中包含兩個(gè)組件:desiredStateOfWorldPopulator 和 reconciler。這兩個(gè)組件相互配合就完成了 volume 在 pod 中的 mount 和 umount 過(guò)程。整個(gè)過(guò)程如下:

CSI 工作原理與JuiceFS CSI Driver 的架構(gòu)設(shè)計(jì)詳解

desiredStateOfWorldPopulator 和 reconciler 的協(xié)同模式是生產(chǎn)者和消費(fèi)者的模式。volumeManager 中維護(hù)了兩個(gè)隊(duì)列(嚴(yán)格來(lái)講是 interface,但這里充當(dāng)了隊(duì)列的作用),即 DesiredStateOfWorld 和 ActualStateOfWorld,前者維護(hù)的是當(dāng)前節(jié)點(diǎn)中 volume 的期望狀態(tài);后者維護(hù)的是當(dāng)前節(jié)點(diǎn)中 volume 的實(shí)際狀態(tài)。

而 desiredStateOfWorldPopulator 在自己的循環(huán)中只做了兩個(gè)事情,一個(gè)是從 kubelet 的 podManager 中獲取當(dāng)前節(jié)點(diǎn)新建的 Pod,將其需要掛載的 volume 信息記錄到 DesiredStateOfWorld 中;另一件事是從 podManager 中獲取當(dāng)前節(jié)點(diǎn)中被刪除的 pod,檢查其 volume 是否在 ActualStateOfWorld 的記錄中,如果沒(méi)有,將其在 DesiredStateOfWorld 中也刪除,從而保證 DesiredStateOfWorld 記錄的是節(jié)點(diǎn)中所有 volume 的期望狀態(tài)。相關(guān)代碼如下(為了精簡(jiǎn)邏輯,刪除了部分代碼):

// Iterate through all pods and add to desired state of world if they don't
// exist but should
func (dswp *desiredStateOfWorldPopulator) findAndAddNewPods() {
	// Map unique pod name to outer volume name to MountedVolume.
	mountedVolumesForPod := make(map[volumetypes.UniquePodName]map[string]cache.MountedVolume)
	...
	processedVolumesForFSResize := sets.NewString()
	for _, pod := range dswp.podManager.GetPods() {
		dswp.processPodVolumes(pod, mountedVolumesForPod, processedVolumesForFSResize)
	}
}

// processPodVolumes processes the volumes in the given pod and adds them to the
// desired state of the world.
func (dswp *desiredStateOfWorldPopulator) processPodVolumes(
	pod *v1.Pod,
	mountedVolumesForPod map[volumetypes.UniquePodName]map[string]cache.MountedVolume,
	processedVolumesForFSResize sets.String) {
	uniquePodName := util.GetUniquePodName(pod)
    ...
	for _, podVolume := range pod.Spec.Volumes {   
		pvc, volumeSpec, volumeGidValue, err :=
			dswp.createVolumeSpec(podVolume, pod, mounts, devices)

		// Add volume to desired state of world
		_, err = dswp.desiredStateOfWorld.AddPodToVolume(
			uniquePodName, pod, volumeSpec, podVolume.Name, volumeGidValue)
		dswp.actualStateOfWorld.MarkRemountRequired(uniquePodName)
    }
}

而 reconciler 就是消費(fèi)者,它主要做了三件事:

  1. unmountVolumes():在 ActualStateOfWorld 中遍歷 volume,判斷其是否在 DesiredStateOfWorld 中,如果不在,則調(diào)用 CSI Node 的接口執(zhí)行 unmount,并在 ActualStateOfWorld 中記錄;
  2. mountAttachVolumes():從 DesiredStateOfWorld 中獲取需要被 mount 的 volume,調(diào)用 CSI Node 的接口執(zhí)行 mount 或擴(kuò)容,并在 ActualStateOfWorld 中做記錄;
  3. unmountDetachDevices(): 在 ActualStateOfWorld 中遍歷 volume,若其已經(jīng) attach,但沒(méi)有使用的 pod,并在 DesiredStateOfWorld 也沒(méi)有記錄,則將其 unmount/detach 掉。

我們以 mountAttachVolumes() 為例,看看其如何調(diào)用 CSI Node 的接口。

func (rc *reconciler) mountAttachVolumes() {
	// Ensure volumes that should be attached/mounted are attached/mounted.
	for _, volumeToMount := range rc.desiredStateOfWorld.GetVolumesToMount() {
		volMounted, devicePath, err := rc.actualStateOfWorld.PodExistsInVolume(volumeToMount.PodName, volumeToMount.VolumeName)
		volumeToMount.DevicePath = devicePath
		if cache.IsVolumeNotAttachedError(err) {
			...
		} else if !volMounted || cache.IsRemountRequiredError(err) {
			// Volume is not mounted, or is already mounted, but requires remounting
			err := rc.operationExecutor.MountVolume(
				rc.waitForAttachTimeout,
				volumeToMount.VolumeToMount,
				rc.actualStateOfWorld,
				isRemount)
			...
		} else if cache.IsFSResizeRequiredError(err) {
			err := rc.operationExecutor.ExpandInUseVolume(
				volumeToMount.VolumeToMount,
				rc.actualStateOfWorld)
			...
		}
	}
}

執(zhí)行 mount 的操作全在 rc.operationExecutor 中完成,再看 operationExecutor 的代碼:

func (oe *operationExecutor) MountVolume(
	waitForAttachTimeout time.Duration,
	volumeToMount VolumeToMount,
	actualStateOfWorld ActualStateOfWorldMounterUpdater,
	isRemount bool) error {
	...
	var generatedOperations volumetypes.GeneratedOperations
		generatedOperations = oe.operationGenerator.GenerateMountVolumeFunc(
			waitForAttachTimeout, volumeToMount, actualStateOfWorld, isRemount)

	// Avoid executing mount/map from multiple pods referencing the
	// same volume in parallel
	podName := nestedpendingoperations.EmptyUniquePodName

	return oe.pendingOperations.Run(
		volumeToMount.VolumeName, podName, "" /* nodeName */, generatedOperations)
}

該函數(shù)先構(gòu)造執(zhí)行函數(shù),再執(zhí)行,那么再看構(gòu)造函數(shù):

func (og *operationGenerator) GenerateMountVolumeFunc(
	waitForAttachTimeout time.Duration,
	volumeToMount VolumeToMount,
	actualStateOfWorld ActualStateOfWorldMounterUpdater,
	isRemount bool) volumetypes.GeneratedOperations {

	volumePlugin, err :=
		og.volumePluginMgr.FindPluginBySpec(volumeToMount.VolumeSpec)

	mountVolumeFunc := func() volumetypes.OperationContext {
		// Get mounter plugin
		volumePlugin, err := og.volumePluginMgr.FindPluginBySpec(volumeToMount.VolumeSpec)
		volumeMounter, newMounterErr := volumePlugin.NewMounter(
			volumeToMount.VolumeSpec,
			volumeToMount.Pod,
			volume.VolumeOptions{})
		...
		// Execute mount
		mountErr := volumeMounter.SetUp(volume.MounterArgs{
			FsUser:              util.FsUserFrom(volumeToMount.Pod),
			FsGroup:             fsGroup,
			DesiredSize:         volumeToMount.DesiredSizeLimit,
			FSGroupChangePolicy: fsGroupChangePolicy,
		})
		// Update actual state of world
		markOpts := MarkVolumeOpts{
			PodName:             volumeToMount.PodName,
			PodUID:              volumeToMount.Pod.UID,
			VolumeName:          volumeToMount.VolumeName,
			Mounter:             volumeMounter,
			OuterVolumeSpecName: volumeToMount.OuterVolumeSpecName,
			VolumeGidVolume:     volumeToMount.VolumeGidValue,
			VolumeSpec:          volumeToMount.VolumeSpec,
			VolumeMountState:    VolumeMounted,
		}

		markVolMountedErr := actualStateOfWorld.MarkVolumeAsMounted(markOpts)
		...
		return volumetypes.NewOperationContext(nil, nil, migrated)
	}

	return volumetypes.GeneratedOperations{
		OperationName:     "volume_mount",
		OperationFunc:     mountVolumeFunc,
		EventRecorderFunc: eventRecorderFunc,
		CompleteFunc:      util.OperationCompleteHook(util.GetFullQualifiedPluginNameForVolume(volumePluginName, volumeToMount.VolumeSpec), "volume_mount"),
	}
}

這里先去注冊(cè)到 kubelet 的 CSI 的 plugin 列表中找到對(duì)應(yīng)的插件,然后再執(zhí)行 volumeMounter.SetUp,最后更新 ActualStateOfWorld 的記錄。這里負(fù)責(zé)執(zhí)行 external CSI 插件的是 csiMountMgr,代碼如下:

func (c *csiMountMgr) SetUp(mounterArgs volume.MounterArgs) error {
	return c.SetUpAt(c.GetPath(), mounterArgs)
}

func (c *csiMountMgr) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
	csi, err := c.csiClientGetter.Get()
	...

	err = csi.NodePublishVolume(
		ctx,
		volumeHandle,
		readOnly,
		deviceMountPath,
		dir,
		accessMode,
		publishContext,
		volAttribs,
		nodePublishSecrets,
		fsType,
		mountOptions,
	)
    ...
	return nil
}

可以看到,在 kubelet 中調(diào)用 CSI Node NodePublishVolume/NodeUnPublishVolume 接口的是 volumeManager 的 csiMountMgr。至此,整個(gè) Pod 的 volume 流程就已經(jīng)梳理清楚了。

JuiceFS CSI Driver 工作原理

接下來(lái)再來(lái)看看 JuiceFS CSI Driver 的工作原理。架構(gòu)圖如下:

CSI 工作原理與JuiceFS CSI Driver 的架構(gòu)設(shè)計(jì)詳解

JuiceFS 在 CSI Node 接口 NodePublishVolume 中創(chuàng)建 pod,用來(lái)執(zhí)行 juicefs mount xxx,從而保證 juicefs 客戶端運(yùn)行在 pod 里。如果有多個(gè)的業(yè)務(wù) pod 共用一份存儲(chǔ),mount pod 會(huì)在 annotation 進(jìn)行引用計(jì)數(shù),確保不會(huì)重復(fù)創(chuàng)建。具體的代碼如下(為了方便閱讀,省去了日志等無(wú)關(guān)代碼):

func (p *PodMount) JMount(jfsSetting *jfsConfig.JfsSetting) error {
	if err := p.createOrAddRef(jfsSetting); err != nil {
		return err
	}
	return p.waitUtilPodReady(GenerateNameByVolumeId(jfsSetting.VolumeId))
}

func (p *PodMount) createOrAddRef(jfsSetting *jfsConfig.JfsSetting) error {
	...
	
	for i := 0; i < 120; i++ {
		// wait for old pod deleted
		oldPod, err := p.K8sClient.GetPod(podName, jfsConfig.Namespace)
		if err == nil && oldPod.DeletionTimestamp != nil {
			time.Sleep(time.Millisecond * 500)
			continue
		} else if err != nil {
			if K8serrors.IsNotFound(err) {
				newPod := r.NewMountPod(podName)
				if newPod.Annotations == nil {
					newPod.Annotations = make(map[string]string)
				}
				newPod.Annotations[key] = jfsSetting.TargetPath
				po, err := p.K8sClient.CreatePod(newPod)
				...
				return err
			}
			return err
		}
      ...
		return p.AddRefOfMount(jfsSetting.TargetPath, podName)
	}
	return status.Errorf(codes.Internal, "Mount %v failed: mount pod %s has been deleting for 1 min", jfsSetting.VolumeId, podName)
}

func (p *PodMount) waitUtilPodReady(podName string) error {
	// Wait until the mount pod is ready
	for i := 0; i < 60; i++ {
		pod, err := p.K8sClient.GetPod(podName, jfsConfig.Namespace)
		...
		if util.IsPodReady(pod) {
			return nil
		}
		time.Sleep(time.Millisecond * 500)
	}
	...
	return status.Errorf(codes.Internal, "waitUtilPodReady: mount pod %s isn't ready in 30 seconds: %v", podName, log)
}

每當(dāng)有業(yè)務(wù) pod 退出時(shí),CSI Node 會(huì)在接口 NodeUnpublishVolume 刪除 mount pod annotation 中對(duì)應(yīng)的計(jì)數(shù),當(dāng)最后一個(gè)記錄被刪除時(shí),mount pod 才會(huì)被刪除。具體代碼如下(為了方便閱讀,省去了日志等無(wú)關(guān)代碼):

func (p *PodMount) JUmount(volumeId, target string) error {
   ...
	err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
		po, err := p.K8sClient.GetPod(pod.Name, pod.Namespace)
		if err != nil {
			return err
		}
		annotation := po.Annotations
		...
		delete(annotation, key)
		po.Annotations = annotation
		return p.K8sClient.UpdatePod(po)
	})
	...

	deleteMountPod := func(podName, namespace string) error {
		return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
			po, err := p.K8sClient.GetPod(podName, namespace)
			...
			shouldDelay, err = util.ShouldDelay(po, p.K8sClient)
			if err != nil {
				return err
			}
			if !shouldDelay {
				// do not set delay delete, delete it now
				if err := p.K8sClient.DeletePod(po); err != nil {
					return err
				}
			}
			return nil
		})
	}

	newPod, err := p.K8sClient.GetPod(pod.Name, pod.Namespace)
	...
	if HasRef(newPod) {
		return nil
	}
	return deleteMountPod(pod.Name, pod.Namespace)
}

CSI Driver 與 juicefs 客戶端解耦,做升級(jí)不會(huì)影響到業(yè)務(wù)容器;將客戶端獨(dú)立在 pod 中運(yùn)行也就使其在 K8s 的管控內(nèi),可觀測(cè)性更強(qiáng);同時(shí) pod 的好處我們也能享受到,比如隔離性更強(qiáng),可以單獨(dú)設(shè)置客戶端的資源配額等。

總結(jié)

本文從 CSI 的組件、CSI 接口、volume 如何掛載到 pod 上,三個(gè)方面入手,分析了 CSI 整個(gè)體系工作的過(guò)程,并介紹了 JuiceFS CSI Driver 的工作原理。CSI 是整個(gè)容器生態(tài)的標(biāo)準(zhǔn)存儲(chǔ)接口,CO 通過(guò) gRPC 方式和 CSI 插件通信,而為了做到普適,K8s 設(shè)計(jì)了很多外部組件來(lái)配合 CSI 插件來(lái)實(shí)現(xiàn)不同的功能,從而保證了 K8s 內(nèi)部邏輯的純粹以及 CSI 插件的簡(jiǎn)單易用。

如有幫助的話歡迎關(guān)注我們項(xiàng)目 Juicedata/JuiceFS 喲! (0?0?)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-487839.html

到了這里,關(guān)于CSI 工作原理與JuiceFS CSI Driver 的架構(gòu)設(shè)計(jì)詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 驅(qū)動(dòng) | Linux | NVMe | 1. NVMe Driver的前世今生和工作原理概述

    驅(qū)動(dòng) | Linux | NVMe | 1. NVMe Driver的前世今生和工作原理概述

    本文主要參考這里 1 ’ 2 的解析和 linux 源碼 3 。 此處推薦一個(gè)可以便捷查看 linux 源碼的網(wǎng)站 bootlin 4 。 更新:2022 / 02 / 19 NVMe 離不開(kāi) PCIe , NVMe SSD 是 PCIe 的 endpoint 。 PCIe 是 x86 平臺(tái)上一種流行的外設(shè)總線,由于其 Plug and Play 的特性,目前很多外設(shè)都通過(guò) PCI Bus 與 Host 通信,

    2024年02月16日
    瀏覽(50)
  • 長(zhǎng)文圖解:詳解金字塔原理如何應(yīng)用于架構(gòu)設(shè)計(jì)

    長(zhǎng)文圖解:詳解金字塔原理如何應(yīng)用于架構(gòu)設(shè)計(jì)

    大家想一想工作中有沒(méi)有遇到以下情況:一位同事用了很長(zhǎng)時(shí)間羅列了很多事實(shí)和數(shù)據(jù)向你說(shuō)明一件事情,但是你聽(tīng)完根本不知道他想要說(shuō)什么。一位同事用了大量筆墨編寫(xiě)了技術(shù)方案,不僅有文字還有圖表,但是你看完也不知道這個(gè)方案到底要解決什么問(wèn)題以及如何落地。

    2024年02月04日
    瀏覽(17)
  • 【中危】Kubernetes secrets-store-csi-driver 信息泄露漏洞

    【中?!縆ubernetes secrets-store-csi-driver 信息泄露漏洞

    Kubernetes secrets-store-csi-driver 是一個(gè)用于 Kubernetes 的 CSI 驅(qū)動(dòng)程序,它提供了一種將外部密鑰存儲(chǔ)系統(tǒng)中的憑據(jù)注入到 Kubernetes Pod 的機(jī)制。 在 secrets-store-csi-driver 受影響版本中,當(dāng)在 CSIDriver 對(duì)象中配置了 TokenRequests 并且將驅(qū)動(dòng)程序日志設(shè)置為級(jí)別 2 或更高的級(jí)別時(shí),會(huì)將服務(wù)

    2024年02月10日
    瀏覽(19)
  • 大白話說(shuō)說(shuō)Docker容器默認(rèn)網(wǎng)絡(luò)模型工作原理

    大白話說(shuō)說(shuō)Docker容器默認(rèn)網(wǎng)絡(luò)模型工作原理

    Docker的默認(rèn)網(wǎng)絡(luò)模型 —— 橋接模式(Bridge) 當(dāng)你不做任何特殊設(shè)置時(shí),Docker會(huì)使用一種叫做“橋接模式”的網(wǎng)絡(luò)設(shè)置。這就像是給你的容器小房子安裝了一個(gè)虛擬的橋接網(wǎng)絡(luò)。這座橋連接著容器和你的電腦(宿主機(jī)),還能與外界通信。 虛擬網(wǎng)絡(luò)橋 :想象一下,在你的電

    2024年02月21日
    瀏覽(17)
  • 2.Spark的工作與架構(gòu)原理

    2.Spark的工作與架構(gòu)原理

    目標(biāo): spark的工作原理 spark數(shù)據(jù)處理通用流程 rdd 什么是 rdd rdd 的特點(diǎn) spark架構(gòu) spark架構(gòu)相關(guān)進(jìn)程 spark架構(gòu)原理 spark 的工作原理,如下圖 圖中中間部分是 spark 集群,也可以是基于 yarn 的,圖上可以理解為 spark 的 standalone 集群,集群中有 6 個(gè)節(jié)點(diǎn) 左邊是 spark 的客戶端節(jié)點(diǎn),

    2024年02月06日
    瀏覽(24)
  • Yarn 集群的架構(gòu)和工作原理

    Yarn 的基本設(shè)計(jì)思想是將 MapReduce V1 中的 JobTracker 拆分為兩個(gè)獨(dú)立的服務(wù):ResourceManager 和 ApplicationMaster。 ResourceManager 負(fù)責(zé)整個(gè)系統(tǒng)的資源管理和分配,ApplicationMaster 負(fù)責(zé)單個(gè)應(yīng)用程序的管理。 ResourceManager RM 是一個(gè)全局的資源管理器,負(fù)責(zé)整個(gè)系統(tǒng)的資源管理和分配,它主要

    2024年02月16日
    瀏覽(17)
  • WLAN的組網(wǎng)架構(gòu)和工作原理

    WLAN的組網(wǎng)架構(gòu)和工作原理

    目錄 WLAN的組網(wǎng)架構(gòu) FAT AP架構(gòu) AC + FIT AP架構(gòu) 敏捷分布式AP 下一代園區(qū)網(wǎng)絡(luò):智簡(jiǎn)園區(qū)(大中型園區(qū)網(wǎng)絡(luò)) WLAN工作原理 WLAN工作流程 1.AP上線 (1)AP獲取IP地址; (2)AP發(fā)現(xiàn)AC并與之建立CAPWAP隧道; (3)AP接入控制; (4)AP版本升級(jí)(可選): (5)CAPWAP隧道維持: 2.WLAN業(yè)務(wù)

    2024年02月08日
    瀏覽(15)
  • Spring Security 的工作原理/總體架構(gòu)

    Spring Security 的工作原理/總體架構(gòu)

    目錄 1、過(guò)濾器的視角 2、DelegatingFilterProxy 委派過(guò)濾器代理(類) 2、FilterChainProxy 過(guò)濾器鏈代理(類) 4、SecurityFilterChain 安全過(guò)濾器鏈(接口) 5、Security Filters 安全過(guò)濾器實(shí)例 6、Spring Security 如何處理安全異常? 7、在認(rèn)證的時(shí)候保存用戶請(qǐng)求 ??????? ?// 釋義、解讀和思

    2024年02月16日
    瀏覽(21)
  • 深入理解 PostgreSQL 的架構(gòu)和內(nèi)部工作原理

    深入理解 PostgreSQL 的架構(gòu)和內(nèi)部工作原理

    ???? 博主 libin9iOak帶您 Go to New World.??? ?? 個(gè)人主頁(yè)——libin9iOak的博客?? ?? 《面試題大全》 文章圖文并茂??生動(dòng)形象??簡(jiǎn)單易學(xué)!歡迎大家來(lái)踩踩~?? ?? 《IDEA開(kāi)發(fā)秘籍》學(xué)會(huì)IDEA常用操作,工作效率翻倍~?? ???? 希望本文能夠給您帶來(lái)一定的幫助??文章粗淺,敬

    2024年02月16日
    瀏覽(20)
  • 【Python爬蟲(chóng)開(kāi)發(fā)基礎(chǔ)?】Scrapy架構(gòu)(組件介紹、架構(gòu)組成和工作原理)

    【Python爬蟲(chóng)開(kāi)發(fā)基礎(chǔ)?】Scrapy架構(gòu)(組件介紹、架構(gòu)組成和工作原理)

    ?? 個(gè)人主頁(yè) :為夢(mèng)而生~ 關(guān)注我一起學(xué)習(xí)吧! ?? 專欄 :python網(wǎng)絡(luò)爬蟲(chóng)從基礎(chǔ)到實(shí)戰(zhàn) 歡迎訂閱!后面的內(nèi)容會(huì)越來(lái)越有意思~ ?? 往期推薦 : ??前面比較重要的 基礎(chǔ)內(nèi)容 : 【Python爬蟲(chóng)開(kāi)發(fā)基礎(chǔ)⑧】XPath庫(kù)及其基本用法 【Python爬蟲(chóng)開(kāi)發(fā)基礎(chǔ)⑨】jsonpath和BeautifulSoup庫(kù)概述及

    2024年02月17日
    瀏覽(22)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包