CSI-PVController-claimWorker

claimWorker()

claim worker中循环执行workFunc()

  • claim worker从claimQueue中取数据,也就是取出的都是PVC
  • workFunc首先从队列中取出一个obj,然后拿name去informer缓存中尝试获取
    • 如果在informer缓存。说明不是删除事件,执行updateClaim()函数
    • 如果不在informer缓存中。
      • 如果pvc控制器的缓存中不存在,说明pvc已经被删除,打印日志。
      • 如果pvc控制器的缓存中存在,就调用deleteClaim()在缓存中删除这个pvc 并且将对应的pv加入volume队列。
// claimWorker processes items from claimQueue. It must run only once,
// syncClaim is not reentrant.
func (ctrl *PersistentVolumeController) claimWorker() {
    workFunc := func() bool {
        keyObj, quit := ctrl.claimQueue.Get()
        if quit {
            return true
        }
        defer ctrl.claimQueue.Done(keyObj)
        key := keyObj.(string)
        klog.V(5).Infof("claimWorker[%s]", key)
        namespace, name, err := cache.SplitMetaNamespaceKey(key)
        if err != nil {
            klog.V(4).Infof("error getting namespace & name of claim %q to get claim from informer: %v", key, err)
            return false
        }
        claim, err := ctrl.claimLister.PersistentVolumeClaims(namespace).Get(name)
        if err == nil {
            // The claim still exists in informer cache, the event must have
            // been add/update/sync
            ctrl.updateClaim(claim)
            return false
        }
        if !errors.IsNotFound(err) {
            klog.V(2).Infof("error getting claim %q from informer: %v", key, err)
            return false
        }
        // The claim is not in informer cache, the event must have been "delete"
        claimObj, found, err := ctrl.claims.GetByKey(key)
        if err != nil {
            klog.V(2).Infof("error getting claim %q from cache: %v", key, err)
            return false
        }
        if !found {
            // The controller has already processed the delete event and
            // deleted the claim from its cache
            klog.V(2).Infof("deletion of claim %q was already processed", key)
            return false
        }
        claim, ok := claimObj.(*v1.PersistentVolumeClaim)
        if !ok {
            klog.Errorf("expected claim, got %+v", claimObj)
            return false
        }
        ctrl.deleteClaim(claim)
        return false
    }
    for {
        if quit := workFunc(); quit {
            klog.Infof("claim worker queue shutting down")
            return
        }
    }
}

updateClaim()

  1. 存储新的PVC版本到缓存

    • new, err := ctrl.storeClaimUpdate(claim)
    • 这行代码尝试将传入的PVC更新到内部缓存中。如果这是一个新的版本(即之前没有这个PVC的更新或者这是该PVC的一个新版本),new将返回true;否则返回falseerr用于捕获过程中可能发生的错误。
  2. 检查是否为新版本

    • if !new { return }
    • 如果newfalse,即这不是一个新的PVC版本,则直接返回,不继续处理。
  3. 同步PVC

    • err = ctrl.syncClaim(claim)
    • 尝试同步PVC的状态。这可能涉及与底层的存储系统交互,确保PVC的状态与期望的状态一致。
  4. 同步错误处理

    • 如果同步过程中出现错误,会进行错误检查和处理。

    • if errors.IsConflict(err) { ... } else { ... }
      
      • 使用errors.IsConflict(err)检查错误是否为版本冲突(通常由于并发更新导致)。
      • 如果是版本冲突,使用klog.V(3).Infof记录一个较低级别的信息日志,因为版本冲突在Kubernetes中是常见的,并且控制器通常可以自动恢复。
      • 如果不是版本冲突,则使用klog.Errorf记录一个错误日志,因为这可能表示一个更严重的问题。

总的来说,这段代码的目的是在PersistentVolumeController中更新一个PVC的状态,并确保其状态与期望的状态同步。它处理了版本控制、错误捕获和日志记录,以确保操作的健壮性和可维护性。

// updateClaim runs in worker thread and handles "claim added",
// "claim updated" and "periodic sync" events.
func (ctrl *PersistentVolumeController) updateClaim(claim *v1.PersistentVolumeClaim) {
    // Store the new claim version in the cache and do not process it if this is
    // an old version.
    new, err := ctrl.storeClaimUpdate(claim)//存储该pvc的新版本
    if err != nil {
        klog.Errorf("%v", err)
    }
    if !new {
        return
    }
    err = ctrl.syncClaim(claim)
    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 claim %q: %+v", claimToClaimKey(claim), err)
        } else {
            klog.Errorf("could not sync volume %q: %+v", claimToClaimKey(claim), err)
        }
    }
}

syncClaim()

  1. 更新迁移注解
    • 调用updateClaimMigrationAnnotations(claim)方法尝试更新PVC上的迁移注解。这个方法可能会返回一个新的PVC对象(newClaim),如果注解有更新,并且这个新的PVC对象需要被保存到API服务器中。
    • 如果在更新注解时发生错误(err != nil),方法将返回这个错误。这意味着当前的PVC状态不会被进一步处理,而是会在下次调用syncClaim时再次尝试。
  2. 处理PVC的绑定状态
    • 接下来,代码检查PVC上是否存在pvutil.AnnBindCompleted注解。这个注解通常用来表示PVC是否已经完成绑定到一个PersistentVolume(PV)。
    • 如果PVC没有完成绑定(即不存在AnnBindCompleted注解),则调用syncUnboundClaim(claim)方法来处理未绑定的PVC。这可能包括寻找合适的PV进行绑定等操作。
    • 如果PVC已经完成绑定(即存在AnnBindCompleted注解),则调用syncBoundClaim(claim)方法来处理已绑定的PVC。这可能包括检查PVC和PV的状态是否一致,以及处理任何需要的后续操作。

总结来说,syncClaim方法是PersistentVolumeController用来同步PVC状态的核心方法。它首先尝试更新PVC的迁移注解,然后根据PVC是否已绑定到PV,分别调用不同的方法来处理PVC的状态。这个方法的设计体现了Kubernetes中控制循环(control loop)的思想,即通过不断检查和更新资源状态来确保系统的一致性和正确性。

// syncClaim is the main controller method to decide what to do with a claim.
// It's invoked by appropriate cache.Controller callbacks when a claim is
// created, updated or periodically synced. We do not differentiate between
// these events.
// For easier readability, it was split into syncUnboundClaim and syncBoundClaim
// methods.
func (ctrl *PersistentVolumeController) syncClaim(claim *v1.PersistentVolumeClaim) error {
   klog.V(4).Infof("synchronizing PersistentVolumeClaim[%s]: %s", claimToClaimKey(claim), getClaimStatusForLogging(claim))

   // Set correct "migrated-to" annotations on PVC and update in API server if
   // necessary
   newClaim, err := ctrl.updateClaimMigrationAnnotations(claim)
   if err != nil {
      // Nothing was saved; we will fall back into the same
      // condition in the next call to this method
      return err
   }
   claim = newClaim

   if !metav1.HasAnnotation(claim.ObjectMeta, pvutil.AnnBindCompleted) {
      return ctrl.syncUnboundClaim(claim)
   } else {
      return ctrl.syncBoundClaim(claim)
   }
}

