詳細なk8s:Kubernetes永続ボリュームPV、PVC、およびソースコード分析

推奨読書:

例のPVとPVCから始めます

Kubernetesプロジェクトでは、永続ボリューム要求(PVC)および永続ボリューム(PV)と呼ばれるAPIオブジェクトのセットを導入して、ストレージボリュームを管理しました。

例を見てみましょう。この例は「k8s in Action」からのものです。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc
spec:
  resources:
    requests:
      storage: 1Gi
  accessModes:
  - ReadWriteOnce
  storageClassName: ""

yamlファイルで定義されているストレージは1 GiBで、PVCに必要な容量を示します。

アクセスモードは必要なボリュームストレージのタイプを示し、ReadWriteOnceは読み取りおよび書き込み操作が1つのノードでのみ実行できることを示します。

storageClassNameは空です。これはstorageClassの名前を意味します。これについては後で説明します。

次に、PVCのステータスを取得します。

$ kubectl get pvc
NAME                   STATUS   VOLUME              CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mongodb-pvc            Available    mongodb-pv          1Gi        RWO,ROX                       2m25s

この時点で、PVCが使用可能な状態にあることがわかります。

次にPVを定義します。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mongodb-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  - ReadOnlyMany
  persistentVolumeReclaimPolicy: Retain
  gcePersistentDisk:
    pdName: mongodb
    fsType: ext4

このPVオブジェクトは、ストレージタイプがGCEでサイズが1 GiBであることを詳細に定義します。storageClassNameはデフォルトで空であるため、ここではstorageClassNameは宣言されていません。

次に、PVとPVCのステータスを確認します。

$ kubectl get pv
NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                          STORAGECLASS   REASON   AGE
mongodb-pv          1Gi        RWO,ROX        Retain           Bound       default/mongodb-pvc                                    77m

$ kubectl get pvc
NAME                   STATUS   VOLUME              CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mongodb-pvc            Bound    mongodb-pv          1Gi        RWO,ROX                       7m7s

PVとPVCが互いにバインドされていることがわかります。

PVCとPVは「インターフェース」と「実装」に相当するため、使用する前にPVCとPVをバインドする必要があります。PVCとPVをバインドするときは、次の条件を満たす必要があります。

  1. PVのストレージサイズなど、PVとPVCのスペックフィールドを一致させるには、PVCの要件を満たす必要があります。
  2. PVとPVCのstorageClassNameフィールドは、バインディングで同じである必要があります。storageClassNameは、StorageClassの名前属性を表します。

このPVCをポッドで使用する場合は、次のようにします。

apiVersion: v1
kind: Pod
metadata:
  name: mongodb
spec:
  containers:
  - image: mongo
    name: mongodb
    volumeMounts:
    - name: mongodb-data
      mountPath: /data/db
    ports:
    - containerPort: 27017
      protocol: TCP
  volumes:
  - name: mongodb-data
    persistentVolumeClaim:
      claimName: mongodb-pvc

ポッドでPVCの名前を宣言するだけでよく、ポッドが作成されると、kubeletはこのPVCに対応するPV(GCEタイプのボリューム)をポッドコンテナのディレクトリにマウントします。

PersistentVolumeControllerは、現在の各PVCがBound状態にあるかどうかを継続的にチェックします。そうでない場合、すべての利用可能なPVをトラバースし、それらをこの「単一の」PVCにバインドしようとします。このPersistentVolumeControllerのソースコードを以下で分析します。それで問題は、k8sがストレージボリュームを2つの部分に分割するのはなぜですか?

実際、私たちのプロジェクトでは、R&D担当者とクラスタ管理担当者が分かれています。R&D担当者はそれを使用するだけで、最下層でどのストレージテクノロジーが使用されているかは気にしません。したがって、R&D担当者は必要な量を示すためにPVCを宣言するだけで済みます。ストレージ、および読み書きタイプが行います。

StorageClassの動的プロビジョニング

上記のPVおよびPVCバインディングのプロセスは静的プロビジョニングと呼ばれ、手動でPVを作成する必要があります。私たちの研究開発では、このような状況が発生する可能性があります。 ?そのため、現時点ではStorageClassが必要であり、StorageClassは、テンプレートに基づいてPVを作成するための動的プロビジョニングメカニズムを提供します。

