JuiceFSCSIドライバーのCSI動作原理とアーキテクチャ設計の詳細な説明

Container Storage Interface(CSI)はCSIと略されます。CSIは業界標準のインターフェース仕様を確立します。CSIContainerOrchestration System(CO)の助けを借りて、任意のストレージシステムを独自のコンテナワークロードにさらすことができます。JuiceFS CSIドライバーを使用すると、Kubernetes上のアプリケーションでCSIインターフェイスを実装することにより、PVC(PersistentVolumeClaim)を介してJuiceFSを使用できます。この記事では、CSIの動作原理とJuiceFSCSIドライバーのアーキテクチャ設計について詳しく説明します。

CSIの基本コンポーネント

CSIには2つのタイプのクラウドプロバイダーがあります。1つはツリー内タイプで、もう1つはツリー外タイプです。前者はK8sコアコンポーネント内で実行されるストレージプラグインを指し、後者はK8sコンポーネントとは独立して実行されるストレージプラグインを指します。この記事では、主にツリー外タイプのプラグインを紹介します。

ツリー外タイプのプラグインは、主にgRPCインターフェースを介してK8sコンポーネントと対話し、K8sは、CSIプラグインと連携して豊富な機能を実現するための多数のSideCarコンポーネントを提供します。ツリー外タイプのプラグインの場合、使用されるコンポーネントは、サードパーティによる実装が必要なSideCarコンポーネントとプラグインに分けられます。

SideCarコンポーネント

外部接続

VolumeAttachmentオブジェクトをモニターし、CSIドライバーControllerサービスのControllerPublishVolumeおよびControllerUnpublishVolumeインターフェイスを呼び出して、ボリュームをノードに接続するか、ノードから削除します。

ストレージシステムにアタッチ/デタッチ手順が必要な場合は、このコンポーネントを使用する必要があります。これは、K8s内のアタッチ/デタッチコントローラーがCSIドライバーのインターフェイスを直接呼び出さないためです。

外部手数料

PVCオブジェクトを監視し、CSIドライバーコントローラーサービスのCreateVolumeおよびDeleteVolumeインターフェイスを呼び出して、新しいボリュームを提供します。PVCで指定されたStorageClassのプロビジョナーフィールドが、CSIドライバーIDサービスのGetPluginInfoインターフェイス。新しいボリュームが提供されると、K8sは対応するPVを作成します。

PVCにバインドされたPVのリサイクルポリシーが削除された場合、外部プロビジョナコンポーネントは、PVCの削除を監視した後、CSIドライバコントローラサービスのDeleteVolumeインターフェイス。ボリュームが正常に削除されると、コンポーネントは対応するPVも削除します。

このコンポーネントは、スナップショットからのデータソースの作成もサポートしています。スナップショットCRDのデータソースがPVCで指定されている場合、コンポーネントはSnapshotContentオブジェクトCreateVolume、インターフェイスを呼び出すときにこのコンテンツをCSIドライバーに渡します。CSIドライバーは、に基づいてボリュームを作成する必要があります。データソースのスナップショット。

外部レジスタ

PVCオブジェクトを監視します。ユーザーがPVCオブジェクトにさらにストレージを要求すると、このコンポーネントはCSIドライバーコントローラーサービスのNodeExpandVolumeインターフェイスボリュームを拡張します。

外部スナップショット

このコンポーネントは、スナップショットコントローラと連携するために必要です。スナップショットコントローラーは、クラスターで作成されたスナップショットオブジェクトに従って対応するVolumeSnapshotContentを作成し、external-snapshotterはVolumeSnapshotContentオブジェクトの監視を担当します。VolumeSnapshotContentが監視されると、対応するパラメーターがCSIドライバーコントローラーサービスにCreateSnapshotRequest渡されそのCreateSnapshotインターフェイスが呼び出されます。DeleteSnapshotコンポーネントは、ListSnapshotsインターフェイスの呼び出しも担当します。

livenessprobe

CSIドライバーの状態を監視し、Liveness Probeメカニズムを介してK8sに報告し、CSIドライバーの異常が検出されたときにポッドを再起動する役割を果たします。

node-driver-registrar

CSIドライバーノードサービスのNodeGetInfoインターフェースの情報は、kubeletのプラグイン登録メカニズムを介して、対応するノードのkubeletに登録されます。

external-health-monitor-controller

CSIドライバーコントローラーサービスListVolumesまたはCSIボリュームの状態を確認し、PVCの場合は報告します。ControllerGetVolume

external-health-monitor-agent

CSIドライバーノードサービスのNodeGetVolumeStatsインターフェイスCSIボリュームの状態を確認し、ポッドの場合に報告します。

サードパーティのプラグイン

サードパーティのストレージプロバイダー(つまり、SP、ストレージプロバイダー)は、コントローラーとノードの2つのプラグインを実装する必要があります。コントローラーはボリューム管理を担当し、StatefulSetの形式でデプロイされます。ノードは、ボリュームをにマウントする責任があります。ポッドを作成し、DaemonSetミドルの形式で各ノードにデプロイします。

