if metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnBoundByController) {

klog.V(4).Infof(“synchronizing PersistentVolume[%s]: volume not bound yet, waiting for syncClaim to fix it”, volume.Name)

} else {

klog.V(4).Infof(“synchronizing PersistentVolume[%s]: volume was bound and got unbound (by user?), waiting for syncClaim to fix it”, volume.Name)

}

ctrl.claimQueue.Add(claimToClaimKey(claim))

return nil

// 已经绑定更新状态status phase为Bound

} else if claim.Spec.VolumeName == volume.Name {

// Volume is bound to a claim properly, update status if necessary

klog.V(4).Infof(“synchronizing PersistentVolume[%s]: all is bound”, volume.Name)

if _, err = ctrl.updateVolumePhase(volume, v1.VolumeBound, “”); err != nil {

// Nothing was saved; we will fall back into the same

// condition in the next call to this method

return err

}

return nil

// PV绑定到PVC上,但是PVC被绑定到其他PV上,重置

} else {

// Volume is bound to a claim, but the claim is bound elsewhere

if metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnDynamicallyProvisioned) && volume.Spec.PersistentVolumeReclaimPolicy == v1.PersistentVolumeReclaimDelete {

if volume.Status.Phase != v1.VolumeReleased && volume.Status.Phase != v1.VolumeFailed {

klog.V(2).Infof(“dynamically volume %q is released and it will be deleted”, volume.Name)

if volume, err = ctrl.updateVolumePhase(volume, v1.VolumeReleased, “”); err != nil {

return err

}

}

if err = ctrl.reclaimVolume(volume); err != nil {

return err

}

return nil

} else {

if metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnBoundByController) {

klog.V(4).Infof(“synchronizing PersistentVolume[%s]: volume is bound by controller to a claim that is bound to another volume, unbinding”, volume.Name)

if err = ctrl.unbindVolume(volume); err != nil {

return err

}

return nil

} else {

klog.V(4).Infof(“synchronizing PersistentVolume[%s]: volume is bound by user to a claim that is bound to another volume, waiting for the claim to get unbound”, volume.Name)

if err = ctrl.unbindVolume(volume); err != nil {

return err

}

return nil

}

}

}

}

}

这个方法有点长,我们一步步分析:

这个方法首先会校验一个ClaimRef有没有被设置,因为如果一个PV被绑定了,那么它的ClaimRef属性是会被赋值的,我们可以使用kubectl edit pv mongodb-pv 进入到实例中查看当前的PV属性,会发现:

claimRef:

apiVersion: v1

kind: PersistentVolumeClaim

name: mongodb-pvc

namespace: default

resourceVersion: “824043”

uid: 5cf34ad0-2181-4d99-9875-0d4559e58f42

所以如果这个属性为空,那么需要更新PV的状态为Available。

如果ClaimRef不为空,接下来会校验UID属性,UID为空说明PV绑定了PVC,但是PVC却没有绑定PV,所以需要重新设置PV的状态为Available;

然后获取PV对应的PVC,如果在PVC集合里没有找到对应的PVC,那么为了防止本地缓存还未刷新,所以再通过apiserver再去找一下,然后给found变量打上标记;

如果找到了对应的PVC,那么需要比较一下UID是否相等,如果不相等,那么说明不是被绑定的那个PVC,可以认为PVC是被删除了,那么需要更新释放PV,将PV的状态改为Released;

然后会调用reclaimVolume方法,这个方法里面会根据persistentVolumeReclaimPolicy配置做相应的处理:

PersistentVolumeController#reclaimVolume

func (ctrl *PersistentVolumeController) reclaimVolume(volume *v1.PersistentVolume) error {

switch volume.Spec.PersistentVolumeReclaimPolicy {

//这个策略允许手动回收资源,当PVC被删除后,PV仍然可以存在,管理员可以手动的执行删除PV

case v1.PersistentVolumeReclaimRetain:

klog.V(4).Infof(“reclaimVolume[%s]: policy is Retain, nothing to do”, volume.Name)

//回收PV,如果没有pod在使用PV,那么将该PV的状态设置为Available

case v1.PersistentVolumeReclaimRecycle:

ctrl.scheduleOperation(opName, func() error {

ctrl.recycleVolumeOperation(volume)

return nil

})

//这个策略会在PVC被删除之后,连带将PV以及PV管理的存储资源也删除

case v1.PersistentVolumeReclaimDelete:

ctrl.scheduleOperation(opName, func() error {

_, err := ctrl.deleteVolumeOperation(volume)

if err != nil {

metrics.RecordMetric(volume.Name, &ctrl.operationTimestamps, err)

}

return err

})

default:

}

return nil

}