syncBoundClaim()

  1. 检查PVC是否已绑定
    • if claim.Spec.VolumeName == ""
    • 首先检查PVC的Spec.VolumeName是否为空。如果为空,表示这个PVC之前绑定过,但现在不再绑定到任何PersistentVolume(PV)。
  2. 更新PVC状态为Lost
    • 如果PVC不再绑定到任何PV,则调用updateClaimStatusWithEvent方法更新PVC的状态为ClaimLost,并记录一个警告事件,表明绑定的PV已经丢失,数据也可能丢失。
  3. 从存储中获取PV
    • obj, found, err := ctrl.volumes.store.GetByKey(claim.Spec.VolumeName)
    • 使用PVC的VolumeName从存储中获取对应的PV对象。如果获取过程中出现错误,返回错误。
  4. 检查PV是否存在
    • 如果未找到PV(!found),则更新PVC状态为ClaimLost,并记录一个警告事件,表明绑定的PV不存在,数据可能丢失。
  5. 类型断言
    • volume, ok := obj.(*v1.PersistentVolume)
    • 对获取到的对象进行类型断言,确保其类型为*v1.PersistentVolume。如果断言失败,返回错误。
  6. 记录日志
    • 使用klog.V(4).Infof记录日志,表明正在同步已绑定的PVC,并找到了对应的PV。
  7. 检查PV的ClaimRef
    • if volume.Spec.ClaimRef == nil
    • 如果PV的Spec.ClaimRef为空,表示PV当前未绑定到任何PVC,或者控制器尚未收到更新的PV信息。此时,尝试重新绑定PV和PVC。
  8. 重新绑定PV和PVC
    • 调用ctrl.bind(volume, claim)方法尝试重新绑定PV和PVC。如果绑定失败,返回错误。
  9. 检查PV的ClaimRef是否与PVC匹配
    • else if volume.Spec.ClaimRef.UID == claim.UID
    • 如果PV的Spec.ClaimRef.UID与PVC的UID相匹配,表示PVC已正确绑定到PV。此时,仍然调用ctrl.bind方法(大多数情况下,这个方法此时不会做任何操作,因为所有状态应该已经设置好了)。
  10. 处理绑定冲突
    • 如果PV的Spec.ClaimRef.UID与PVC的UID不匹配,表示存在绑定冲突(即两个PVC绑定到了同一个PV)。此时,更新PVC的状态为ClaimLost,并记录一个警告事件,表明存在绑定错误。

总结来说,这段代码的主要作用是确保PVC和PV之间的绑定关系是正确的,并在出现问题时进行相应的处理和状态更新。

func (ctrl *PersistentVolumeController) syncBoundClaim(claim *v1.PersistentVolumeClaim) error {
	// HasAnnotation(pvc, pvutil.AnnBindCompleted)
	// This PVC has previously been bound
	// OBSERVATION: pvc is not "Pending"
	// [Unit test set 3]
	if claim.Spec.VolumeName == "" {
		// Claim was bound before but not any more.
		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
	}
	if !found {
		// Claim is bound to a non-existing volume.
		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
	} 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))
		if volume.Spec.ClaimRef == nil {
			// Claim is bound but volume has come unbound.
			// Or, a claim was bound and the controller has not received updated
			// volume yet. We can't distinguish these cases.
			// Bind the volume again and set all states to Bound.
			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 {
			// All is well
			// NOTE: syncPV can handle this so it can be left out.
			// NOTE: bind() call here will do nothing in most cases as
			// everything should be already set.
			klog.V(4).Infof("synchronizing bound PersistentVolumeClaim[%s]: claim is already correctly bound", 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 {
			// Claim is bound but volume has a different claimant.
			// Set the claim phase to 'Lost', which is a terminal
			// phase.
			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
		}
	}
}
bind()
  1. 绑定 PV 到 PVC
    • 调用 ctrl.bindVolumeToClaim(volume, claim) 尝试将 PV 绑定到 PVC。如果操作失败,记录错误日志并返回错误。
    • 如果绑定成功,更新 volume 变量为 updatedVolume,以反映绑定后的状态。
  2. 更新 PV 状态
    • 调用 ctrl.updateVolumePhase(volume, v1.VolumeBound, "") 更新 PV 的状态为 Bound,表示它已被绑定到一个 PVC。如果更新失败,记录错误日志并返回错误。
    • 更新 volume 变量以反映状态更新后的 PV。
  3. 绑定 PVC 到 PV
    • 调用 ctrl.bindClaimToVolume(claim, volume) 尝试将 PVC 绑定到 PV。如果操作失败,记录错误日志并返回错误。
    • 如果绑定成功,更新 claim 变量为 updatedClaim
  4. 更新 PVC 状态
    • 调用 ctrl.updateClaimStatus(claim, v1.ClaimBound, volume) 更新 PVC 的状态为 Bound,表示它已被绑定到一个 PV。如果更新失败,记录错误日志并返回错误。
    • 更新 claim 变量以反映状态更新后的 PVC。
// bind saves binding information both to the volume and the claim and marks
// both objects as Bound. Volume is saved first.
// It returns on first error, it's up to the caller to implement some retry
// mechanism.
func (ctrl *PersistentVolumeController) bind(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) error {
   var err error
   // use updateClaim/updatedVolume to keep the original claim/volume for
   // logging in error cases.
   var updatedClaim *v1.PersistentVolumeClaim
   var updatedVolume *v1.PersistentVolume

   klog.V(4).Infof("binding volume %q to claim %q", volume.Name, claimToClaimKey(claim))

   if updatedVolume, err = ctrl.bindVolumeToClaim(volume, claim); err != nil {
      klog.V(3).Infof("error binding volume %q to claim %q: failed saving the volume: %v", volume.Name, claimToClaimKey(claim), err)
      return err
   }
   volume = updatedVolume

   if updatedVolume, err = ctrl.updateVolumePhase(volume, v1.VolumeBound, ""); err != nil {
      klog.V(3).Infof("error binding volume %q to claim %q: failed saving the volume status: %v", volume.Name, claimToClaimKey(claim), err)
      return err
   }
   volume = updatedVolume

   if updatedClaim, err = ctrl.bindClaimToVolume(claim, volume); err != nil {
      klog.V(3).Infof("error binding volume %q to claim %q: failed saving the claim: %v", volume.Name, claimToClaimKey(claim), err)
      return err
   }
   claim = updatedClaim

   if updatedClaim, err = ctrl.updateClaimStatus(claim, v1.ClaimBound, volume); err != nil {
      klog.V(3).Infof("error binding volume %q to claim %q: failed saving the claim status: %v", volume.Name, claimToClaimKey(claim), err)
      return err
   }
   claim = updatedClaim

   klog.V(4).Infof("volume %q bound to claim %q", volume.Name, claimToClaimKey(claim))
   klog.V(4).Infof("volume %q status after binding: %s", volume.Name, getVolumeStatusForLogging(volume))
   klog.V(4).Infof("claim %q status after binding: %s", claimToClaimKey(claim), getClaimStatusForLogging(claim))
   return nil
}
bindVolumeToClaim()
  • 检查 PV 是否需要绑定到目标 PVC。
  • 如果需要绑定,则生成新的 PV 对象并更新其绑定信息。
  • 通过 updateBindVolumeToClaim 完成状态更新和缓存同步。
  1. 获取绑定信息
    • volumeClone, dirty, err := pvutil.GetBindVolumeToClaim(volume, claim): 这行代码调用pvutil.GetBindVolumeToClaim函数,尝试将PVC的信息绑定到PV上。该函数返回三个值:一个PV的克隆(volumeClone),一个布尔值(dirty)表示PV是否被修改,以及一个error对象。如果发生错误,方法将返回nil和错误对象。
  2. 更新PV
    • if dirty {: 如果dirtytrue,表示PV在绑定过程中被修改了。
    • return ctrl.updateBindVolumeToClaim(volumeClone, true): 调用ctrlupdateBindVolumeToClaim方法,传入修改后的PV克隆和true(可能表示需要强制更新或确认更新)。该方法负责更新PV的状态,并返回更新后的PV和可能的错误。
  3. 未修改时的处理
    • 如果dirtyfalse,表示PV在尝试绑定过程中没有被修改。
    • klog.V(4).Infof("updating PersistentVolume[%s]: already bound to %q", volume.Name, claimToClaimKey(claim)): 记录一个信息级别的日志,表明PV已经绑定到了指定的PVC,没有进一步的更新操作需要执行。- return volume, nil: 返回原始的PV和nil错误,表示操作成功完成,没有错误。

总结来说,这段代码的主要功能是尝试将一个PV绑定到一个PVC。如果绑定过程中PV被修改,则更新PV的状态;如果PV未被修改(可能已经绑定到了该PVC),则记录日志并返回原始的PV。这个过程涉及到日志记录、数据绑定和条件更新操作。

func (ctrl *PersistentVolumeController) bindVolumeToClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) (*v1.PersistentVolume, error) {
   klog.V(4).Infof("updating PersistentVolume[%s]: binding to %q", volume.Name, claimToClaimKey(claim))

   volumeClone, dirty, err := pvutil.GetBindVolumeToClaim(volume, claim)
   if err != nil {
      return nil, err
   }

   // Save the volume only if something was changed
   if dirty {
      return ctrl.updateBindVolumeToClaim(volumeClone, true)
   }

   klog.V(4).Infof("updating PersistentVolume[%s]: already bound to %q", volume.Name, claimToClaimKey(claim))
   return volume, nil
}
GetBindVolumeToClaim()
  1. 初始化 dirty 标志:
    • dirty 初始化为 false,用于标记PV是否被修改。
  2. 检查PV是否已绑定到PVC:
    • 使用 IsVolumeBoundToClaim(volume, claim) 函数检查PV是否已经绑定到PVC。
    • 如果未绑定,设置 shouldSetBoundByControllertrue,表示需要通过控制器来绑定。
  3. 创建PV的深度复制:
    • 使用 volume.DeepCopy() 创建PV的一个深度复制,以避免直接修改原始对象。
  4. 绑定PV到PVC:
    • 检查PV的 Spec.ClaimRef 是否为空,或者是否与给定的PVC不匹配(通过名称、命名空间和UID判断)。
    • 如果不匹配,使用 reference.GetReference(scheme.Scheme, claim) 获取PVC的引用,并将其设置为PV复制的 Spec.ClaimRef
    • 设置 dirtytrue,表示PV已被修改。
  5. 设置 AnnBoundByController 注解:
    • 如果 shouldSetBoundByControllertrue 且PV复制对象上没有 AnnBoundByController 注解,则添加该注解并设置其值为 “yes”。
    • 同样,设置 dirtytrue
  6. 返回结果:
    • 返回PV的深度复制对象、dirty 标志和 nil 错误(如果没有发生错误)。

总结
这个函数的核心功能是将一个PV绑定到一个PVC,并在必要时更新PV的状态(如设置绑定注解)。通过返回一个深度复制的PV对象和一个表示是否进行了修改的 dirty 标志,调用者可以决定是否需要进一步处理或持久化这个修改过的PV对象。

// GetBindVolumeToClaim returns a new volume which is bound to given claim. In
// addition, it returns a bool which indicates whether we made modification on
// original volume.
func GetBindVolumeToClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) (*v1.PersistentVolume, bool, error) {
    dirty := false
    // Check if the volume was already bound (either by user or by controller)
    shouldSetBoundByController := false
    if !IsVolumeBoundToClaim(volume, claim) {
        shouldSetBoundByController = true
    }
    // The volume from method args can be pointing to watcher cache. We must not
    // modify these, therefore create a copy.
    volumeClone := volume.DeepCopy()
    // Bind the volume to the claim if it is not bound yet
    if volume.Spec.ClaimRef == nil ||
        volume.Spec.ClaimRef.Name != claim.Name ||
        volume.Spec.ClaimRef.Namespace != claim.Namespace ||
        volume.Spec.ClaimRef.UID != claim.UID {
        claimRef, err := reference.GetReference(scheme.Scheme, claim)
        if err != nil {
            return nil, false, fmt.Errorf("Unexpected error getting claim reference: %v", err)
        }
        volumeClone.Spec.ClaimRef = claimRef
        dirty = true
    }
    // Set AnnBoundByController if it is not set yet
    if shouldSetBoundByController && !metav1.HasAnnotation(volumeClone.ObjectMeta, AnnBoundByController) {
        metav1.SetMetaDataAnnotation(&volumeClone.ObjectMeta, AnnBoundByController, "yes")
        dirty = true
    }
    return volumeClone, dirty, nil
}
updateVolumePhase()
  1. 检查当前 PV 状态是否已与目标状态一致,若一致则直接返回。
  2. 深拷贝 PV 对象,避免修改原对象。
  3. 更新状态和消息。
  4. 调用 Kubernetes API 更新 PV 状态。
  5. 同步更新本地缓存。
  6. 记录日志。
