主頁 >  其他 > CSI 作業原理與JuiceFS CSI Driver 的架構設計詳解

CSI 作業原理與JuiceFS CSI Driver 的架構設計詳解

2022-03-23 08:04:03 其他

容器存盤介面(Container Storage Interface)簡稱 CSI,CSI 建立了行業標準介面的規范,借助 CSI 容器編排系統(CO)可以將任意存盤系統暴露給自己的容器作業負載,JuiceFS CSI Driver 通過實作 CSI 介面使得 Kubernetes 上的應用可以通過 PVC(PersistentVolumeClaim)使用 JuiceFS,本文將詳細介紹 CSI 的作業原理以及 JuiceFS CSI Driver 的架構設計,

CSI 的基本組件

CSI 的 cloud providers 有兩種型別,一種為 in-tree 型別,一種為 out-of-tree 型別,前者是指運行在 K8s 核心組件內部的存盤插件;后者是指獨立在 K8s 組件之外運行的存盤插件,本文主要介紹 out-of-tree 型別的插件,

out-of-tree 型別的插件主要是通過 gRPC 介面跟 K8s 組件互動,并且 K8s 提供了大量的 SideCar 組件來配合 CSI 插件實作豐富的功能,對于 out-of-tree 型別的插件來說,所用到的組件分為 SideCar 組件和第三方需要實作的插件,

SideCar 組件

external-attacher

監聽 VolumeAttachment 物件,并呼叫 CSI driver Controller 服務的 ControllerPublishVolumeControllerUnpublishVolume 介面,用來將 volume 附著到 node 上,或從 node 上洗掉,

如果存盤系統需要 attach/detach 這一步,就需要使用到這個組件,因為 K8s 內部的 Attach/Detach Controller 不會直接呼叫 CSI driver 的介面,

external-provisioner

監聽 PVC 物件,并呼叫 CSI driver Controller 服務的 CreateVolumeDeleteVolume 介面,用來提供一個新的 volume,前提是 PVC 中指定的 StorageClass 的 provisioner 欄位和 CSI driver Identity 服務的 GetPluginInfo 介面的回傳值一樣,一旦新的 volume 提供出來,K8s 就會創建對應的 PV,

而如果 PVC 系結的 PV 的回收策略是 delete,那么 external-provisioner 組件監聽到 PVC 的洗掉后,會呼叫 CSI driver Controller 服務的 DeleteVolume 介面,一旦 volume 洗掉成功,該組件也會洗掉相應的 PV,

該組件還支持從快照創建資料源,如果在 PVC 中指定了 Snapshot CRD 的資料源,那么該組件會通過 SnapshotContent 物件獲取有關快照的資訊,并將此內容在呼叫 CreateVolume 介面的時候傳給 CSI driver,CSI driver 需要根據資料源快照來創建 volume,

external-resizer

監聽 PVC 物件,如果用戶請求在 PVC 物件上請求更多存盤,該組件會呼叫 CSI driver Controller 服務的 NodeExpandVolume 介面,用來對 volume 進行擴容,

external-snapshotter

該組件需要與 Snapshot Controller 配合使用,Snapshot Controller 會根據集群中創建的 Snapshot 物件創建對應的 VolumeSnapshotContent,而 external-snapshotter 負責監聽 VolumeSnapshotContent 物件,當監聽到 VolumeSnapshotContent 時,將其對應引數通過 CreateSnapshotRequest 傳給 CSI driver Controller 服務,呼叫其 CreateSnapshot 介面,該組件還負責呼叫 DeleteSnapshotListSnapshots 介面,

livenessprobe

負責監測 CSI driver 的健康情況,并通過 Liveness Probe 機制匯報給 K8s,當監測到 CSI driver 有例外時負責重啟 pod,

node-driver-registrar

通過直接呼叫 CSI driver Node 服務的 NodeGetInfo 介面,將 CSI driver 的資訊通過 kubelet 的插件注冊機制在對應節點的 kubelet 上進行注冊,

external-health-monitor-controller

通過呼叫 CSI driver Controller 服務的 ListVolumes 或者 ControllerGetVolume 介面,來檢查 CSI volume 的健康情況,并上報在 PVC 的 event 中,

external-health-monitor-agent

通過呼叫 CSI driver Node 服務的 NodeGetVolumeStats 介面,來檢查 CSI volume 的健康情況,并上報在 pod 的 event 中,

第三方插件