StorageClassオブジェクトは、次の2つの部分を定義します。

  1. PVの属性。たとえば、ストレージタイプ、ボリュームサイズなど。
  2. この種類のPVを作成するために必要なストレージプラグイン。たとえば、Cephなどです。

このようにして、k8sはユーザーが送信したPVCに従って対応するStorageClassを見つけ、StorageClassによって宣言されたストレージプラグインを呼び出して、必要なPVを作成できます。

たとえば、次のStorageClassを宣言します。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: block-service
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd

ここで、block-serviceという名前のStorageClassが定義されています。provisionerフィールドの値は、kubernetes.io / gce-pdです。これは、k8sの組み込みストレージプラグインです。typeフィールドは、provisionerでも定義されています。公式のデフォルトでは、Dynamic Provisioningの組み込みストレージプラグインがサポートされています

次に、PVCでstorageClassNameをblock-serviceとして宣言できます。PVCオブジェクトが作成された後、k8sは対応するストレージプラグインAPIを呼び出してPVオブジェクトを作成します。

次のように:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: claim1
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: block-service
  resources:
    requests:
      storage: 30Gi

この自動PV作成メカニズムは動的プロビジョニングです。Kubernetesは、ユーザーが送信したPVCに基づいて対応するStorageClassを見つけ、StorageClassによって宣言されたストレージプラグインを呼び出して、必要なPVを作成できます。

StorageClassNameがPVCで宣言されていない場合、PVCのstorageClassNameの値は「」です。これは、storageClassNameも「」であるPVにのみバインドできることも意味します。

PVとPVCのライフサイクル

PVとPVC間の相互作用は、このライフサイクルに従います。

プロビジョニング—>バインド—>使用—>再利用

プロビジョニング

k8sは、静的または動的の2つのPV生成方法を提供します

静的:PVは管理者によって作成され、クラスターユーザーが使用できる実ストレージに関する詳細情報を伝達します。これらはKubernetes APIに存在し、消費に使用できます。

動的:管理者が作成した静的PVがユーザーのPersistentVolumeClaimと一致しない場合、クラスターはPVCのボリュームを動的に構成しようとする場合があります。この構成はStorageClassesに基づいています。PVCはStorageClassesを要求する必要があり、管理者は動的に構成されるクラスを作成して構成する必要があります。

バインド

ユーザーがPersistentVolumeClaimを作成した後、PersistentVolumeControllerは、現在の各PVCが既にBound状態にあるかどうかを継続的にチェックします。そうでない場合、すべての利用可能なPVをトラバースし、それらをこの「単一の」PVCにバインドしようとします。

使用する

ポッドがPVCを宣言してボリュームとして使用すると、クラスターはPVCを検出します。PVCがすでにPVにバインドされている場合は、ボリュームがポッドにマウントされます。

埋め立て

ユーザーがボリュームを使用しなくなったら、PVCを削除して、リソースをリサイクルできます。これに対応して、PVCが削除された後、PVリサイクル戦略を保持、リサイクル、または削除できます。この戦略は、spec.persistentVolumeReclaimPolicyフィールドで設定できます。

  • 保持:このポリシーでは、リソースを手動で回復できます。PVCが削除されても、PVは引き続き存在できます。管理者はPVを手動で削除でき、PVにバインドされているストレージリソースは削除されません。対応するストレージを削除する場合リソースデータは、ストレージリソースに対応するデータを手動で削除する必要があります。
  • 削除:この戦略は、PVCが削除された後にPVによって管理されているPVおよびストレージリソースを削除します。
  • リサイクル:これは、ボリュームでrm -rf / thevolume / *コマンドを実行して、ボリュームを再利用できるようにするのと同じです。

通常の状況では、次の削除プロセスに従います。

  1. このPVを使用してポッドを削除します。
  2. ホストからローカルディスクを削除します(たとえば、umountします)。
  3. PVCを削除します。
  4. PVを削除します。

ソースコード分析

PVとPVCのソースコード処理ロジックは、2つのファイルpv_controller_base.goとpv_controller.goにあります。コアコードを直接見てみましょう。

まず、PersistentVolumeControllerのRunメソッドを見てみましょう。これがエントリポイントです。