// updateVolumePhase saves new volume phase to API server.
func (ctrl *PersistentVolumeController) updateVolumePhase(volume *v1.PersistentVolume, phase v1.PersistentVolumePhase, message string) (*v1.PersistentVolume, error) {
   klog.V(4).Infof("updating PersistentVolume[%s]: set phase %s", volume.Name, phase)
   if volume.Status.Phase == phase {
      // Nothing to do.
      klog.V(4).Infof("updating PersistentVolume[%s]: phase %s already set", volume.Name, phase)
      return volume, nil
   }

   volumeClone := volume.DeepCopy()
   volumeClone.Status.Phase = phase
   volumeClone.Status.Message = message

   newVol, err := ctrl.kubeClient.CoreV1().PersistentVolumes().UpdateStatus(context.TODO(), volumeClone, metav1.UpdateOptions{})
   if err != nil {
      klog.V(4).Infof("updating PersistentVolume[%s]: set phase %s failed: %v", volume.Name, phase, err)
      return newVol, err
   }
   _, err = ctrl.storeVolumeUpdate(newVol)
   if err != nil {
      klog.V(4).Infof("updating PersistentVolume[%s]: cannot update internal cache: %v", volume.Name, err)
      return newVol, err
   }
   klog.V(2).Infof("volume %q entered phase %q", volume.Name, phase)
   return newVol, err
}
bindClaimToVolume()
  • 检查 PVC 是否需要绑定到目标 PV。
  • 更新 PVC 的 Spec.VolumeName 和相关注解(如绑定状态和控制器标识)。
  • 通过 Kubernetes API 更新 PVC 并同步本地缓存。
  1. 检查PVC是否已绑定

    • 通过比较volume.Nameclaim.Spec.VolumeName来判断PVC是否已经绑定到某个PV。如果它们不相等,说明需要绑定(shouldBind设置为true)。
  2. 创建PVC的深拷贝

    • 由于传入的PVC可能指向watcher的缓存,直接修改它可能会导致问题。因此,这里使用DeepCopy方法创建了一个PVC的副本(claimClone),后续的操作都将在这个副本上进行。
  3. 执行绑定操作

    • 如果shouldBindtrue,则进行绑定操作:
      • 设置claimClone.Spec.VolumeNamevolume.Name,将PVC绑定到PV。
      • 如果PVC的元数据中没有AnnBoundByController注解,则添加这个注解,值为"yes",表示这个绑定操作是由控制器完成的。
  4. 设置AnnBindCompleted注解

    • 无论是否进行了绑定操作,如果PVC的元数据中没有AnnBindCompleted注解,则添加这个注解,值为"yes",表示绑定操作已经完成。
  5. 更新PVC

    • 如果进行了任何修改(即dirtytrue),则需要更新PVC:
      • 使用ctrl.kubeClient.CoreV1().PersistentVolumeClaims(claim.Namespace).Update方法更新Kubernetes中的PVC对象。
      • 更新内部缓存,调用ctrl.storeClaimUpdate(newClaim)
      • 记录日志,表示PVC已成功绑定到PV。

总结来说,这段代码的作用是检查一个PVC是否已经绑定到一个PV,如果没有绑定,则进行绑定操作,并更新PVC对象及其元数据注解,最后更新Kubernetes中的PVC对象和内部缓存。