这个方法里面是用了一个switch case来处理PersistentVolumeReclaimPolicy策略,如果是Retain策略,那么需要手动执行删除,这里只记录了一个log;如果是Recycle则调用recycleVolumeOperation执行解绑操作;如果是Delete则调用deleteVolumeOperation方法将对应的PV删除。

下面我们来挑deleteVolumeOperation看一下这个方法的具体实现:

func (ctrl *PersistentVolumeController) deleteVolumeOperation(volume *v1.PersistentVolume) (string, error) {

klog.V(4).Infof(“deleteVolumeOperation [%s] started”, volume.Name)

//这里先读取最新的PV实例

newVolume, err := ctrl.kubeClient.CoreV1().PersistentVolumes().Get(context.TODO(), volume.Name, metav1.GetOptions{})

if err != nil {

klog.V(3).Infof(“error reading persistent volume %q: %v”, volume.Name, err)

return “”, nil

}

//如果已经被删除了,直接返回

if newVolume.GetDeletionTimestamp() != nil {

klog.V(3).Infof(“Volume %q is already being deleted”, volume.Name)

return “”, nil

}

//看一下是否还能找得到对应的PVC

needsReclaim, err := ctrl.isVolumeReleased(newVolume)

if err != nil {

klog.V(3).Infof(“error reading claim for volume %q: %v”, volume.Name, err)

return “”, nil

}

//如果还有PVC与之关联,那么就不能删除这个PV

if !needsReclaim {

klog.V(3).Infof(“volume %q no longer needs deletion, skipping”, volume.Name)

return “”, nil

}

//调用相应的plugin删除PV

pluginName, deleted, err := ctrl.doDeleteVolume(volume)

return pluginName, nil

}

可见在执行删除的时候先会进行一系列的校验,会去确认这个PV是否已手动删除、PV所对应的PVC是否还存在然后才调用对应的插件执行删除。

我们继续回到PersistentVolumeController的syncVolume方法中。对claim进行校验之后会继续检查VolumeName是否为空,这种情况是表明正在绑定中;

如果PVC的VolumeName等于PV的name,那么说明已经绑定,那么更新一下状态为Bound;否则表示PV绑定到PVC上,但是PVC被绑定到其他PV上,检查一下是否是dynamically provisioned自动生成的,如果是的话就释放这个PV;如果是手动创建的PV,那么调用unbindVolume进行解绑。

到这里我们volumeWorker已经看完了,接下来看一下claimWorker

claimWorker

和volumeWorker一样,claimWorker也在一个循环里不断的获取PVC,然后调用updateClaim方法进入到syncClaim进行具体的操作:

PersistentVolumeController#syncClaim

func (ctrl *PersistentVolumeController) syncClaim(claim *v1.PersistentVolumeClaim) error {

klog.V(4).Infof(“synchronizing PersistentVolumeClaim[%s]: %s”, claimToClaimKey(claim), getClaimStatusForLogging(claim))

newClaim, err := ctrl.updateClaimMigrationAnnotations(claim)

if err != nil {

return err

}

claim = newClaim

//根据当前对象中的注解决定调用逻辑

if !metav1.HasAnnotation(claim.ObjectMeta, pvutil.AnnBindCompleted) {

//处理未绑定的pvc

return ctrl.syncUnboundClaim(claim)

} else {

//处理已经绑定的pvc

return ctrl.syncBoundClaim(claim)

}

}

这个方法会从缓存里面重新获取一些PVC,然后根据PVC的注解决定调用逻辑。

下面我先从syncUnboundClaim开始,方法比较长,分成两部分:

func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVolumeClaim) error {

//说明pvc处于pending状态,没有完成绑定操作

if claim.Spec.VolumeName == “” {

// User did not care which PV they get.

// 是否是延迟绑定

delayBinding, err := pvutil.IsDelayBindingMode(claim, ctrl.classLister)

if err != nil {

return err

}

// [Unit test set 1]

//根据声明的PVC设置的字段找到对应的PV

volume, err := ctrl.volumes.findBestMatchForClaim(claim, delayBinding)

if err != nil {

klog.V(2).Infof(“synchronizing unbound PersistentVolumeClaim[%s]: Error finding PV for claim: %v”, claimToClaimKey(claim), err)

return fmt.Errorf(“Error finding PV for claim %q: %v”, claimToClaimKey(claim), err)

}

//如果没有可用volume情况

if volume == nil {

klog.V(4).Infof(“synchronizing unbound PersistentVolumeClaim[%s]: no volume found”, claimToClaimKey(claim))

switch {

case delayBinding && !pvutil.IsDelayBindingProvisioning(claim):

if err = ctrl.emitEventForUnboundDelayBindingClaim(claim); err != nil {

return err

}

// 找对应的storageclass

case v1helper.GetPersistentVolumeClaimClass(claim) != “”:

//根据对应的插件创建PV

if err = ctrl.provisionClaim(claim); err != nil {

return err

}

return nil

default:

ctrl.eventRecorder.Event(claim, v1.EventTypeNormal, events.FailedBinding, “no persistent volumes available for this claim and no storage class is set”)

}

// 等待下次循环再查找匹配的PV进行绑定

if _, err = ctrl.updateClaimStatus(claim, v1.ClaimPending, nil); err != nil {

return err

}

return nil

// 找到volume,进行绑定操作

} else /* pv != nil */ {

claimKey := claimToClaimKey(claim)

klog.V(4).Infof(“synchronizing unbound PersistentVolumeClaim[%s]: volume %q found: %s”, claimKey, volume.Name, getVolumeStatusForLogging(volume))

//执行绑定操作

if err = ctrl.bind(volume, claim); err != nil {

metrics.RecordMetric(claimKey, &ctrl.operationTimestamps, err)

return err

}

metrics.RecordMetric(claimKey, &ctrl.operationTimestamps, nil)

return nil

}

}

}

这个方法首先会校验VolumeName是否为空,如果为空,那么检查一下是否设置了延迟绑定

然后去PV集合里面查看是否能找到符合要求的PV,如果没有可用的PV,那么看一下是否是dynamically provisioned,如果是的话异步创建PV后设置PVC状态为Binding,然后等待下次循环再查找匹配的PV进行绑定;

如果找到相匹配的PV,那么调用bind方法执行绑定,bind方法就不贴出来了,里面会更新ClaimRef字段、status phase、VolumeName等。

接下来看看syncUnboundClaim下半部分代码:

func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *v1.PersistentVolumeClaim) error {

} else /* pvc.Spec.VolumeName != nil */ {

klog.V(4).Infof(“synchronizing unbound PersistentVolumeClaim[%s]: volume %q requested”, claimToClaimKey(claim), claim.Spec.VolumeName)

//若VolumeName不为空,那么找到相应的PV

obj, found, err := ctrl.volumes.store.GetByKey(claim.Spec.VolumeName)

if err != nil {

return err

}

//说明对应的PV已经不存在了,更新状态为Pending

if !found {

klog.V(4).Infof(“synchronizing unbound PersistentVolumeClaim[%s]: volume %q requested and not found, will try again next time”, claimToClaimKey(claim), claim.Spec.VolumeName)

if _, err = ctrl.updateClaimStatus(claim, v1.ClaimPending, nil); err != nil {

return err

}

return nil

} else {

volume, ok := obj.(*v1.PersistentVolume)

if !ok {

return fmt.Errorf(“Cannot convert object from volume cache to volume %q!?: %+v”, claim.Spec.VolumeName, obj)

}

klog.V(4).Infof(“synchronizing unbound PersistentVolumeClaim[%s]: volume %q requested and found: %s”, claimToClaimKey(claim), claim.Spec.VolumeName, getVolumeStatusForLogging(volume))

//PV的ClaimRef字段为空,那么调用bind执行绑定操作

if volume.Spec.ClaimRef == nil {

klog.V(4).Infof(“synchronizing unbound PersistentVolumeClaim[%s]: volume is unbound, binding”, claimToClaimKey(claim))

if err = checkVolumeSatisfyClaim(volume, claim); err != nil {

klog.V(4).Infof(“Can’t bind the claim to volume %q: %v”, volume.Name, err)

msg := fmt.Sprintf(“Cannot bind to requested volume %q: %s”, volume.Name, err)

ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.VolumeMismatch, msg)

if _, err = ctrl.updateClaimStatus(claim, v1.ClaimPending, nil); err != nil {

return err

}

} else if err = ctrl.bind(volume, claim); err != nil {

return err

}

return nil

// 这里主要校验volume是否已绑定了别的PVC,如果没有的话,执行绑定

} else if pvutil.IsVolumeBoundToClaim(volume, claim) {

klog.V(4).Infof(“synchronizing unbound PersistentVolumeClaim[%s]: volume already bound, finishing the binding”, claimToClaimKey(claim))

if err = ctrl.bind(volume, claim); err != nil {

return err

}

return nil

} else {

//这里是PV绑定了其他PVC,等待下次循环再重试

}

}

}

}