func (ctrl *PersistentVolumeController) Run(stopCh <-chan struct{}) {
    ... 
    go wait.Until(ctrl.resync, ctrl.resyncPeriod, stopCh)
    go wait.Until(ctrl.volumeWorker, time.Second, stopCh)
    go wait.Until(ctrl.claimWorker, time.Second, stopCh)
    ...
}

このコードは主に、異なるメソッドを実行する3つのGoroutineで構成されています。resyncメソッドは非常に単純です。主な機能は、PVとPVCのリストを見つけて、volumeWorkerとClaimWorkerのために消費するために、volumeQueueとClaimQueueのキューに入れることです。以下では、主にvolumeWorkerとClaimWorkerを見ていきます。

volumeWorker

volumeWorkerは、volumeQueueキュー内のデータを継続的に消費し、次に対応するPVを取得してupdateVolumeオペレーションを実行します。

func (ctrl *PersistentVolumeController) updateVolume(volume *v1.PersistentVolume) {
    // Store the new volume version in the cache and do not process it if this
    // is an old version.
    //更新缓存
    new, err := ctrl.storeVolumeUpdate(volume)
    if err != nil {
        klog.Errorf("%v", err)
    }
    if !new {
        return
    }
    //核心方法,根据当前 PV 对象的规格对 PV 和 PVC 进行绑定或者解绑
    err = ctrl.syncVolume(volume)
    if err != nil {
        if errors.IsConflict(err) {
            // Version conflict error happens quite often and the controller
            // recovers from it easily.
            klog.V(3).Infof("could not sync volume %q: %+v", volume.Name, err)
        } else {
            klog.Errorf("could not sync volume %q: %+v", volume.Name, err)
        }
    }
}

updateVolumeメソッドは、syncVolumeメソッドを呼び出してコアプロセスを実行します。

私たちは続けます:

func (ctrl *PersistentVolumeController) syncVolume(volume *v1.PersistentVolume) error {
    klog.V(4).Infof("synchronizing PersistentVolume[%s]: %s", volume.Name, getVolumeStatusForLogging(volume)) 
    ...
    //如果spec.claimRef未设置,则是未使用过的pv,则调用updateVolumePhase函数更新状态设置 phase 为 available
    if volume.Spec.ClaimRef == nil { 
        klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is unused", volume.Name)
        if _, err := ctrl.updateVolumePhase(volume, v1.VolumeAvailable, ""); err != nil { 
            return err
        }
        return nil
    } else /* pv.Spec.ClaimRef != nil */ { 
        //正在被bound中,更新状态available
        if volume.Spec.ClaimRef.UID == "" { 
            klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is pre-bound to claim %s", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
            if _, err := ctrl.updateVolumePhase(volume, v1.VolumeAvailable, ""); err != nil { 
                return err
            }
            return nil
        }
        klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is bound to claim %s", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
        // Get the PVC by _name_
        var claim *v1.PersistentVolumeClaim
        //根据 pv 的 claimRef 获得 pvc
        claimName := claimrefToClaimKey(volume.Spec.ClaimRef)
        obj, found, err := ctrl.claims.GetByKey(claimName)
        if err != nil {
            return err
        }
        //如果在队列未发现,可能是volume被删除了,或者失败了,重新同步pvc
        if !found && metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnBoundByController) { 
            if volume.Status.Phase != v1.VolumeReleased && volume.Status.Phase != v1.VolumeFailed {
                obj, err = ctrl.claimLister.PersistentVolumeClaims(volume.Spec.ClaimRef.Namespace).Get(volume.Spec.ClaimRef.Name)
                if err != nil && !apierrors.IsNotFound(err) {
                    return err
                }
                found = !apierrors.IsNotFound(err)
                if !found {
                    obj, err = ctrl.kubeClient.CoreV1().PersistentVolumeClaims(volume.Spec.ClaimRef.Namespace).Get(context.TODO(), volume.Spec.ClaimRef.Name, metav1.GetOptions{})
                    if err != nil && !apierrors.IsNotFound(err) {
                        return err
                    }
                    found = !apierrors.IsNotFound(err)
                }
            }
        }
        if !found {
            klog.V(4).Infof("synchronizing PersistentVolume[%s]: claim %s not found", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef)) 
        } else {
            var ok bool
            claim, ok = obj.(*v1.PersistentVolumeClaim)
            if !ok {
                return fmt.Errorf("Cannot convert object from volume cache to volume %q!?: %#v", claim.Spec.VolumeName, obj)
            }
            klog.V(4).Infof("synchronizing PersistentVolume[%s]: claim %s found: %s", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef), getClaimStatusForLogging(claim))
        }
        if claim != nil && claim.UID != volume.Spec.ClaimRef.UID { 
            klog.V(4).Infof("synchronizing PersistentVolume[%s]: claim %s has different UID, the old one must have been deleted", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
            // Treat the volume as bound to a missing claim.
            claim = nil
        }
        //claim可能被删除了,或者pv被删除了
        if claim == nil { 
            if volume.Status.Phase != v1.VolumeReleased && volume.Status.Phase != v1.VolumeFailed {
                // Also, log this only once:
                klog.V(2).Infof("volume %q is released and reclaim policy %q will be executed", volume.Name, volume.Spec.PersistentVolumeReclaimPolicy)
                if volume, err = ctrl.updateVolumePhase(volume, v1.VolumeReleased, ""); err != nil { 
                    return err
                }
            }
            //根据persistentVolumeReclaimPolicy配置做相应的处理,Retain 保留/ Delete 删除/ Recycle 回收
            if err = ctrl.reclaimVolume(volume); err != nil { 
                return err
            }
            if volume.Spec.PersistentVolumeReclaimPolicy == v1.PersistentVolumeReclaimRetain {
                // volume is being retained, it references a claim that does not exist now.
                klog.V(4).Infof("PersistentVolume[%s] references a claim %q (%s) that is not found", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef), volume.Spec.ClaimRef.UID)
            }
            return nil
        } else if claim.Spec.VolumeName == "" {
            if pvutil.CheckVolumeModeMismatches(&claim.Spec, &volume.Spec) { 
                volumeMsg := fmt.Sprintf("Cannot bind PersistentVolume to requested PersistentVolumeClaim %q due to incompatible volumeMode.", claim.Name)
                ctrl.eventRecorder.Event(volume, v1.EventTypeWarning, events.VolumeMismatch, volumeMsg)
                claimMsg := fmt.Sprintf("Cannot bind PersistentVolume %q to requested PersistentVolumeClaim due to incompatible volumeMode.", volume.Name)
                ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.VolumeMismatch, claimMsg)
                // Skipping syncClaim
                return nil
            }

            if metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnBoundByController) { 
                klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume not bound yet, waiting for syncClaim to fix it", volume.Name)
            } else { 
                klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume was bound and got unbound (by user?), waiting for syncClaim to fix it", volume.Name)
            } 
            ctrl.claimQueue.Add(claimToClaimKey(claim))
            return nil
        //  已经绑定更新状态status phase为Bound
        } else if claim.Spec.VolumeName == volume.Name {
            // Volume is bound to a claim properly, update status if necessary
            klog.V(4).Infof("synchronizing PersistentVolume[%s]: all is bound", volume.Name)
            if _, err = ctrl.updateVolumePhase(volume, v1.VolumeBound, ""); err != nil {
                // Nothing was saved; we will fall back into the same
                // condition in the next call to this method
                return err
            }
            return nil
        //  PV绑定到PVC上,但是PVC被绑定到其他PV上,重置
        } else {
            // Volume is bound to a claim, but the claim is bound elsewhere
            if metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnDynamicallyProvisioned) && volume.Spec.PersistentVolumeReclaimPolicy == v1.PersistentVolumeReclaimDelete {

                if volume.Status.Phase != v1.VolumeReleased && volume.Status.Phase != v1.VolumeFailed { 
                    klog.V(2).Infof("dynamically volume %q is released and it will be deleted", volume.Name)
                    if volume, err = ctrl.updateVolumePhase(volume, v1.VolumeReleased, ""); err != nil { 
                        return err
                    }
                }
                if err = ctrl.reclaimVolume(volume); err != nil { 
                    return err
                }
                return nil
            } else { 
                if metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnBoundByController) { 
                    klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is bound by controller to a claim that is bound to another volume, unbinding", volume.Name)
                    if err = ctrl.unbindVolume(volume); err != nil {
                        return err
                    }
                    return nil
                } else { 
                    klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is bound by user to a claim that is bound to another volume, waiting for the claim to get unbound", volume.Name) 
                    if err = ctrl.unbindVolume(volume); err != nil {
                        return err
                    }
                    return nil
                }
            }
        }
    }
}