func (ctrl *PersistentVolumeController) bindClaimToVolume(claim *v1.PersistentVolumeClaim, volume *v1.PersistentVolume) (*v1.PersistentVolumeClaim, error) {
   klog.V(4).Infof("updating PersistentVolumeClaim[%s]: binding to %q", claimToClaimKey(claim), volume.Name)

   dirty := false

   // Check if the claim was already bound (either by controller or by user)
   shouldBind := false
   if volume.Name != claim.Spec.VolumeName {
      shouldBind = true
   }

   // The claim from method args can be pointing to watcher cache. We must not
   // modify these, therefore create a copy.
   claimClone := claim.DeepCopy()

   if shouldBind {
      dirty = true
      // Bind the claim to the volume
      claimClone.Spec.VolumeName = volume.Name

      // Set AnnBoundByController if it is not set yet
      if !metav1.HasAnnotation(claimClone.ObjectMeta, pvutil.AnnBoundByController) {
         metav1.SetMetaDataAnnotation(&claimClone.ObjectMeta, pvutil.AnnBoundByController, "yes")
      }
   }

   // Set AnnBindCompleted if it is not set yet
   if !metav1.HasAnnotation(claimClone.ObjectMeta, pvutil.AnnBindCompleted) {
      metav1.SetMetaDataAnnotation(&claimClone.ObjectMeta, pvutil.AnnBindCompleted, "yes")
      dirty = true
   }

   if dirty {
      klog.V(2).Infof("volume %q bound to claim %q", volume.Name, claimToClaimKey(claim))
      newClaim, err := ctrl.kubeClient.CoreV1().PersistentVolumeClaims(claim.Namespace).Update(context.TODO(), claimClone, metav1.UpdateOptions{})
      if err != nil {
         klog.V(4).Infof("updating PersistentVolumeClaim[%s]: binding to %q failed: %v", claimToClaimKey(claim), volume.Name, err)
         return newClaim, err
      }
      _, err = ctrl.storeClaimUpdate(newClaim)
      if err != nil {
         klog.V(4).Infof("updating PersistentVolumeClaim[%s]: cannot update internal cache: %v", claimToClaimKey(claim), err)
         return newClaim, err
      }
      klog.V(4).Infof("updating PersistentVolumeClaim[%s]: bound to %q", claimToClaimKey(claim), volume.Name)
      return newClaim, nil
   }

   klog.V(4).Infof("updating PersistentVolumeClaim[%s]: already bound to %q", claimToClaimKey(claim), volume.Name)
   return claim, nil
}
updateClaimStatus()
  • 根据目标 phase 更新 PVC 的状态。
  • 根据是否关联 PV,更新或重置访问模式和容量。
  • 通过 Kubernetes API 更新 PVC 状态,并同步本地缓存。
  1. 深拷贝:为了避免直接修改原始PVC对象,函数创建了claim的一个深拷贝claimClone
  2. 状态更新
    • 如果PVC的当前状态与要设置的状态不同,则更新claimClone的状态,并将dirty标志设置为true,表示有更改。
    • 如果volumenil,表示PVC不与任何PV关联,因此需要清除AccessModesCapacity
    • 如果volume不为nil,则需要根据volume的信息更新claimCloneAccessModesCapacity。特别注意,只有在PVC状态发生变化时(即之前不是这个状态),才更新Capacity
  3. 无更改处理:如果没有任何更改(dirtyfalse),则记录一条日志并返回原始的PVC对象和nil错误。
  4. 更新状态:如果有更改,使用Kubernetes客户端(ctrl.kubeClient)调用UpdateStatus方法更新PVC的状态。这里使用了context.TODO()作为上下文,通常在实际调用中需要提供一个具体的上下文。
  5. 内部缓存更新:更新内部缓存中的PVC信息。这是通过调用ctrl.storeClaimUpdate方法实现的。
  6. 日志记录和返回:最后,记录一条日志表明PVC已经进入了新的状态,并返回更新后的PVC对象和可能的错误。

关键点

  • 深拷贝:为了避免对原始对象的意外修改,使用了深拷贝。
  • 条件更新:根据volume是否为nil以及PVC的当前状态,有条件地更新AccessModesCapacity
  • 错误处理:在关键操作(如更新状态和内部缓存)中进行了错误检查和处理。

这段代码是Kubernetes中处理PVC状态更新的一个典型例子,展示了如何在复杂的系统中管理资源状态,并确保状态的一致性和正确性。

func (ctrl *PersistentVolumeController) updateClaimStatus(claim *v1.PersistentVolumeClaim, phase v1.PersistentVolumeClaimPhase, volume *v1.PersistentVolume) (*v1.PersistentVolumeClaim, error) {
   klog.V(4).Infof("updating PersistentVolumeClaim[%s] status: set phase %s", claimToClaimKey(claim), phase)

   dirty := false

   claimClone := claim.DeepCopy()
   if claim.Status.Phase != phase {
      claimClone.Status.Phase = phase
      dirty = true
   }

   if volume == nil {
      // Need to reset AccessModes and Capacity
      if claim.Status.AccessModes != nil {
         claimClone.Status.AccessModes = nil
         dirty = true
      }
      if claim.Status.Capacity != nil {
         claimClone.Status.Capacity = nil
         dirty = true
      }
   } else {
      // Need to update AccessModes and Capacity
      if !reflect.DeepEqual(claim.Status.AccessModes, volume.Spec.AccessModes) {
         claimClone.Status.AccessModes = volume.Spec.AccessModes
         dirty = true
      }

      // Update Capacity if the claim is becoming Bound, not if it was already.
      // A discrepancy can be intentional to mean that the PVC filesystem size
      // doesn't match the PV block device size, so don't clobber it
      if claim.Status.Phase != phase {
         volumeCap, ok := volume.Spec.Capacity[v1.ResourceStorage]
         if !ok {
            return nil, fmt.Errorf("PersistentVolume %q is without a storage capacity", volume.Name)
         }
         claimCap, ok := claim.Status.Capacity[v1.ResourceStorage]
         if !ok || volumeCap.Cmp(claimCap) != 0 {
            claimClone.Status.Capacity = volume.Spec.Capacity
            dirty = true
         }
      }
   }

   if !dirty {
      // Nothing to do.
      klog.V(4).Infof("updating PersistentVolumeClaim[%s] status: phase %s already set", claimToClaimKey(claim), phase)
      return claim, nil
   }

   newClaim, err := ctrl.kubeClient.CoreV1().PersistentVolumeClaims(claimClone.Namespace).UpdateStatus(context.TODO(), claimClone, metav1.UpdateOptions{})
   if err != nil {
      klog.V(4).Infof("updating PersistentVolumeClaim[%s] status: set phase %s failed: %v", claimToClaimKey(claim), phase, err)
      return newClaim, err
   }
   _, err = ctrl.storeClaimUpdate(newClaim)
   if err != nil {
      klog.V(4).Infof("updating PersistentVolumeClaim[%s] status: cannot update internal cache: %v", claimToClaimKey(claim), err)
      return newClaim, err
   }
   klog.V(2).Infof("claim %q entered phase %q", claimToClaimKey(claim), phase)
   return newClaim, nil
}

syncUnboundClaim()

PersistentVolumeController 的核心方法,用于处理未绑定的 PersistentVolumeClaim (PVC),尝试将其与 PersistentVolume (PV) 绑定。其核心逻辑包括:

  • 自动绑定:当 PVC 未指定具体 PV 时,根据容量、访问模式等条件匹配 PV。
  • 延迟绑定:触发动态供应 PV(如通过 StorageClass)。
  • 手动绑定:当 PVC 显式指定 PV 时,验证 PV 是否可用并完成绑定。