CSIプラグイン、kubelet、およびK8sの外部コンポーネントは、Unix DomaniSocketgRPCを介してインタラクティブに呼び出されます。CSIは、SPがK8s外部コンポーネントと通信するために実装する必要があるRPCインターフェイスの3つのセットを定義します。インターフェイスの3つのグループは、CSI Identity、CSI Controller、およびCSINodeです。これらのインターフェイス定義を詳しく見ていきましょう。

CSIアイデンティティ

CSIドライバーのID情報を提供するには、コントローラーとノードの両方を実装する必要があります。インターフェイスは次のとおりです。

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

  rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
    returns (GetPluginCapabilitiesResponse) {}

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

GetPluginInfonode-driver-registrarコンポーネントは、このインターフェイスを呼び出してCSIドライバーをkubeletに登録します。GetPluginCapabilitiesこれは、CSIドライバーが主に提供する機能を示すために使用されます。

CSIコントローラー

ボリュームの作成/削除、ボリュームのアタッチ/デタッチ、ボリュームスナップショット、ボリュームスケーリングなどの機能を実装するために使用されます。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;
    }
}

前述のように、K8の外部コンポーネントを導入する場合、さまざまなコンポーネント呼び出しに対してさまざまなインターフェイスが提供され、さまざまな機能を実現するために連携します。例えば、CreateVolume/外部プロビジョナーと連携してボリュームの作成/DeleteVolume削除機能を実現します。/外部アタッチメントと連携してボリュームのアタッチ/デタッチ機能を実現します。ControllerPublishVolumeControllerUnpublishVolume

CSIノード

ボリュームのマウント/アンマウントやボリュームステータスの確認などの機能を実装するために使用されます。ノードプラグインは、この一連のインターフェイスを実装する必要があります。インターフェイスは次のとおりです。

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複数のポッドがボリュームを共有する機能を実現するために使用されます。最初に一時ディレクトリにボリュームNodePublishVolumeを。NodeUnstageVolumeこれは逆の操作です。

作業過程

ポッドマウントボリュームのワークフロー全体を見てみましょう。プロセスフロー全体は、プロビジョニング/削除、アタッチ/デタッチ、マウント/アンマウントの3つのステージに分かれていますが、すべてのストレージソリューションがこれらの3つのステージを通過するわけではありません。たとえば、NFSにはアタッチ/デタッチステージがありません。

プロセス全体には、上記で紹介したコンポーネントの作業だけでなく、ControllerManagerとkubeletのAttachDetachControllerコンポーネントとPVControllerコンポーネントも含まれます。プロビジョニング、アタッチ、マウントの3つの段階について、以下で詳しく分析します。

規定

最初にプロビジョニング段階を見てみましょう。プロセス全体が上の図に示されています。その中で、extenal-provisionerとPVControllerの両方が監視PVCリソースです。

  1. PVControllerは、クラスター内にPVCが作成されていることを確認すると、それに一致するツリー内プラグインがあるかどうかを判断します。存在しない場合は、ストレージタイプがツリー外タイプであると判断します。 PVCに注釈を付けvolume.beta.kubernetes.io/storage-provisioner={csi driver name}ます;
  2. PVCのアノテーションcsiドライバーに対する外部プロビジョナーウォッチがそれ自体のcsiドライバーと一致している場合は、CSIコントローラーのCreateVolumeインターフェイス。
  3. CSIコントローラのCreateVolumeインターフェイスが正常に戻ると、外部プロビジョナは対応するPVをクラスタに作成します。
  4. PVControllerは、クラスター内にPVを作成することを監視するときに、PVをPVCにバインドします。

添付

アタッチステージとは、ボリュームをノードにアタッチすることです。プロセス全体を上の図に示します。

  1. ADControllerは、ポッドがノードにスケジュールされ、CSIタイプのPVを使用することを監視し、内部ツリー内CSIプラグインのインターフェイスを呼び出します。これにより、クラスター内にVolumeAttachmentリソースが作成されます。
  2. VolumeAttachmentリソースが作成されると、外部アタッチメントコンポーネントウォッチはCSIコントローラーのControllerPublishVolumeインターフェイス。
  3. CSIコントローラーのControllerPublishVolumeインターフェース正常に呼び出されると、external-attacherは、対応するVolumeAttachmentオブジェクトのAttached状態をtrueに設定します。
  4. ADControllerがVolumeAttachmentオブジェクトのAttached状態を監視している場合、ADController内の状態ActualStateOfWorldを更新します。

マウント

ボリュームをポッドに取り付ける最後のステップには、kubeletが含まれます。プロセス全体は、対応するノードのkubeletがポッドを作成するときに、CSIノードプラグインを呼び出してマウント操作を実行するというものです。次に、kubelet内のコンポーネントセグメンテーションを分析します。