第三方存盤提供方(即 SP,Storage Provider)需要實作 Controller 和 Node 兩個插件,其中 Controller 負責 Volume 的管理,以 StatefulSet 形式部署;Node 負責將 Volume mount 到 pod 中,以 DaemonSet 形式部署在每個 node 中,

CSI 插件與 kubelet 以及 K8s 外部組件是通過 Unix Domani Socket gRPC 來進行互動呼叫的,CSI 定義了三套 RPC 介面,SP 需要實作這三組介面,以便與 K8s 外部組件進行通信,三組介面分別是:CSI Identity、CSI Controller 和 CSI Node,下面詳細看看這些介面定義,

CSI Identity

用于提供 CSI driver 的身份資訊,Controller 和 Node 都需要實作,介面如下:

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

  rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
    returns (GetPluginCapabilitiesResponse) {}

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

GetPluginInfo 是必須要實作的,node-driver-registrar 組件會呼叫這個介面將 CSI driver 注冊到 kubelet;GetPluginCapabilities 是用來表明該 CSI driver 主要提供了哪些功能,

CSI Controller

用于實作創建/洗掉 volume、attach/detach volume、volume 快照、volume 擴縮容等功能,Controller 插件需要實作這組介面,介面如下:

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 外部組件的時候已經提到,不同的介面分別提供給不同的組件呼叫,用于配合實作不同的功能,比如 CreateVolume/DeleteVolume 配合 external-provisioner 實作創建/洗掉 volume 的功能;ControllerPublishVolume/ControllerUnpublishVolume 配合 external-attacher 實作 volume 的 attach/detach 功能等,

CSI Node

用于實作 mount/umount volume、檢查 volume 狀態等功能,Node 插件需要實作這組介面,介面如下:

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 用來實作多個 pod 共享一個 volume 的功能,支持先將 volume 掛載到一個臨時目錄,然后通過 NodePublishVolume 將其掛載到 pod 中;NodeUnstageVolume 為其反操作,

作業流程

下面來看看 pod 掛載 volume 的整個作業流程,整個流程流程分別三個階段:Provision/Delete、Attach/Detach、Mount/Unmount,不過不是每個存盤方案都會經歷這三個階段,比如 NFS 就沒有 Attach/Detach 階段,

整個程序不僅僅涉及到上面介紹的組件的作業,還涉及 ControllerManager 的 AttachDetachController 組件和 PVController 組件以及 kubelet,下面分別詳細分析一下 Provision、Attach、Mount 三個階段,

Provision

先來看 Provision 階段,整個程序如上圖所示,其中 extenal-provisioner 和 PVController 均 watch PVC 資源,

  1. 當 PVController watch 到集群中有 PVC 創建時,會判斷當前是否有 in-tree plugin 與之相符,如果沒有則判斷其存盤型別為 out-of-tree 型別,于是給 PVC 打上注解 volume.beta.kubernetes.io/storage-provisioner={csi driver name}
  2. 當 extenal-provisioner watch 到 PVC 的注解 csi driver 與自己的 csi driver 一致時,呼叫 CSI Controller 的 CreateVolume 介面;
  3. 當 CSI Controller 的 CreateVolume 介面回傳成功時,extenal-provisioner 會在集群中創建對應的 PV;
  4. PVController watch 到集群中有 PV 創建時,將 PV 與 PVC 進行系結,

Attach

Attach 階段是指將 volume 附著到節點上,整個程序如上圖所示,

  1. ADController 監聽到 pod 被調度到某節點,并且使用的是 CSI 型別的 PV,會呼叫內部的 in-tree CSI 插件的介面,該介面會在集群中創建一個 VolumeAttachment 資源;
  2. external-attacher 組件 watch 到有 VolumeAttachment 資源創建出來時,會呼叫 CSI Controller 的 ControllerPublishVolume 介面;
  3. 當 CSI Controller 的 ControllerPublishVolume 介面呼叫成功后,external-attacher 將對應的 VolumeAttachment 物件的 Attached 狀態設為 true;
  4. ADController watch 到 VolumeAttachment 物件的 Attached 狀態為 true 時,更新 ADController 內部的狀態 ActualStateOfWorld,

Mount

最后一步將 volume 掛載到 pod 里的程序涉及到 kubelet,整個流程簡單地說是,對應節點上的 kubelet 在創建 pod 的程序中,會呼叫 CSI Node 插件,執行 mount 操作,下面再針對 kubelet 內部的組件細分進行分析,