この方法は少し長いので、少しずつ分析していきましょう。

このメソッドは、PVがバインドされている場合、ClaimRef属性が割り当てられるため、最初にClaimRefが設定されているかどうかを確認します。kubectledit pv mongodb-pvを使用してインスタンスを入力し、現在のPVを表示できますプロパティ、あなたは見つけるでしょう:

  claimRef:
    apiVersion: v1
    kind: PersistentVolumeClaim
    name: mongodb-pvc
    namespace: default
    resourceVersion: "824043"
    uid: 5cf34ad0-2181-4d99-9875-0d4559e58f42

したがって、この属性が空の場合、PVのステータスをAvailableに更新する必要があります。

ClaimRefが空でない場合、次にUID属性がチェックされます。UIDが空の場合、PVはPVCにバインドされていますが、PVCはPVにバインドされていないため、PVのステータスをAvailableにリセットする必要があります。

次に、PVに対応するPVCを取得します。対応するPVCがPVCコレクションで見つからない場合は、ローカルキャッシュが更新されないようにするために、apiserverで再度検索し、見つかった変数にマークを付けます。

対応するPVCが見つかった場合、UIDが等しいかどうかを比較する必要があります。UIDが等しくない場合は、バインドされているPVCではないことを意味します。PVCは削除されたと考えることができます。次に、PVを更新して解放し、PVのステータスをリリース済みに変更する必要があります。 ;

次に、reclaimVolumeメソッドが呼び出され、persistentVolumeReclaimPolicy構成に従って処理されます。

PersistentVolumeController#reclaimVolume

func (ctrl *PersistentVolumeController) reclaimVolume(volume *v1.PersistentVolume) error {
    ...
    switch volume.Spec.PersistentVolumeReclaimPolicy {
    //这个策略允许手动回收资源,当PVC被删除后,PV仍然可以存在,管理员可以手动的执行删除PV
    case v1.PersistentVolumeReclaimRetain:
        klog.V(4).Infof("reclaimVolume[%s]: policy is Retain, nothing to do", volume.Name)
    //回收PV,如果没有pod在使用PV,那么将该PV的状态设置为Available
    case v1.PersistentVolumeReclaimRecycle:
        ...
        ctrl.scheduleOperation(opName, func() error {
            ctrl.recycleVolumeOperation(volume)
            return nil
        })
    //这个策略会在PVC被删除之后,连带将PV以及PV管理的存储资源也删除
    case v1.PersistentVolumeReclaimDelete:
        ...
        ctrl.scheduleOperation(opName, func() error {
            _, err := ctrl.deleteVolumeOperation(volume)
            if err != nil { 
                metrics.RecordMetric(volume.Name, &ctrl.operationTimestamps, err)
            }
            return err
        })

    default:
        ...
    }
    return nil
}

このメソッドでは、スイッチケースを使用してPersistentVolumeReclaimPolicyポリシーを処理します。保持ポリシーの場合は、手動で削除する必要があります。ここで記録されるログは1つだけです。それがリサイクルの場合は、recycleVolumeOperationを呼び出してバインド解除操作を実行します。削除の場合は、deleteVolumeOperationメソッドを呼び出します。対応するPVを削除します。

deleteVolumeOperationを選択して、このメソッドの特定の実装を見てみましょう。

func (ctrl *PersistentVolumeController) deleteVolumeOperation(volume *v1.PersistentVolume) (string, error) {
    klog.V(4).Infof("deleteVolumeOperation [%s] started", volume.Name)

    //这里先读取最新的PV实例
    newVolume, err := ctrl.kubeClient.CoreV1().PersistentVolumes().Get(context.TODO(), volume.Name, metav1.GetOptions{})
    if err != nil {
        klog.V(3).Infof("error reading persistent volume %q: %v", volume.Name, err)
        return "", nil
    }
    //如果已经被删除了,直接返回
    if newVolume.GetDeletionTimestamp() != nil {
        klog.V(3).Infof("Volume %q is already being deleted", volume.Name)
        return "", nil
    }
    //看一下是否还能找得到对应的PVC
    needsReclaim, err := ctrl.isVolumeReleased(newVolume)
    if err != nil {
        klog.V(3).Infof("error reading claim for volume %q: %v", volume.Name, err)
        return "", nil
    }
    //如果还有PVC与之关联,那么就不能删除这个PV
    if !needsReclaim {
        klog.V(3).Infof("volume %q no longer needs deletion, skipping", volume.Name)
        return "", nil
    }
    //调用相应的plugin删除PV
    pluginName, deleted, err := ctrl.doDeleteVolume(volume) 
    ...
    return pluginName, nil
}