まず、ポッドを作成するkubeletのメイン関数syncPodで、kubeletWaitForAttachAndMountはそのサブコンポーネントvolumeManagerのメソッドを呼び出し、ボリュームのマウントが完了するのを待ちます。

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の2つのコンポーネントが含まれています。これらの2つのコンポーネントは互いに連携して、ポッド内のボリュームのマウントおよびアンマウントプロセスを完了します。全体のプロセスは次のとおりです。

desiredStateOfWorldPopulatorとreconcilerのコラボレーションパターンは、プロデューサーとコンシューマーのパターンです。2つのキュー(厳密に言えば、インターフェイスですが、ここではキューとして機能します)、つまり、DesiredStateOfWorldとActualStateOfWorldの2つのキューが維持されます。前者は現在のノードのボリュームの期待される状態を維持し、後者はボリュームを維持します。現在のノード。実際の状態。

DesiredStateOfWorldPopulatorは、独自のループで2つのことのみを実行します。1つは、kubeletのpodManagerから現在のノードの新しく作成されたポッドを取得し、DesiredStateOfWorldにマウントされるボリューム情報を記録することです。もう1つは、podManagerから取得することです。 。現在のノードで削除されたポッドについて、そのボリュームがActualStateOfWorldのレコードにあるかどうかを確認します。ない場合は、DesiredStateOfWorldで削除して、DesiredStateOfWorldがノード内のすべてのボリュームの予想される状態を記録するようにします。関連するコードは次のとおりです(ロジックを簡略化するために、一部のコードが削除されています)。

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

調停者は消費者であり、主に次の3つのことを行います。

  1. unmountVolumes():ActualStateOfWorldのボリュームをトラバースして、DesiredStateOfWorldにあるかどうかを判断します。そうでない場合は、CSIノードのインターフェイスを呼び出してアンマウントを実行し、ActualStateOfWorldに記録します。
  2. mountAttachVolumes():マウントするボリュームをDesiredStateOfWorldから取得し、CSIノードのインターフェースを呼び出してマウントまたは拡張し、ActualStateOfWorldに記録します。
  3. unmountDetachDevices():ActualStateOfWorldでボリュームをトラバースします。ボリュームが接続されているが、使用中のポッドがなく、DesiredStateOfWorldにレコードがない場合は、ボリュームをアンマウント/デタッチします。

mountAttachVolumes()例として、 CSIノードのインターフェースを呼び出す方法を見てみましょう。

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

マウントを実行する操作はすべてで実行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プラグインリストに移動して、対応するプラグインを見つけ、それを実行volumeMounter.SetUpして、最後にActualStateOfWorldレコードを更新します。外部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
}

ご覧のとおり、volumeManagerのcsiMountMgrは、kubeletのCSIノードNodePublishVolume/NodeUnPublishVolumeインターフェースを呼び出しています。これまでのところ、ポッド全体のボリュームプロセスは整理されています。

JuiceFSCSIドライバーのしくみ

次に、JuiceFSCSIドライバーがどのように機能するかを見てみましょう。アーキテクチャ図は次のとおりです。

JuiceFSはNodePublishVolume、実行juicefs mount xxxのためにCSIノードインターフェイスにポッドを作成します。これにより、juicefsクライアントがポッド内で実行されるようになります。ストレージを共有する複数のビジネスポッドがある場合、マウントポッドは注釈で参照カウントされ、繰り返し作成されないようにします。具体的なコードは次のとおりです(読みやすくするために、ログやその他の無関係なコードは省略されています)。

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

サービスポッドが終了するたびに、CSIノードはインターフェイスのマウントポッドアノテーションの対応するカウントをNodeUnpublishVolume削除マウントポッドは最後のレコードが削除された場合にのみ削除されます。具体的なコードは次のとおりです(読みやすくするために、ログやその他の無関係なコードは省略されています)。

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ドライバーはjuicefsクライアントから切り離されており、アップグレードはビジネスコンテナーに影響を与えません。ポッド内でクライアントを個別に実行すると、K8の制御下でクライアントをより観察しやすくなります。同時に、 podたとえば、分離はより強力であり、クライアントのリソースクォータを個別に設定できます。

要約する

この記事は、CSIコンポーネント、CSIインターフェイス、およびボリュームがポッドにマウントされる方法の3つの側面から始まり、CSIシステム全体の動作プロセスを分析し、JuiceFSCSIドライバーの動作原理を紹介します。CSIは、コンテナエコシステム全体の標準ストレージインターフェースです。COはgRPCを介してCSIプラグインと通信します。ユニバーサルにするために、K8sはCSIプラグインと連携してさまざまな機能を実現する多くの外部コンポーネントを設計し、純度を確保しています。 K8sの内部ロジックとCSIプラグインの使いやすさ。

それが役に立ったら、私たちのプロジェクトJuicedata / JuiceFSに従ってください!(0ᴗ0✿)

{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/5389802/blog/5495854