首先 kubelet 創建 pod 的主函式 syncPod 中,kubelet 會呼叫其子組件 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 中包含兩個組件:desiredStateOfWorldPopulator 和 reconciler,這兩個組件相互配合就完成了 volume 在 pod 中的 mount 和 umount 程序,整個程序如下:

desiredStateOfWorldPopulator 和 reconciler 的協同模式是生產者和消費者的模式,volumeManager 中維護了兩個佇列(嚴格來講是 interface,但這里充當了佇列的作用),即 DesiredStateOfWorld 和 ActualStateOfWorld,前者維護的是當前節點中 volume 的期望狀態;后者維護的是當前節點中 volume 的實際狀態,

而 desiredStateOfWorldPopulator 在自己的回圈中只做了兩個事情,一個是從 kubelet 的 podManager 中獲取當前節點新建的 Pod,將其需要掛載的 volume 資訊記錄到 DesiredStateOfWorld 中;另一件事是從 podManager 中獲取當前節點中被洗掉的 pod,檢查其 volume 是否在 ActualStateOfWorld 的記錄中,如果沒有,將其在 DesiredStateOfWorld 中也洗掉,從而保證 DesiredStateOfWorld 記錄的是節點中所有 volume 的期望狀態,相關代碼如下(為了精簡邏輯,洗掉了部分代碼):

// 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 就是消費者,它主要做了三件事:

  1. unmountVolumes():在 ActualStateOfWorld 中遍歷 volume,判斷其是否在 DesiredStateOfWorld 中,如果不在,則呼叫 CSI Node 的介面執行 unmount,并在 ActualStateOfWorld 中記錄;
  2. mountAttachVolumes():從 DesiredStateOfWorld 中獲取需要被 mount 的 volume,呼叫 CSI Node 的介面執行 mount 或擴容,并在 ActualStateOfWorld 中做記錄;
  3. unmountDetachDevices(): 在 ActualStateOfWorld 中遍歷 volume,若其已經 attach,但沒有使用的 pod,并在 DesiredStateOfWorld 也沒有記錄,則將其 unmount/detach 掉,

我們以 mountAttachVolumes() 為例,看看其如何呼叫 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)
			...
		}
	}
}

執行 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)
}

該函式先構造執行函式,再執行,那么再看建構式:

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"),
	}
}

這里先去注冊到 kubelet 的 CSI 的 plugin 串列中找到對應的插件,然后再執行 volumeMounter.SetUp,最后更新 ActualStateOfWorld 的記錄,這里負責執行 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 中呼叫 CSI Node NodePublishVolume/NodeUnPublishVolume 介面的是 volumeManager 的 csiMountMgr,至此,整個 Pod 的 volume 流程就已經梳理清楚了,

JuiceFS CSI Driver 作業原理

接下來再來看看 JuiceFS CSI Driver 的作業原理,架構圖如下:

JuiceFS 在 CSI Node 介面 NodePublishVolume 中創建 pod,用來執行 juicefs mount xxx,從而保證 juicefs 客戶端運行在 pod 里,如果有多個的業務 pod 共用一份存盤,mount pod 會在 annotation 進行參考計數,確保不會重復創建,具體的代碼如下(為了方便閱讀,省去了日志等無關代碼):

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)
}

每當有業務 pod 退出時,CSI Node 會在介面 NodeUnpublishVolume 洗掉 mount pod annotation 中對應的計數,當最后一個記錄被洗掉時,mount pod 才會被洗掉,具體代碼如下(為了方便閱讀,省去了日志等無關代碼):

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 客戶端解耦,做升級不會影響到業務容器;將客戶端獨立在 pod 中運行也就使其在 K8s 的管控內,可觀測性更強;同時 pod 的好處我們也能享受到,比如隔離性更強,可以單獨設定客戶端的資源配額等,

總結

本文從 CSI 的組件、CSI 介面、volume 如何掛載到 pod 上,三個方面入手,分析了 CSI 整個體系作業的程序,并介紹了 JuiceFS CSI Driver 的作業原理,CSI 是整個容器生態的標準存盤介面,CO 通過 gRPC 方式和 CSI 插件通信,而為了做到普適,K8s 設計了很多外部組件來配合 CSI 插件來實作不同的功能,從而保證了 K8s 內部邏輯的純粹以及 CSI 插件的簡單易用,

如有幫助的話歡迎關注我們專案 Juicedata/JuiceFS 喲! (0?0?)

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/447064.html

標籤:其他

上一篇:華為云UGO正式亮相DTCC 2021,去“O”從此再無后顧之憂

下一篇:DTCC 2021 | 華為云資料庫戰略啟示錄

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more