claimWorker()
claim worker中循环执行workFunc()
- claim worker从claimQueue中取数据,也就是取出的都是PVC
- workFunc首先从队列中取出一个obj,然后拿name去informer缓存中尝试获取
- 如果在informer缓存。说明不是删除事件,执行
updateClaim()函数 - 如果不在informer缓存中。
- 如果pvc控制器的缓存中不存在,说明pvc已经被删除,打印日志。
- 如果pvc控制器的缓存中存在,就调用
deleteClaim()在缓存中删除这个pvc 并且将对应的pv加入volume队列。
- 如果在informer缓存。说明不是删除事件,执行
// 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()
-
存储新的PVC版本到缓存:
new, err := ctrl.storeClaimUpdate(claim)- 这行代码尝试将传入的PVC更新到内部缓存中。如果这是一个新的版本(即之前没有这个PVC的更新或者这是该PVC的一个新版本),
new将返回true;否则返回false。err用于捕获过程中可能发生的错误。
-
检查是否为新版本:
if !new { return }- 如果
new为false,即这不是一个新的PVC版本,则直接返回,不继续处理。
-
同步PVC:
err = ctrl.syncClaim(claim)- 尝试同步PVC的状态。这可能涉及与底层的存储系统交互,确保PVC的状态与期望的状态一致。
-
同步错误处理:
-
如果同步过程中出现错误,会进行错误检查和处理。
-
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()
- 更新迁移注解:
- 调用
updateClaimMigrationAnnotations(claim)方法尝试更新PVC上的迁移注解。这个方法可能会返回一个新的PVC对象(newClaim),如果注解有更新,并且这个新的PVC对象需要被保存到API服务器中。 - 如果在更新注解时发生错误(
err != nil),方法将返回这个错误。这意味着当前的PVC状态不会被进一步处理,而是会在下次调用syncClaim时再次尝试。
- 调用
- 处理PVC的绑定状态:
- 接下来,代码检查PVC上是否存在
pvutil.AnnBindCompleted注解。这个注解通常用来表示PVC是否已经完成绑定到一个PersistentVolume(PV)。 - 如果PVC没有完成绑定(即不存在
AnnBindCompleted注解),则调用syncUnboundClaim(claim)方法来处理未绑定的PVC。这可能包括寻找合适的PV进行绑定等操作。 - 如果PVC已经完成绑定(即存在
AnnBindCompleted注解),则调用syncBoundClaim(claim)方法来处理已绑定的PVC。这可能包括检查PVC和PV的状态是否一致,以及处理任何需要的后续操作。
- 接下来,代码检查PVC上是否存在
总结来说,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()
- 检查PVC是否已绑定:
if claim.Spec.VolumeName == ""- 首先检查PVC的
Spec.VolumeName是否为空。如果为空,表示这个PVC之前绑定过,但现在不再绑定到任何PersistentVolume(PV)。
- 更新PVC状态为Lost:
- 如果PVC不再绑定到任何PV,则调用
updateClaimStatusWithEvent方法更新PVC的状态为ClaimLost,并记录一个警告事件,表明绑定的PV已经丢失,数据也可能丢失。
- 如果PVC不再绑定到任何PV,则调用
- 从存储中获取PV:
obj, found, err := ctrl.volumes.store.GetByKey(claim.Spec.VolumeName)- 使用PVC的
VolumeName从存储中获取对应的PV对象。如果获取过程中出现错误,返回错误。
- 检查PV是否存在:
- 如果未找到PV(
!found),则更新PVC状态为ClaimLost,并记录一个警告事件,表明绑定的PV不存在,数据可能丢失。
- 如果未找到PV(
- 类型断言:
volume, ok := obj.(*v1.PersistentVolume)- 对获取到的对象进行类型断言,确保其类型为
*v1.PersistentVolume。如果断言失败,返回错误。
- 记录日志:
- 使用
klog.V(4).Infof记录日志,表明正在同步已绑定的PVC,并找到了对应的PV。
- 使用
- 检查PV的ClaimRef:
if volume.Spec.ClaimRef == nil- 如果PV的
Spec.ClaimRef为空,表示PV当前未绑定到任何PVC,或者控制器尚未收到更新的PV信息。此时,尝试重新绑定PV和PVC。
- 重新绑定PV和PVC:
- 调用
ctrl.bind(volume, claim)方法尝试重新绑定PV和PVC。如果绑定失败,返回错误。
- 调用
- 检查PV的ClaimRef是否与PVC匹配:
else if volume.Spec.ClaimRef.UID == claim.UID- 如果PV的
Spec.ClaimRef.UID与PVC的UID相匹配,表示PVC已正确绑定到PV。此时,仍然调用ctrl.bind方法(大多数情况下,这个方法此时不会做任何操作,因为所有状态应该已经设置好了)。
- 处理绑定冲突:
- 如果PV的
Spec.ClaimRef.UID与PVC的UID不匹配,表示存在绑定冲突(即两个PVC绑定到了同一个PV)。此时,更新PVC的状态为ClaimLost,并记录一个警告事件,表明存在绑定错误。
- 如果PV的
总结来说,这段代码的主要作用是确保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()
- 绑定 PV 到 PVC:
- 调用
ctrl.bindVolumeToClaim(volume, claim)尝试将 PV 绑定到 PVC。如果操作失败,记录错误日志并返回错误。 - 如果绑定成功,更新
volume变量为updatedVolume,以反映绑定后的状态。
- 调用
- 更新 PV 状态:
- 调用
ctrl.updateVolumePhase(volume, v1.VolumeBound, "")更新 PV 的状态为Bound,表示它已被绑定到一个 PVC。如果更新失败,记录错误日志并返回错误。 - 更新
volume变量以反映状态更新后的 PV。
- 调用
- 绑定 PVC 到 PV:
- 调用
ctrl.bindClaimToVolume(claim, volume)尝试将 PVC 绑定到 PV。如果操作失败,记录错误日志并返回错误。 - 如果绑定成功,更新
claim变量为updatedClaim。
- 调用
- 更新 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完成状态更新和缓存同步。
- 获取绑定信息:
volumeClone, dirty, err := pvutil.GetBindVolumeToClaim(volume, claim): 这行代码调用pvutil.GetBindVolumeToClaim函数,尝试将PVC的信息绑定到PV上。该函数返回三个值:一个PV的克隆(volumeClone),一个布尔值(dirty)表示PV是否被修改,以及一个error对象。如果发生错误,方法将返回nil和错误对象。
- 更新PV:
if dirty {: 如果dirty为true,表示PV在绑定过程中被修改了。return ctrl.updateBindVolumeToClaim(volumeClone, true): 调用ctrl的updateBindVolumeToClaim方法,传入修改后的PV克隆和true(可能表示需要强制更新或确认更新)。该方法负责更新PV的状态,并返回更新后的PV和可能的错误。
- 未修改时的处理:
- 如果
dirty为false,表示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()
- 初始化
dirty标志:dirty初始化为false,用于标记PV是否被修改。
- 检查PV是否已绑定到PVC:
- 使用
IsVolumeBoundToClaim(volume, claim)函数检查PV是否已经绑定到PVC。 - 如果未绑定,设置
shouldSetBoundByController为true,表示需要通过控制器来绑定。
- 使用
- 创建PV的深度复制:
- 使用
volume.DeepCopy()创建PV的一个深度复制,以避免直接修改原始对象。
- 使用
- 绑定PV到PVC:
- 检查PV的
Spec.ClaimRef是否为空,或者是否与给定的PVC不匹配(通过名称、命名空间和UID判断)。 - 如果不匹配,使用
reference.GetReference(scheme.Scheme, claim)获取PVC的引用,并将其设置为PV复制的Spec.ClaimRef。 - 设置
dirty为true,表示PV已被修改。
- 检查PV的
- 设置
AnnBoundByController注解:- 如果
shouldSetBoundByController为true且PV复制对象上没有AnnBoundByController注解,则添加该注解并设置其值为 “yes”。 - 同样,设置
dirty为true。
- 如果
- 返回结果:
- 返回PV的深度复制对象、
dirty标志和nil错误(如果没有发生错误)。
- 返回PV的深度复制对象、
总结
这个函数的核心功能是将一个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()
- 检查当前 PV 状态是否已与目标状态一致,若一致则直接返回。
- 深拷贝 PV 对象,避免修改原对象。
- 更新状态和消息。
- 调用 Kubernetes API 更新 PV 状态。
- 同步更新本地缓存。
- 记录日志。
// 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 并同步本地缓存。
-
检查PVC是否已绑定:
- 通过比较
volume.Name和claim.Spec.VolumeName来判断PVC是否已经绑定到某个PV。如果它们不相等,说明需要绑定(shouldBind设置为true)。
- 通过比较
-
创建PVC的深拷贝:
- 由于传入的PVC可能指向watcher的缓存,直接修改它可能会导致问题。因此,这里使用
DeepCopy方法创建了一个PVC的副本(claimClone),后续的操作都将在这个副本上进行。
- 由于传入的PVC可能指向watcher的缓存,直接修改它可能会导致问题。因此,这里使用
-
执行绑定操作:
- 如果
shouldBind为true,则进行绑定操作:- 设置
claimClone.Spec.VolumeName为volume.Name,将PVC绑定到PV。 - 如果PVC的元数据中没有
AnnBoundByController注解,则添加这个注解,值为"yes",表示这个绑定操作是由控制器完成的。
- 设置
- 如果
-
设置
AnnBindCompleted注解:- 无论是否进行了绑定操作,如果PVC的元数据中没有
AnnBindCompleted注解,则添加这个注解,值为"yes",表示绑定操作已经完成。
- 无论是否进行了绑定操作,如果PVC的元数据中没有
-
更新PVC:
- 如果进行了任何修改(即
dirty为true),则需要更新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 状态,并同步本地缓存。
- 深拷贝:为了避免直接修改原始PVC对象,函数创建了
claim的一个深拷贝claimClone。 - 状态更新:
- 如果PVC的当前状态与要设置的状态不同,则更新
claimClone的状态,并将dirty标志设置为true,表示有更改。 - 如果
volume为nil,表示PVC不与任何PV关联,因此需要清除AccessModes和Capacity。 - 如果
volume不为nil,则需要根据volume的信息更新claimClone的AccessModes和Capacity。特别注意,只有在PVC状态发生变化时(即之前不是这个状态),才更新Capacity。
- 如果PVC的当前状态与要设置的状态不同,则更新
- 无更改处理:如果没有任何更改(
dirty为false),则记录一条日志并返回原始的PVC对象和nil错误。 - 更新状态:如果有更改,使用Kubernetes客户端(
ctrl.kubeClient)调用UpdateStatus方法更新PVC的状态。这里使用了context.TODO()作为上下文,通常在实际调用中需要提供一个具体的上下文。 - 内部缓存更新:更新内部缓存中的PVC信息。这是通过调用
ctrl.storeClaimUpdate方法实现的。 - 日志记录和返回:最后,记录一条日志表明PVC已经进入了新的状态,并返回更新后的PVC对象和可能的错误。
关键点
- 深拷贝:为了避免对原始对象的意外修改,使用了深拷贝。
- 条件更新:根据
volume是否为nil以及PVC的当前状态,有条件地更新AccessModes和Capacity。 - 错误处理:在关键操作(如更新状态和内部缓存)中进行了错误检查和处理。
这段代码是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,以便后续再次尝试匹配。
- 如果找到匹配的PV:
- 延迟绑定检查:通过
- 如果
claim.Spec.VolumeName不为空,表示用户指定了特定的PV。- 查找指定的PV:通过
ctrl.volumes.store.GetByKey尝试获取用户指定的PV。- 如果PV不存在:
- 更新PVC的状态为
Pending,以便后续再次尝试。
- 更新PVC的状态为
- 如果PV存在但不是
PersistentVolume类型:- 返回错误。
- 如果PV存在且未被绑定:
- 检查PV是否满足PVC的要求,如果满足则调用
ctrl.bind进行绑定。
- 检查PV是否满足PVC的要求,如果满足则调用
- 如果PV已被绑定:
- 检查PV是否被当前PVC绑定,如果是,则完成绑定操作。
- 如果PV被其他PVC绑定,且没有
AnnBoundByController注解,记录事件并更新PVC状态为Pending。 - 如果PV被其他PVC绑定,且有
AnnBoundByController注解,这是一个异常情况,返回错误。
- 如果PV不存在:
- 查找指定的PV:通过
关键函数和概念
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()
-
检查动态供给是否启用:
if !ctrl.enableDynamicProvisioning { return nil }- 如果
enableDynamicProvisioning为false,则直接返回nil,表示不进行动态供给。
- 如果
-
定义操作名称:
opName := fmt.Sprintf("provision-%s[%s]", claimToClaimKey(claim), string(claim.UID))- 构造一个操作名称,用于后续的记录和调试。
-
查找可供给的插件:
plugin, storageClass, err := ctrl.findProvisionablePlugin(claim)- 调用
findProvisionablePlugin方法查找适合该PVC的供给插件和存储类。
- 调用
-
处理查找插件的错误:
- 如果
err不为nil,表示查找插件失败,记录事件和日志,并返回nil。这里返回nil是因为对于外部供给器,即使查找失败,控制器也会在后续的syncUnboundClaim调用中重试供给。
- 如果
-
调度供给操作:
ctrl.scheduleOperation(opName, func() error { // ... })- 使用
scheduleOperation方法调度一个供给操作。该方法接受一个操作名称和一个函数作为参数,函数内部执行实际的供给逻辑。
- 使用
-
供给逻辑:
- 在供给函数内部,首先为供给操作在缓存中添加一个开始时间戳条目(如果尚不存在)。
- 根据
plugin是否为nil,选择调用provisionClaimOperationExternal或provisionClaimOperation方法进行供给。 - 如果供给过程中发生错误,记录一个错误计数指标。时间戳条目将保留在缓存中,直到成功绑定为止。
-
返回:
- 无论供给操作是否成功,
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()
- 获取PVC的类:
claimClass := v1helper.GetPersistentVolumeClaimClass(claim)- 通过
v1helper.GetPersistentVolumeClaimClass函数获取PVC的存储类名。
BetaStorageClassAnnotation = "volume.beta.kubernetes.io/storage-class"
- 设置Provisioner名称:
- 初始时,
provisionerName被设置为storageClass.Provisioner。 - 如果CSI迁移插件管理器对于该Provisioner启用了迁移,则通过
ctrl.translator.GetCSINameFromInTreeName获取迁移后的CSI插件名称,并更新provisionerName。 - 如果获取CSI名称时发生错误,记录错误日志,发送事件,并返回错误。
- 初始时,
- 更新PVC的Provisioner注解:
- 调用
ctrl.setClaimProvisioner(claim, provisionerName)为PVC设置Provisioner注解,以便外部Provisioner知道何时开始工作。 - 如果保存失败,记录错误日志,并返回错误。
- 调用
- 记录等待外部Provisioner的日志和事件:
- 构建一条消息,表明正在等待外部Provisioner或系统管理员手动创建卷。
- 使用
ctrl.eventRecorder.Event记录一个正常事件,表明正在等待外部Provisioner完成。 - 使用
klog.V(3).Infof记录等待的日志。
- 返回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”
- 检查注解:
if val, ok := claim.Annotations[pvutil.AnnStorageProvisioner]; ok && val == provisionerName { return claim, nil }- 这段代码首先检查PVC对象的注解中是否已经存在存储提供者(
pvutil.AnnStorageProvisioner)的注解,并且其值是否与provisionerName相同。如果是,说明不需要做任何更新,直接返回原始的PVC对象和nil错误。
- 创建PVC对象的深拷贝:
claimClone := claim.DeepCopy()- 由于原始PVC对象可能来自于监视器的缓存,直接修改它可能会导致问题。因此,这里创建了一个PVC对象的深拷贝(
claimClone),以便在不影响原始对象的情况下进行修改。
- 设置注解:
metav1.SetMetaDataAnnotation(&claimClone.ObjectMeta, pvutil.AnnStorageProvisioner, provisionerName)- 在拷贝的PVC对象上设置存储提供者注解,注解的键是
pvutil.AnnStorageProvisioner,值是provisionerName。
- 更新迁移注解:
updateMigrationAnnotations(ctrl.csiMigratedPluginManager, ctrl.translator, claimClone.Annotations, pvutil.AnnStorageProvisioner)- 这个函数调用可能是用于处理与CSI迁移相关的注解更新。具体实现细节不在代码段中给出,但可以推测它与CSI(Container Storage Interface)插件的迁移有关。
- 更新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()作为上下文,实际使用中应该提供一个具体的上下文。
- 处理更新后的PVC对象:
_, err = ctrl.storeClaimUpdate(newClaim)- 调用
storeClaimUpdate方法可能是为了在本地缓存或控制器内部状态中更新PVC对象的状态。
- 返回结果:
- 如果在更新PVC对象或处理更新后的对象时发生错误,方法将返回错误和可能部分更新的PVC对象。如果一切顺利,将返回更新后的PVC对象和
nil错误。
- 如果在更新PVC对象或处理更新后的对象时发生错误,方法将返回错误和可能部分更新的PVC对象。如果一切顺利,将返回更新后的PVC对象和
总之,这段代码的主要目的是在不直接修改原始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对象。
- 获取PVC的类:
- 使用
v1helper.GetPersistentVolumeClaimClass(claim)获取PVC的存储类。
- 使用
- 日志记录:
- 使用
klog.V(4).Infof(...)记录操作的开始,包括PVC的键和类。
- 使用
- 检查插件和存储类:
- 从插件中获取插件名(
pluginName)和从存储类中获取供应者名(provisionerName)。
- 从插件中获取插件名(
- 设置PVC的供应者注解:
- 调用
ctrl.setClaimProvisioner(claim, provisionerName)将供应者名设置为PVC的注解,以确保与外部供应者工作流程的一致性。
- 调用
- 检查PV是否已存在:
- 使用
ctrl.getProvisionedVolumeNameForClaim(claim)获取预期的PV名,并尝试从Kubernetes API中获取该PV。 - 如果PV已存在,则记录日志并返回,不做进一步操作。
- 使用
- 准备PVC引用:
- 使用
ref.GetReference(scheme.Scheme, claim)获取PVC的引用,以便在后续步骤中使用。
- 使用
- 收集供应选项:
- 创建一个
vol.VolumeOptions对象,包含供应卷时需要的各种选项,如回收策略、挂载选项、云标签等。
- 创建一个
- 检查挂载选项支持:
- 如果插件不支持挂载选项但存储类中指定了挂载选项,则记录错误事件并返回错误。
- 创建供应者:
- 使用
plugin.NewProvisioner(options)创建一个供应者对象,用于实际供应存储卷。
- 使用
- 处理选定的节点和拓扑:
- 如果PVC注解中指定了选定的节点,则尝试获取该节点对象。
- 获取存储类中允许的拓扑。
- 供应卷:
- 调用
provisioner.Provision(selectedNode, allowedTopologies)实际供应存储卷。 - 如果供应失败,则记录错误事件、重新调度供应,并返回错误。
- 调用
- 创建和绑定PV对象:
- 为新供应的卷创建一个Kubernetes PV对象。
- 设置PV的名称、绑定到PVC的引用、状态、存储类名等。
- 添加一些注解,如
AnnBoundByController和AnnDynamicallyProvisioned。
- 保存PV对象:
- 尝试将PV对象保存到Kubernetes API中。
- 如果保存失败,则重试多次,每次失败后等待一段时间。
- 处理保存失败:
- 如果多次尝试后仍然无法保存PV对象,则记录错误事件,并尝试删除新供应的存储卷。
- 如果删除也失败,则记录另一个错误事件,提示用户手动删除孤立的卷。
- 成功供应:
- 如果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
}
4165

被折叠的 条评论
为什么被折叠?