削除を実行すると、PVが手動で削除されたかどうか、PVに対応するPVCがまだ存在するかどうかを確認する一連の検証が最初に実行され、次に対応するプラグインが呼び出されて削除が実行されることがわかります。

PersistentVolumeControllerのsyncVolumeメソッドに戻ります。クレームを検証した後、VolumeNameが空であるかどうかのチェックを続けます。これは、それがバインドされていることを示しています。

PVCのVolumeNameがPVの名前と等しい場合は、それがバインドされていることを意味し、ステータスを「バインド済み」に更新します。それ以外の場合は、PVがPVCにバインドされているが、PVCが他のPVにバインドされていることを示します。動的にプロビジョニングされ、自動的に生成されるかどうかを確認しますの場合はPVを解放し、手動で作成したPVの場合はunbindVolumeを呼び出してバインドを解除します。

この時点でvolumeWorkerの監視を終了しました。claimWorkerを見てみましょう

ClaimWorker

volumeWorkerと同様に、claimWorkerもループ内で継続的にPVCを取得し、次にupdateClaimメソッドを呼び出して特定の操作のsyncClaimに入ります。

PersistentVolumeController#syncClaim

func (ctrl *PersistentVolumeController) syncClaim(claim *v1.PersistentVolumeClaim) error {
    klog.V(4).Infof("synchronizing PersistentVolumeClaim[%s]: %s", claimToClaimKey(claim), getClaimStatusForLogging(claim))

    newClaim, err := ctrl.updateClaimMigrationAnnotations(claim)
    if err != nil { 
        return err
    }
    claim = newClaim
    //根据当前对象中的注解决定调用逻辑
    if !metav1.HasAnnotation(claim.ObjectMeta, pvutil.AnnBindCompleted) {
        //处理未绑定的pvc
        return ctrl.syncUnboundClaim(claim)
    } else {
        //处理已经绑定的pvc
        return ctrl.syncBoundClaim(claim)
    }
}

このメソッドは、キャッシュからいくつかのPVCを取得し、宛先のPVCに従ってロジックを呼び出します。

syncUnboundClaimから始めましょう。このメソッドは比較的長く、2つの部分に分かれています。

func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVolumeClaim) error {
        //说明pvc处于pending状态,没有完成绑定操作
    if claim.Spec.VolumeName == "" {
        // User did not care which PV they get.
        // 是否是延迟绑定
        delayBinding, err := pvutil.IsDelayBindingMode(claim, ctrl.classLister)
        if err != nil {
            return err
        }

        // [Unit test set 1]
        //根据声明的PVC设置的字段找到对应的PV
        volume, err := ctrl.volumes.findBestMatchForClaim(claim, delayBinding)
        if err != nil {
            klog.V(2).Infof("synchronizing unbound PersistentVolumeClaim[%s]: Error finding PV for claim: %v", claimToClaimKey(claim), err)
            return fmt.Errorf("Error finding PV for claim %q: %v", claimToClaimKey(claim), err)
        }
        //如果没有可用volume情况
        if volume == nil {
            klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: no volume found", claimToClaimKey(claim)) 
            switch {
            case delayBinding && !pvutil.IsDelayBindingProvisioning(claim):
                if err = ctrl.emitEventForUnboundDelayBindingClaim(claim); err != nil {
                    return err
                }
            //  找对应的storageclass
            case v1helper.GetPersistentVolumeClaimClass(claim) != "":
                //根据对应的插件创建PV
                if err = ctrl.provisionClaim(claim); err != nil {
                    return err
                }
                return nil
            default:
                ctrl.eventRecorder.Event(claim, v1.EventTypeNormal, events.FailedBinding, "no persistent volumes available for this claim and no storage class is set")
            }

            // 等待下次循环再查找匹配的PV进行绑定
            if _, err = ctrl.updateClaimStatus(claim, v1.ClaimPending, nil); err != nil {
                return err
            }
            return nil
        //  找到volume,进行绑定操作
        } else /* pv != nil */ { 
            claimKey := claimToClaimKey(claim)
            klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume %q found: %s", claimKey, volume.Name, getVolumeStatusForLogging(volume))
            //执行绑定操作
            if err = ctrl.bind(volume, claim); err != nil { 
                metrics.RecordMetric(claimKey, &ctrl.operationTimestamps, err)
                return err
            } 
            metrics.RecordMetric(claimKey, &ctrl.operationTimestamps, nil)
            return nil
        }
    }
    ...
}