这里说明VolumeName不为空,那么自然需要取出对应的PV,如果对应的PV已经不存在了,那么等待下次调用再执行绑定;

如果找到对应的PV,那么如果ClaimRef字段为空,那么调用bind执行绑定操作;

如果ClaimRef不为空,那么调用IsVolumeBoundToClaim校验一下PV是否已绑定了别的PVC,如果没有的话,执行绑定

IsVolumeBoundToClaim

func IsVolumeBoundToClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) bool {

if volume.Spec.ClaimRef == nil {

return false

}

if claim.Name != volume.Spec.ClaimRef.Name || claim.Namespace != volume.Spec.ClaimRef.Namespace {

return false

}

if volume.Spec.ClaimRef.UID != “” && claim.UID != volume.Spec.ClaimRef.UID {

return false

}

return true

}

我们可以看到这个方法主要是校验相应字段是否相等,如果不相等则返回false,说明PV绑定了其他PVC,等待下次循环再重试。

下面我们看一下syncBoundClaim进行了什么操作:

func (ctrl *PersistentVolumeController) syncBoundClaim(claim *v1.PersistentVolumeClaim) error {

if claim.Spec.VolumeName == “” {

//这里说明以前被绑定过,但现在已经找不到对应的PV了,说明数据丢失,在变更状态的同时,需要发出一个警告事件

if _, err := ctrl.updateClaimStatusWithEvent(claim, v1.ClaimLost, nil, v1.EventTypeWarning, “ClaimLost”, “Bound claim has lost reference to PersistentVolume. Data on the volume is lost!”); err != nil {

return err

}

return nil

}

obj, found, err := ctrl.volumes.store.GetByKey(claim.Spec.VolumeName)

if err != nil {

return err

}

//绑定到不存在的pv情况

if !found {

//这里说明以前被绑定过,但现在已经找不到对应的PV了,说明数据丢失,在变更状态的同时,需要发出一个警告事件

if _, err = ctrl.updateClaimStatusWithEvent(claim, v1.ClaimLost, nil, v1.EventTypeWarning, “ClaimLost”, “Bound claim has lost its PersistentVolume. Data on the volume is lost!”); err != nil {

return err

}

return nil

// 存在pv情况

} else {

volume, ok := obj.(*v1.PersistentVolume)

if !ok {

return fmt.Errorf(“Cannot convert object from volume cache to volume %q!?: %#v”, claim.Spec.VolumeName, obj)

}

klog.V(4).Infof(“synchronizing bound PersistentVolumeClaim[%s]: volume %q found: %s”, claimToClaimKey(claim), claim.Spec.VolumeName, getVolumeStatusForLogging(volume))

//更新绑定关系,这里说明PVC是绑定的,但是PV处于未绑定

if volume.Spec.ClaimRef == nil {

klog.V(4).Infof(“synchronizing bound PersistentVolumeClaim[%s]: volume is unbound, fixing”, claimToClaimKey(claim))

if err = ctrl.bind(volume, claim); err != nil {

// Objects not saved, next syncPV or syncClaim will try again

return err

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后

这份《“java高分面试指南”-25分类227页1000+题50w+字解析》同样可分享给有需要的朋友,感兴趣的伙伴们可挑战一下自我,在不看答案解析的情况,测试测试自己的解题水平,这样也能达到事半功倍的效果!(好东西要大家一起看才香)

image

image

next syncPV or syncClaim will try again

return err

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-4hL2bRDh-1711178054774)]
[外链图片转存中…(img-NcJvIGKz-1711178054775)]
[外链图片转存中…(img-p6jVzIZd-1711178054775)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-hFqdCFuQ-1711178054776)]

最后

这份《“java高分面试指南”-25分类227页1000+题50w+字解析》同样可分享给有需要的朋友,感兴趣的伙伴们可挑战一下自我,在不看答案解析的情况,测试测试自己的解题水平,这样也能达到事半功倍的效果!(好东西要大家一起看才香)

[外链图片转存中…(img-0yPgeUxf-1711178054776)]

[外链图片转存中…(img-rl6lIMGY-1711178054777)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