代码流程与关键逻辑
检查PVC是否指定了VolumeName

  • 如果claim.Spec.VolumeName为空,表示用户没有指定特定的PV。
    • 延迟绑定检查:通过pvutil.IsDelayBindingMode检查是否为延迟绑定模式。
    • 寻找最佳匹配的PV:调用ctrl.volumes.findBestMatchForClaim尝试找到一个与PVC最匹配的PV。
      • 如果找到匹配的PV:
        • 调用ctrl.bind将PVC与PV绑定。
        • 记录绑定操作的指标。
      • 如果未找到匹配的PV:
        • 根据是否为延迟绑定模式和是否设置了存储类,决定是否进行动态供应或记录事件。
        • 更新PVC的状态为Pending,以便后续再次尝试匹配。
  • 如果claim.Spec.VolumeName不为空,表示用户指定了特定的PV。
    • 查找指定的PV:通过ctrl.volumes.store.GetByKey尝试获取用户指定的PV。
      • 如果PV不存在:
        • 更新PVC的状态为Pending,以便后续再次尝试。
      • 如果PV存在但不是PersistentVolume类型:
        • 返回错误。
      • 如果PV存在且未被绑定:
        • 检查PV是否满足PVC的要求,如果满足则调用ctrl.bind进行绑定。
      • 如果PV已被绑定:
        • 检查PV是否被当前PVC绑定,如果是,则完成绑定操作。
        • 如果PV被其他PVC绑定,且没有AnnBoundByController注解,记录事件并更新PVC状态为Pending。
        • 如果PV被其他PVC绑定,且有AnnBoundByController注解,这是一个异常情况,返回错误。

关键函数和概念

  • pvutil.IsDelayBindingMode:检查PVC是否为延迟绑定模式。
  • ctrl.volumes.findBestMatchForClaim:寻找与PVC最匹配的PV。
  • ctrl.bind:将PVC与PV绑定。
  • ctrl.updateClaimStatus:更新PVC的状态。
  • ctrl.eventRecorder.Event:记录事件,如绑定失败或成功。
  • checkVolumeSatisfyClaim:检查PV是否满足PVC的要求。