このメソッドは、最初にVolumeNameが空かどうかを確認し、空の場合は、遅延バインディングが設定されているかどうかを確認します

次に、PVコレクションに移動して、要件を満たすPVを見つけることができるかどうかを確認します。使用可能なPVがない場合は、動的にプロビジョニングされているかどうかを確認します。動的にプロビジョニングされている場合は、PVを非同期で作成した後、PVCステータスを[バインド]に設定し、次のサイクルで一致するものを見つけます。 PVはバインドされています。

一致するPVが見つかった場合、バインドを実行するためにbindメソッドが呼び出され、bindメソッドはポストされず、ClaimRefフィールド、ステータスフェーズ、VolumeNameなどが更新されます。

次に、syncUnboundClaimコードの下部を見てください。

func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVolumeClaim) error {
    ...
    } else /* pvc.Spec.VolumeName != nil */ { 
        klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume %q requested", claimToClaimKey(claim), claim.Spec.VolumeName)
        //若VolumeName不为空,那么找到相应的PV
        obj, found, err := ctrl.volumes.store.GetByKey(claim.Spec.VolumeName)
        if err != nil {
            return err
        }
        //说明对应的PV已经不存在了,更新状态为Pending
        if !found { 
            klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume %q requested and not found, will try again next time", claimToClaimKey(claim), claim.Spec.VolumeName)
            if _, err = ctrl.updateClaimStatus(claim, v1.ClaimPending, nil); err != nil {
                return err
            }
            return nil
        } else {
            volume, ok := obj.(*v1.PersistentVolume)
            if !ok {
                return fmt.Errorf("Cannot convert object from volume cache to volume %q!?: %+v", claim.Spec.VolumeName, obj)
            }
            klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume %q requested and found: %s", claimToClaimKey(claim), claim.Spec.VolumeName, getVolumeStatusForLogging(volume))
            //PV的ClaimRef字段为空,那么调用bind执行绑定操作
            if volume.Spec.ClaimRef == nil { 
                klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume is unbound, binding", claimToClaimKey(claim))
                if err = checkVolumeSatisfyClaim(volume, claim); err != nil {
                    klog.V(4).Infof("Can't bind the claim to volume %q: %v", volume.Name, err) 
                    msg := fmt.Sprintf("Cannot bind to requested volume %q: %s", volume.Name, err)
                    ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.VolumeMismatch, msg) 
                    if _, err = ctrl.updateClaimStatus(claim, v1.ClaimPending, nil); err != nil {
                        return err
                    }
                } else if err = ctrl.bind(volume, claim); err != nil { 
                    return err
                } 
                return nil
            //  这里主要校验volume是否已绑定了别的PVC,如果没有的话,执行绑定
            } else if pvutil.IsVolumeBoundToClaim(volume, claim) { 
                klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume already bound, finishing the binding", claimToClaimKey(claim))

                if err = ctrl.bind(volume, claim); err != nil {
                    return err
                } 
                return nil
            } else {
                //这里是PV绑定了其他PVC,等待下次循环再重试
                ...
            }
        }
    }
}

ここでは、VolumeNameが空でないことを説明しているため、対応するPVを取り出すのが自然です。対応するPVが存在しない場合は、次の呼び出しがバインドを実行するのを待ちます。

対応するPVが見つかった場合、ClaimRefフィールドが空の場合は、bindを呼び出してバインディング操作を実行します。

ClaimRefが空でない場合は、IsVolumeBoundToClaimを呼び出して、PVが別のPVCにバインドされているかどうかを確認します。バインドされていない場合は、バインドを実行します

IsVolumeBoundToClaim

func IsVolumeBoundToClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) bool {
    if volume.Spec.ClaimRef == nil {
        return false
    }
    if claim.Name != volume.Spec.ClaimRef.Name || claim.Namespace != volume.Spec.ClaimRef.Namespace {
        return false
    }
    if volume.Spec.ClaimRef.UID != "" && claim.UID != volume.Spec.ClaimRef.UID {
        return false
    }
    return true
}

このメソッドは主に、対応するフィールドが等しいかどうかをチェックし、等しくない場合はfalseを返し、PVが他のPVCにバインドされていることを示し、次のサイクルを待って再試行することがわかります。

syncBoundClaimの機能を見てみましょう。

func (ctrl *PersistentVolumeController) syncBoundClaim(claim *v1.PersistentVolumeClaim) error { 
    if claim.Spec.VolumeName == "" { 
        //这里说明以前被绑定过,但现在已经找不到对应的PV了,说明数据丢失,在变更状态的同时,需要发出一个警告事件
        if _, err := ctrl.updateClaimStatusWithEvent(claim, v1.ClaimLost, nil, v1.EventTypeWarning, "ClaimLost", "Bound claim has lost reference to PersistentVolume. Data on the volume is lost!"); err != nil {
            return err
        }
        return nil
    }
    obj, found, err := ctrl.volumes.store.GetByKey(claim.Spec.VolumeName)
    if err != nil {
        return err
    }
    //绑定到不存在的pv情况
    if !found { 
        //这里说明以前被绑定过,但现在已经找不到对应的PV了,说明数据丢失,在变更状态的同时,需要发出一个警告事件
        if _, err = ctrl.updateClaimStatusWithEvent(claim, v1.ClaimLost, nil, v1.EventTypeWarning, "ClaimLost", "Bound claim has lost its PersistentVolume. Data on the volume is lost!"); err != nil {
            return err
        }
        return nil
    //  存在pv情况
    } else {
        volume, ok := obj.(*v1.PersistentVolume)
        if !ok {
            return fmt.Errorf("Cannot convert object from volume cache to volume %q!?: %#v", claim.Spec.VolumeName, obj)
        }

        klog.V(4).Infof("synchronizing bound PersistentVolumeClaim[%s]: volume %q found: %s", claimToClaimKey(claim), claim.Spec.VolumeName, getVolumeStatusForLogging(volume))
        //更新绑定关系,这里说明PVC是绑定的,但是PV处于未绑定
        if volume.Spec.ClaimRef == nil { 
            klog.V(4).Infof("synchronizing bound PersistentVolumeClaim[%s]: volume is unbound, fixing", claimToClaimKey(claim))
            if err = ctrl.bind(volume, claim); err != nil {
                // Objects not saved, next syncPV or syncClaim will try again
                return err
            }
            return nil
        //  更新绑定关系
        } else if volume.Spec.ClaimRef.UID == claim.UID { 
            klog.V(4).Infof("synchronizing bound PersistentVolumeClaim[%s]: claim is already correctly bound", claimToClaimKey(claim))
            if err = ctrl.bind(volume, claim); err != nil { 
                return err
            }
            return nil
        } else { 
            //这里说明两个PVC绑定到同一个PV上了
            if _, err = ctrl.updateClaimStatusWithEvent(claim, v1.ClaimLost, nil, v1.EventTypeWarning, "ClaimMisbound", "Two claims are bound to the same volume, this one is bound incorrectly"); err != nil {
                return err
            }
            return nil
        }
    }
}

このメソッドは主に、PVCがバインドされているさまざまな異常な状況に対処するためのものです。たとえば、VolumeNameフィールドが空かどうか、対応するPVが検出できるかどうか、対応するPVが現在のPVCにバインドされているかどうか、複数のPVCがバインドされているかどうかを確認します同じPVなどに設定してください。

総括する

この記事では、最初にPVとPVCの使用例を説明し、次に動的バインディングのプロセスを説明し、最後にPVとPVCのいくつかの基本概念を説明しました。次に、PVとPVCに対応する処理フローと、ソースコードを通じてお互いをバインドする詳細について学びましたが、この記事では、ボリュームのアタッチとデタッチに対応するADコントローラーがどのように実行されるかについて、いくつか後悔があります。後で機会があるでしょう。塗りつぶします。

おすすめ

転載: blog.csdn.net/weixin_45784983/article/details/108340193