总结
`方法是Kubernetes中PersistentVolumeController的核心方法之一,负责处理未绑定的PVC,尝试将其与合适的PV绑定,或者处理无法绑定的情况。通过详细的逻辑分支和错误处理,确保了PVC和PV的正确匹配和绑定。

func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVolumeClaim) error {
	// This is a new PVC that has not completed binding
	// OBSERVATION: pvc is "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]
		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)
		}
		if volume == nil {
			klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: no volume found", claimToClaimKey(claim))
			// No PV could be found
			// OBSERVATION: pvc is "Pending", will retry
			switch {
			case delayBinding && !pvutil.IsDelayBindingProvisioning(claim):
				if err = ctrl.emitEventForUnboundDelayBindingClaim(claim); err != nil {
					return err
				}
			case v1helper.GetPersistentVolumeClaimClass(claim) != "":
				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")
			}

			// Mark the claim as Pending and try to find a match in the next
			// periodic syncClaim
			if _, err = ctrl.updateClaimStatus(claim, v1.ClaimPending, nil); err != nil {
				return err
			}
			return nil
		} else /* pv != nil */ {
			// Found a PV for this claim
			// OBSERVATION: pvc is "Pending", pv is "Available"
			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 {
				// On any error saving the volume or the claim, subsequent
				// syncClaim will finish the binding.
				// record count error for provision if exists
				// timestamp entry will remain in cache until a success binding has happened
				metrics.RecordMetric(claimKey, &ctrl.operationTimestamps, err)
				return err
			}
			// OBSERVATION: claim is "Bound", pv is "Bound"
			// if exists a timestamp entry in cache, record end to end provision latency and clean up cache
			// End of the provision + binding operation lifecycle, cache will be cleaned by "RecordMetric"
			// [Unit test 12-1, 12-2, 12-4]
			metrics.RecordMetric(claimKey, &ctrl.operationTimestamps, nil)
			return nil
		}
	} else /* pvc.Spec.VolumeName != nil */ {
		// [Unit test set 2]
		// User asked for a specific PV.
		klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume %q requested", claimToClaimKey(claim), claim.Spec.VolumeName)
		obj, found, err := ctrl.volumes.store.GetByKey(claim.Spec.VolumeName)
		if err != nil {
			return err
		}
		if !found {
			// User asked for a PV that does not exist.
			// OBSERVATION: pvc is "Pending"
			// Retry later.
			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))
			if volume.Spec.ClaimRef == nil {
				// User asked for a PV that is not claimed
				// OBSERVATION: pvc is "Pending", pv is "Available"
				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)
					// send an event
					msg := fmt.Sprintf("Cannot bind to requested volume %q: %s", volume.Name, err)
					ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.VolumeMismatch, msg)
					// volume does not satisfy the requirements of the claim
					if _, err = ctrl.updateClaimStatus(claim, v1.ClaimPending, nil); err != nil {
						return err
					}
				} else if err = ctrl.bind(volume, claim); err != nil {
					// On any error saving the volume or the claim, subsequent
					// syncClaim will finish the binding.
					return err
				}
				// OBSERVATION: pvc is "Bound", pv is "Bound"
				return nil
			} else if pvutil.IsVolumeBoundToClaim(volume, claim) {
				// User asked for a PV that is claimed by this PVC
				// OBSERVATION: pvc is "Pending", pv is "Bound"
				klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume already bound, finishing the binding", claimToClaimKey(claim))

				// Finish the volume binding by adding claim UID.
				if err = ctrl.bind(volume, claim); err != nil {
					return err
				}
				// OBSERVATION: pvc is "Bound", pv is "Bound"
				return nil
			} else {
				// User asked for a PV that is claimed by someone else
				// OBSERVATION: pvc is "Pending", pv is "Bound"
				if !metav1.HasAnnotation(claim.ObjectMeta, pvutil.AnnBoundByController) {
					klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume already bound to different claim by user, will retry later", claimToClaimKey(claim))
					claimMsg := fmt.Sprintf("volume %q already bound to a different claim.", volume.Name)
					ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.FailedBinding, claimMsg)
					// User asked for a specific PV, retry later
					if _, err = ctrl.updateClaimStatus(claim, v1.ClaimPending, nil); err != nil {
						return err
					}
					return nil
				} else {
					// This should never happen because someone had to remove
					// AnnBindCompleted annotation on the claim.
					klog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: volume already bound to different claim %q by controller, THIS SHOULD NEVER HAPPEN", claimToClaimKey(claim), claimrefToClaimKey(volume.Spec.ClaimRef))
					claimMsg := fmt.Sprintf("volume %q already bound to a different claim.", volume.Name)
					ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.FailedBinding, claimMsg)

					return fmt.Errorf("Invalid binding of claim %q to volume %q: volume already claimed by %q", claimToClaimKey(claim), claim.Spec.VolumeName, claimrefToClaimKey(volume.Spec.ClaimRef))
				}
			}
		}
	}
}
provisionClaim()
  1. 检查动态供给是否启用

    if !ctrl.enableDynamicProvisioning {
        return nil
    }
    
    • 如果enableDynamicProvisioningfalse,则直接返回nil,表示不进行动态供给。
  2. 定义操作名称

    opName := fmt.Sprintf("provision-%s[%s]", claimToClaimKey(claim), string(claim.UID))
    
    • 构造一个操作名称,用于后续的记录和调试。
  3. 查找可供给的插件

    plugin, storageClass, err := ctrl.findProvisionablePlugin(claim)
    
    • 调用findProvisionablePlugin方法查找适合该PVC的供给插件和存储类。
  4. 处理查找插件的错误

    • 如果err不为nil,表示查找插件失败,记录事件和日志,并返回nil。这里返回nil是因为对于外部供给器,即使查找失败,控制器也会在后续的syncUnboundClaim调用中重试供给。
  5. 调度供给操作

    ctrl.scheduleOperation(opName, func() error {
        // ...
    })
    
    • 使用scheduleOperation方法调度一个供给操作。该方法接受一个操作名称和一个函数作为参数,函数内部执行实际的供给逻辑。
  6. 供给逻辑

    • 在供给函数内部,首先为供给操作在缓存中添加一个开始时间戳条目(如果尚不存在)。
    • 根据plugin是否为nil,选择调用provisionClaimOperationExternalprovisionClaimOperation方法进行供给。
    • 如果供给过程中发生错误,记录一个错误计数指标。时间戳条目将保留在缓存中,直到成功绑定为止。
  7. 返回

    • 无论供给操作是否成功,provisionClaim方法最终都返回nil。这是因为方法的调用者期望通过其他机制(如事件或状态变化)来了解供给的结果,而不是通过返回值。

总结
该方法的主要作用是检查是否需要为给定的PVC进行动态供给,并尝试找到一个合适的供给插件来执行供给操作。如果找到插件并成功调度供给操作,则记录相关的时间戳和指标;如果找不到插件或供给失败,则记录相应的日志和事件,并等待后续的重试。

// provisionClaim starts new asynchronous operation to provision a claim if
// provisioning is enabled.
func (ctrl *PersistentVolumeController) provisionClaim(claim *v1.PersistentVolumeClaim) error {
    if !ctrl.enableDynamicProvisioning {
        return nil
    }
    klog.V(4).Infof("provisionClaim[%s]: started", claimToClaimKey(claim))
    opName := fmt.Sprintf("provision-%s[%s]", claimToClaimKey(claim), string(claim.UID))
    plugin, storageClass, err := ctrl.findProvisionablePlugin(claim)
    // findProvisionablePlugin does not return err for external provisioners
    if err != nil {
        ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.ProvisioningFailed, err.Error())
        klog.Errorf("error finding provisioning plugin for claim %s: %v", claimToClaimKey(claim), err)
        // failed to find the requested provisioning plugin, directly return err for now.
        // controller will retry the provisioning in every syncUnboundClaim() call
        // retain the original behavior of returning nil from provisionClaim call
        return nil
    }
    ctrl.scheduleOperation(opName, func() error {
        // create a start timestamp entry in cache for provision operation if no one exists with
        // key = claimKey, pluginName = provisionerName, operation = "provision"
        claimKey := claimToClaimKey(claim)
        ctrl.operationTimestamps.AddIfNotExist(claimKey, ctrl.getProvisionerName(plugin, storageClass), "provision")
        var err error
        if plugin == nil {
            _, err = ctrl.provisionClaimOperationExternal(claim, storageClass)
        } else {
//启动一个goroutine进行创建操作
            _, err = ctrl.provisionClaimOperation(claim, plugin, storageClass)
        }
        // if error happened, record an error count metric
        // timestamp entry will remain in cache until a success binding has happened
        if err != nil {
            metrics.RecordMetric(claimKey, &ctrl.operationTimestamps, err)
        }
        return err
    })
    return nil
}
provisionClaimOperationExternal()
  1. 获取PVC的类
    • claimClass := v1helper.GetPersistentVolumeClaimClass(claim)
    • 通过v1helper.GetPersistentVolumeClaimClass函数获取PVC的存储类名。
      BetaStorageClassAnnotation = "volume.beta.kubernetes.io/storage-class"
  2. 设置Provisioner名称
    • 初始时,provisionerName被设置为storageClass.Provisioner
    • 如果CSI迁移插件管理器对于该Provisioner启用了迁移,则通过ctrl.translator.GetCSINameFromInTreeName获取迁移后的CSI插件名称,并更新provisionerName
    • 如果获取CSI名称时发生错误,记录错误日志,发送事件,并返回错误。
  3. 更新PVC的Provisioner注解
    • 调用ctrl.setClaimProvisioner(claim, provisionerName)为PVC设置Provisioner注解,以便外部Provisioner知道何时开始工作。
    • 如果保存失败,记录错误日志,并返回错误。
  4. 记录等待外部Provisioner的日志和事件
    • 构建一条消息,表明正在等待外部Provisioner或系统管理员手动创建卷。
    • 使用ctrl.eventRecorder.Event记录一个正常事件,表明正在等待外部Provisioner完成。
    • 使用klog.V(3).Infof记录等待的日志。
  5. 返回Provisioner名称
    • 最后,返回provisionerName(无错误)。

关键组件和概念

  • PersistentVolumeClaim (PVC)
    • PVC是Kubernetes中用户请求存储资源的对象。
  • StorageClass
    • StorageClass为管理员提供了一种描述存储类别的方法。不同的类可能映射到不同的服务质量或备份策略等。
  • CSI (Container Storage Interface)
    • CSI是一个标准接口,用于容器编排系统与存储系统之间的交互。
  • In-tree vs CSI Plugins
    • In-tree插件是Kubernetes核心代码中的一部分,而CSI插件是外部的,通过CSI接口与Kubernetes交互。
    • 代码中提到的迁移是指从In-tree插件迁移到CSI插件。
  • Event Recording
    • Kubernetes中的事件记录机制,用于记录系统中发生的重要事件。
  • klog
    • Kubernetes的日志库,用于记录日志信息。

这个方法的主要目的是处理外部存储提供者的PVC供给请求,通过更新PVC的注解、记录事件和日志,以及处理迁移逻辑,来协调外部Provisioner的工作。

// provisionClaimOperationExternal provisions a volume using external provisioner async-ly
// This method will be running in a standalone go-routine scheduled in "provisionClaim"
func (ctrl *PersistentVolumeController) provisionClaimOperationExternal(
	claim *v1.PersistentVolumeClaim,
	storageClass *storage.StorageClass) (string, error) {
	claimClass := v1helper.GetPersistentVolumeClaimClass(claim)
	klog.V(4).Infof("provisionClaimOperationExternal [%s] started, class: %q", claimToClaimKey(claim), claimClass)
	// Set provisionerName to external provisioner name by setClaimProvisioner
	var err error
	provisionerName := storageClass.Provisioner
	if ctrl.csiMigratedPluginManager.IsMigrationEnabledForPlugin(storageClass.Provisioner) {
		// update the provisioner name to use the migrated CSI plugin name
		provisionerName, err = ctrl.translator.GetCSINameFromInTreeName(storageClass.Provisioner)
		if err != nil {
			strerr := fmt.Sprintf("error getting CSI name for In tree plugin %s: %v", storageClass.Provisioner, err)
			klog.V(2).Infof("%s", strerr)
			ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.ProvisioningFailed, strerr)
			return provisionerName, err
		}
	}
	// Add provisioner annotation so external provisioners know when to start
	newClaim, err := ctrl.setClaimProvisioner(claim, provisionerName)
	if err != nil {
		// Save failed, the controller will retry in the next sync
		klog.V(2).Infof("error saving claim %s: %v", claimToClaimKey(claim), err)
		return provisionerName, err
	}
	claim = newClaim
	msg := fmt.Sprintf("waiting for a volume to be created, either by external provisioner %q or manually created by system administrator", provisionerName)
	// External provisioner has been requested for provisioning the volume
	// Report an event and wait for external provisioner to finish
	ctrl.eventRecorder.Event(claim, v1.EventTypeNormal, events.ExternalProvisioning, msg)
	klog.V(3).Infof("provisionClaimOperationExternal provisioning claim %q: %s", claimToClaimKey(claim), msg)
	// return provisioner name here for metric reporting
	return provisionerName, nil
}

setClaimProvisioner()

AnnStorageProvisioner = “volume.beta.kubernetes.io/storage-provisioner”

  1. 检查注解
    • if val, ok := claim.Annotations[pvutil.AnnStorageProvisioner]; ok && val == provisionerName { return claim, nil }
    • 这段代码首先检查PVC对象的注解中是否已经存在存储提供者(pvutil.AnnStorageProvisioner)的注解,并且其值是否与provisionerName相同。如果是,说明不需要做任何更新,直接返回原始的PVC对象和nil错误。
  2. 创建PVC对象的深拷贝
    • claimClone := claim.DeepCopy()
    • 由于原始PVC对象可能来自于监视器的缓存,直接修改它可能会导致问题。因此,这里创建了一个PVC对象的深拷贝(claimClone),以便在不影响原始对象的情况下进行修改。
  3. 设置注解
    • metav1.SetMetaDataAnnotation(&claimClone.ObjectMeta, pvutil.AnnStorageProvisioner, provisionerName)
    • 在拷贝的PVC对象上设置存储提供者注解,注解的键是pvutil.AnnStorageProvisioner,值是provisionerName
  4. 更新迁移注解
    • updateMigrationAnnotations(ctrl.csiMigratedPluginManager, ctrl.translator, claimClone.Annotations, pvutil.AnnStorageProvisioner)
    • 这个函数调用可能是用于处理与CSI迁移相关的注解更新。具体实现细节不在代码段中给出,但可以推测它与CSI(Container Storage Interface)插件的迁移有关。
  5. 更新PVC对象
    • newClaim, err := ctrl.kubeClient.CoreV1().PersistentVolumeClaims(claim.Namespace).Update(context.TODO(), claimClone, metav1.UpdateOptions{})
    • 使用Kubernetes客户端(ctrl.kubeClient)将更新后的PVC对象(claimClone)发送回Kubernetes API服务器,以更新原始的PVC对象。这里使用了context.TODO()作为上下文,实际使用中应该提供一个具体的上下文。
  6. 处理更新后的PVC对象
    • _, err = ctrl.storeClaimUpdate(newClaim)
    • 调用storeClaimUpdate方法可能是为了在本地缓存或控制器内部状态中更新PVC对象的状态。
  7. 返回结果
    • 如果在更新PVC对象或处理更新后的对象时发生错误,方法将返回错误和可能部分更新的PVC对象。如果一切顺利,将返回更新后的PVC对象和nil错误。

总之,这段代码的主要目的是在不直接修改原始PVC对象的情况下,更新其存储提供者注解,并确保相关的内部状态或缓存也得到更新。

// setClaimProvisioner saves
// claim.Annotations[pvutil.AnnStorageProvisioner] = class.Provisioner
func (ctrl *PersistentVolumeController) setClaimProvisioner(claim *v1.PersistentVolumeClaim, provisionerName string) (*v1.PersistentVolumeClaim, error) {
    if val, ok := claim.Annotations[pvutil.AnnStorageProvisioner]; ok && val == provisionerName {
        // annotation is already set, nothing to do
        return claim, nil
    }
    // The volume from method args can be pointing to watcher cache. We must not
    // modify these, therefore create a copy.
    claimClone := claim.DeepCopy()
    metav1.SetMetaDataAnnotation(&claimClone.ObjectMeta, pvutil.AnnStorageProvisioner, provisionerName)
    updateMigrationAnnotations(ctrl.csiMigratedPluginManager, ctrl.translator, claimClone.Annotations, pvutil.AnnStorageProvisioner)
    newClaim, err := ctrl.kubeClient.CoreV1().PersistentVolumeClaims(claim.Namespace).Update(context.TODO(), claimClone, metav1.UpdateOptions{})
    if err != nil {
        return newClaim, err
    }
    _, err = ctrl.storeClaimUpdate(newClaim)
    if err != nil {
        return newClaim, err
    }
    return newClaim, nil
}
provisionClaimOperation()

这段代码是Kubernetes中动态卷供应流程的核心部分,它负责根据PVC和存储类的配置,使用相应的插件供应存储卷,并在Kubernetes中创建和绑定相应的PV对象。

  1. 获取PVC的类
    • 使用v1helper.GetPersistentVolumeClaimClass(claim)获取PVC的存储类。
  2. 日志记录
    • 使用klog.V(4).Infof(...)记录操作的开始,包括PVC的键和类。
  3. 检查插件和存储类
    • 从插件中获取插件名(pluginName)和从存储类中获取供应者名(provisionerName)。
  4. 设置PVC的供应者注解
    • 调用ctrl.setClaimProvisioner(claim, provisionerName)将供应者名设置为PVC的注解,以确保与外部供应者工作流程的一致性。
  5. 检查PV是否已存在
    • 使用ctrl.getProvisionedVolumeNameForClaim(claim)获取预期的PV名,并尝试从Kubernetes API中获取该PV。
    • 如果PV已存在,则记录日志并返回,不做进一步操作。
  6. 准备PVC引用
    • 使用ref.GetReference(scheme.Scheme, claim)获取PVC的引用,以便在后续步骤中使用。
  7. 收集供应选项
    • 创建一个vol.VolumeOptions对象,包含供应卷时需要的各种选项,如回收策略、挂载选项、云标签等。
  8. 检查挂载选项支持
    • 如果插件不支持挂载选项但存储类中指定了挂载选项,则记录错误事件并返回错误。
  9. 创建供应者
    • 使用plugin.NewProvisioner(options)创建一个供应者对象,用于实际供应存储卷。
  10. 处理选定的节点和拓扑
    • 如果PVC注解中指定了选定的节点,则尝试获取该节点对象。
    • 获取存储类中允许的拓扑。
  11. 供应卷
    • 调用provisioner.Provision(selectedNode, allowedTopologies)实际供应存储卷。
    • 如果供应失败,则记录错误事件、重新调度供应,并返回错误。
  12. 创建和绑定PV对象
    • 为新供应的卷创建一个Kubernetes PV对象。
    • 设置PV的名称、绑定到PVC的引用、状态、存储类名等。
    • 添加一些注解,如AnnBoundByControllerAnnDynamicallyProvisioned
  13. 保存PV对象
    • 尝试将PV对象保存到Kubernetes API中。
    • 如果保存失败,则重试多次,每次失败后等待一段时间。
  14. 处理保存失败
    • 如果多次尝试后仍然无法保存PV对象,则记录错误事件,并尝试删除新供应的存储卷。
    • 如果删除也失败,则记录另一个错误事件,提示用户手动删除孤立的卷。
  15. 成功供应
    • 如果PV对象成功保存,则记录成功消息并返回。
/ provisionClaimOperation provisions a volume. This method is running in
// standalone goroutine and already has all necessary locks.
func (ctrl *PersistentVolumeController) provisionClaimOperation(
    claim *v1.PersistentVolumeClaim,
    plugin vol.ProvisionableVolumePlugin,
    storageClass *storage.StorageClass) (string, error) {
    claimClass := v1helper.GetPersistentVolumeClaimClass(claim)
    klog.V(4).Infof("provisionClaimOperation [%s] started, class: %q", claimToClaimKey(claim), claimClass)
    // called from provisionClaim(), in this case, plugin MUST NOT be nil
    // NOTE: checks on plugin/storageClass has been saved
    pluginName := plugin.GetPluginName()
    provisionerName := storageClass.Provisioner
    // Add provisioner annotation to be consistent with external provisioner workflow
//用户创建sc,/pv controller负责添加相应的annotation,external-provisioner监听到之后,调用csi-plugin创建盘,创建好pv资源
//为pvc加claim.Annotation["k8s.../storege-provisioner"]=xxx
    newClaim, err := ctrl.setClaimProvisioner(claim, provisionerName)
    if err != nil {
        // Save failed, the controller will retry in the next sync
        klog.V(2).Infof("error saving claim %s: %v", claimToClaimKey(claim), err)
        return pluginName, err
    }
    claim = newClaim
    // internal provisioning
    //  A previous provisionClaimOperation may just have finished while we were waiting for
    //  the locks. Check that PV (with deterministic name) hasn't been provisioned
    //  yet.
//如果找到了pv,说明pv已经存在,跳过provisioner
    pvName := ctrl.getProvisionedVolumeNameForClaim(claim)
    volume, err := ctrl.kubeClient.CoreV1().PersistentVolumes().Get(context.TODO(), pvName, metav1.GetOptions{})
    if err != nil && !apierrors.IsNotFound(err) {
        klog.V(3).Infof("error reading persistent volume %q: %v", pvName, err)
        return pluginName, err
    }
    if err == nil && volume != nil {
        // Volume has been already provisioned, nothing to do.
        klog.V(4).Infof("provisionClaimOperation [%s]: volume already exists, skipping", claimToClaimKey(claim))
        return pluginName, err
    }
    // Prepare a claimRef to the claim early (to fail before a volume is
    // provisioned)
    claimRef, err := ref.GetReference(scheme.Scheme, claim)
    if err != nil {
        klog.V(3).Infof("unexpected error getting claim reference: %v", err)
        return pluginName, err
    }
    // Gather provisioning options
    tags := make(map[string]string)
    tags[CloudVolumeCreatedForClaimNamespaceTag] = claim.Namespace
    tags[CloudVolumeCreatedForClaimNameTag] = claim.Name
    tags[CloudVolumeCreatedForVolumeNameTag] = pvName
//收集pv/pvc信息到options里
    options := vol.VolumeOptions{
        PersistentVolumeReclaimPolicy: *storageClass.ReclaimPolicy,
        MountOptions:                  storageClass.MountOptions,
        CloudTags:                     &tags,
        ClusterName:                   ctrl.clusterName,
        PVName:                        pvName,
        PVC:                           claim,
        Parameters:                    storageClass.Parameters,
    }
    // Refuse to provision if the plugin doesn't support mount options, creation
    // of PV would be rejected by validation anyway
    if !plugin.SupportsMountOption() && len(options.MountOptions) > 0 {
        strerr := fmt.Sprintf("Mount options are not supported by the provisioner but StorageClass %q has mount options %v", storageClass.Name, options.MountOptions)
        klog.V(2).Infof("Mount options are not supported by the provisioner but claim %q's StorageClass %q has mount options %v", claimToClaimKey(claim), storageClass.Name, options.MountOptions)
        ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.ProvisioningFailed, strerr)
        return pluginName, fmt.Errorf("provisioner %q doesn't support mount options", plugin.GetPluginName())
    }
    // Provision the volume
    provisioner, err := plugin.NewProvisioner(options)
    if err != nil {
        strerr := fmt.Sprintf("Failed to create provisioner: %v", err)
        klog.V(2).Infof("failed to create provisioner for claim %q with StorageClass %q: %v", claimToClaimKey(claim), storageClass.Name, err)
        ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.ProvisioningFailed, strerr)
        return pluginName, err
    }
    var selectedNode *v1.Node = nil
//找到了调度的node
    if nodeName, ok := claim.Annotations[pvutil.AnnSelectedNode]; ok {
        selectedNode, err = ctrl.NodeLister.Get(nodeName)
        if err != nil {
            strerr := fmt.Sprintf("Failed to get target node: %v", err)
            klog.V(3).Infof("unexpected error getting target node %q for claim %q: %v", nodeName, claimToClaimKey(claim), err)
            ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.ProvisioningFailed, strerr)
            return pluginName, err
        }
    }
    allowedTopologies := storageClass.AllowedTopologies
    opComplete := util.OperationCompleteHook(plugin.GetPluginName(), "volume_provision")
//对应供应卷
    volume, err = provisioner.Provision(selectedNode, allowedTopologies)
    opComplete(&err)
    if err != nil {
        // Other places of failure have nothing to do with VolumeScheduling,
        // so just let controller retry in the next sync. We'll only call func
        // rescheduleProvisioning here when the underlying provisioning actually failed.
        ctrl.rescheduleProvisioning(claim)
        strerr := fmt.Sprintf("Failed to provision volume with StorageClass %q: %v", storageClass.Name, err)
        klog.V(2).Infof("failed to provision volume for claim %q with StorageClass %q: %v", claimToClaimKey(claim), storageClass.Name, err)
        ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.ProvisioningFailed, strerr)
        return pluginName, err
    }
    klog.V(3).Infof("volume %q for claim %q created", volume.Name, claimToClaimKey(claim))
    // Create Kubernetes PV object for the volume.
    if volume.Name == "" {
        volume.Name = pvName
    }
//创建出pv会与绑定pvc
    // Bind it to the claim
    volume.Spec.ClaimRef = claimRef
    volume.Status.Phase = v1.VolumeBound
    volume.Spec.StorageClassName = claimClass
    // Add AnnBoundByController (used in deleting the volume)
//为创建出来的pv关联pvc对象(claimRef),尝试创建pv对象(重复多次)
    metav1.SetMetaDataAnnotation(&volume.ObjectMeta, pvutil.AnnBoundByController, "yes")
    metav1.SetMetaDataAnnotation(&volume.ObjectMeta, pvutil.AnnDynamicallyProvisioned, plugin.GetPluginName())
    // Try to create the PV object several times
    for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ {
        klog.V(4).Infof("provisionClaimOperation [%s]: trying to save volume %s", claimToClaimKey(claim), volume.Name)
        var newVol *v1.PersistentVolume
        if newVol, err = ctrl.kubeClient.CoreV1().PersistentVolumes().Create(context.TODO(), volume, metav1.CreateOptions{}); err == nil || apierrors.IsAlreadyExists(err) {
            // Save succeeded.
            if err != nil {
                klog.V(3).Infof("volume %q for claim %q already exists, reusing", volume.Name, claimToClaimKey(claim))
                err = nil
            } else {
                klog.V(3).Infof("volume %q for claim %q saved", volume.Name, claimToClaimKey(claim))
                _, updateErr := ctrl.storeVolumeUpdate(newVol)
                if updateErr != nil {
                    // We will get an "volume added" event soon, this is not a big error
                    klog.V(4).Infof("provisionClaimOperation [%s]: cannot update internal cache: %v", volume.Name, updateErr)
                }
            }
            break
        }
        // Save failed, try again after a while.
        klog.V(3).Infof("failed to save volume %q for claim %q: %v", volume.Name, claimToClaimKey(claim), err)
        time.Sleep(ctrl.createProvisionedPVInterval)
    }
//如果创建pv失败,则尝试调用delete方法删除创建volumn资源
    if err != nil {
        // Save failed. Now we have a storage asset outside of Kubernetes,
        // but we don't have appropriate PV object for it.
        // Emit some event here and try to delete the storage asset several
        // times.
        strerr := fmt.Sprintf("Error creating provisioned PV object for claim %s: %v. Deleting the volume.", claimToClaimKey(claim), err)
        klog.V(3).Info(strerr)
        ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.ProvisioningFailed, strerr)
        var deleteErr error
        var deleted bool
        for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ {
            _, deleted, deleteErr = ctrl.doDeleteVolume(volume)
            if deleteErr == nil && deleted {
                // Delete succeeded
                klog.V(4).Infof("provisionClaimOperation [%s]: cleaning volume %s succeeded", claimToClaimKey(claim), volume.Name)
                break
            }
            if !deleted {
                // This is unreachable code, the volume was provisioned by an
                // internal plugin and therefore there MUST be an internal
                // plugin that deletes it.
                klog.Errorf("Error finding internal deleter for volume plugin %q", plugin.GetPluginName())
                break
            }
            // Delete failed, try again after a while.
            klog.V(3).Infof("failed to delete volume %q: %v", volume.Name, deleteErr)
            time.Sleep(ctrl.createProvisionedPVInterval)
        }
        if deleteErr != nil {
            // Delete failed several times. There is an orphaned volume and there
            // is nothing we can do about it.
            strerr := fmt.Sprintf("Error cleaning provisioned volume for claim %s: %v. Please delete manually.", claimToClaimKey(claim), deleteErr)
            klog.V(2).Info(strerr)
            ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.ProvisioningCleanupFailed, strerr)
        }
    } else {
        klog.V(2).Infof("volume %q provisioned for claim %q", volume.Name, claimToClaimKey(claim))
        msg := fmt.Sprintf("Successfully provisioned volume %s using %s", volume.Name, plugin.GetPluginName())
        ctrl.eventRecorder.Event(claim, v1.EventTypeNormal, events.ProvisioningSucceeded, msg)
    }
    return pluginName, nil
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值