external-attacher原始碼分析(2)-核心處理邏輯分析

良凱爾發表於2021-07-10

kubernetes ceph-csi分析目錄導航

基於tag v2.1.1

https://github.com/kubernetes-csi/external-attacher/releases/tag/v2.1.1

external-attacher

external-attacher屬於external plugin中的一個。下面我們先來回顧一下external plugin以及csi系統結構。

external plugin

external plugin包括了external-provisioner、external-attacher、external-resizer、external-snapshotter等,external plugin輔助csi plugin元件,共同完成了儲存相關操作。external plugin負責watch pvc、volumeAttachment等物件,然後呼叫volume plugin來完成儲存的相關操作。如external-provisioner watch pvc物件,然後呼叫csi plugin來建立儲存,最後建立pv物件;external-attacher watch volumeAttachment物件,然後呼叫csi plugin來做attach/dettach操作,並修改volumeAttachment物件與pv物件;external-resizer watch pvc物件,然後呼叫csi plugin來做儲存的擴容操作等。

csi系統結構

image

external-attacher作用分析

根據CSI plugin是否支援ControllerPublish/ControllerUnpublish操作,external-attacher的作用分為如下兩種:
(1)當CSI plugin不支援ControllerPublish/ControllerUnpublish操作時,AD controller(或kubelet的volume manager)建立VolumeAttachment物件後,external-attacher僅參與VolumeAttachment物件的修改,將attached屬性值patch為true;而external-attacher對pv物件無任何同步處理操作。
(2)當CSI plugin支援ControllerPublish/ControllerUnpublish操作時,external-attacher呼叫csi plugin(ControllerPublishVolume)進行儲存的attach操作,然後更改VolumeAttachment物件,將attached屬性值patch為true,並patch pv物件,增加該external-attacher相關的finalizer;對於pv物件,external-attacher負責處理pv物件的finalizer,patch pv物件,去除該external-attacher相關的finalizer(該external-attacher執行attach操作時新增的finalizer)。

原始碼分析

external-attacher的原始碼分析將分為兩部分:
(1)main方法以及啟動引數分析;
(2)核心處理邏輯分析。

前面一篇部落格已經對external-attacher的main方法以及啟動引數做了分析,這篇部落格將對external-attacher的核心處理邏輯進行原始碼分析。

Run

核心邏輯為跑goroutine不停的呼叫ctrl.syncVA與ctrl.syncPV對VolumeAttachment物件以及PV物件進行同步處理。

//external-attcher/pkg/controller/controller.go

// Run starts CSI attacher and listens on channel events
func (ctrl *CSIAttachController) Run(workers int, stopCh <-chan struct{}) {
	defer ctrl.vaQueue.ShutDown()
	defer ctrl.pvQueue.ShutDown()

	klog.Infof("Starting CSI attacher")
	defer klog.Infof("Shutting CSI attacher")

	if !cache.WaitForCacheSync(stopCh, ctrl.vaListerSynced, ctrl.pvListerSynced) {
		klog.Errorf("Cannot sync caches")
		return
	}
	for i := 0; i < workers; i++ {
		go wait.Until(ctrl.syncVA, 0, stopCh)
		go wait.Until(ctrl.syncPV, 0, stopCh)
	}

	if ctrl.shouldReconcileVolumeAttachment {
		go wait.Until(func() {
			err := ctrl.handler.ReconcileVA()
			if err != nil {
				klog.Errorf("Failed to reconcile volume attachments: %v", err)
			}
		}, ctrl.reconcileSync, stopCh)
	}

	<-stopCh
}

1.syncVA

syncVA負責VolumeAttachment物件的處理,核心邏輯為:
(1)判斷VolumeAttachment物件的.Spec.Attacher屬性值,判斷是否由本attacher元件來負責VolumeAttachment物件的同步處理;
(2)呼叫ctrl.handler.SyncNewOrUpdatedVolumeAttachment(va)。

//external-attcher/pkg/controller/controller.go

func (ctrl *CSIAttachController) syncVA() {
	key, quit := ctrl.vaQueue.Get()
	if quit {
		return
	}
	defer ctrl.vaQueue.Done(key)

	vaName := key.(string)
	klog.V(4).Infof("Started VA processing %q", vaName)

	// get VolumeAttachment to process
	va, err := ctrl.vaLister.Get(vaName)
	if err != nil {
		if apierrs.IsNotFound(err) {
			// VolumeAttachment was deleted in the meantime, ignore.
			klog.V(3).Infof("VA %q deleted, ignoring", vaName)
			return
		}
		klog.Errorf("Error getting VolumeAttachment %q: %v", vaName, err)
		ctrl.vaQueue.AddRateLimited(vaName)
		return
	}
	if va.Spec.Attacher != ctrl.attacherName {
		klog.V(4).Infof("Skipping VolumeAttachment %s for attacher %s", va.Name, va.Spec.Attacher)
		return
	}
	ctrl.handler.SyncNewOrUpdatedVolumeAttachment(va)
}

1.1 SyncNewOrUpdatedVolumeAttachment

ctrl.handler.SyncNewOrUpdatedVolumeAttachment()包含兩個實現,將根據CSI plugin是否支援ControllerPublish/ControllerUnpublish操作來呼叫不同的實現。(呼叫不同實現的判斷邏輯在external-attacher的main方法裡,前面分析external-attacher的main方法時已經分析過了,忘記的可以回去看下)

(1)trivialHandler.SyncNewOrUpdatedVolumeAttachment:當CSI plugin不支援ControllerPublish/ControllerUnpublish操作時,AD controller(或kubelet的volume manager)建立VolumeAttachment物件後,external-attacher僅參與VolumeAttachment物件的修改,將attached屬性值patch為true。
(2)csiHandler.SyncNewOrUpdatedVolumeAttachment:當CSI plugin支援ControllerPublish/ControllerUnpublish操作時,external-attacher呼叫csi plugin(ControllerPublishVolume)進行儲存的attach操作,然後更改VolumeAttachment物件,將attached屬性值patch為true。

1.1.1 trivialHandler.SyncNewOrUpdatedVolumeAttachment

先來看trivialHandler的實現。

當VolumeAttachment的attached屬性值為false時,呼叫markAsAttached做進一步處理。

//external-attcher/pkg/controller/trivial_handler.go

func (h *trivialHandler) SyncNewOrUpdatedVolumeAttachment(va *storage.VolumeAttachment) {
	klog.V(4).Infof("Trivial sync[%s] started", va.Name)
	if !va.Status.Attached {
		// mark as attached
		if _, err := markAsAttached(h.client, va, nil); err != nil {
			klog.Warningf("Error saving VolumeAttachment %s as attached: %s", va.Name, err)
			h.vaQueue.AddRateLimited(va.Name)
			return
		}
		klog.V(2).Infof("Marked VolumeAttachment %s as attached", va.Name)
	}
	h.vaQueue.Forget(va.Name)
}
markAsAttached

markAsAttached主要是將VolumeAttachment的attached屬性值patch為true。

//external-attcher/pkg/controller/util.go

func markAsAttached(client kubernetes.Interface, va *storage.VolumeAttachment, metadata map[string]string) (*storage.VolumeAttachment, error) {
	klog.V(4).Infof("Marking as attached %q", va.Name)
	clone := va.DeepCopy()
	clone.Status.Attached = true
	clone.Status.AttachmentMetadata = metadata
	clone.Status.AttachError = nil
	patch, err := createMergePatch(va, clone)
	if err != nil {
		return va, err
	}
	newVA, err := client.StorageV1beta1().VolumeAttachments().Patch(va.Name, types.MergePatchType, patch)
	if err != nil {
		return va, err
	}
	klog.V(4).Infof("Marked as attached %q", va.Name)
	return newVA, nil
}

1.1.2 csiHandler.SyncNewOrUpdatedVolumeAttachment

先來看csiHandler的實現。主要邏輯如下:
(1)當VolumeAttachment物件的.DeletionTimestamp欄位為空,呼叫h.syncAttach(呼叫了csi plugin的ControllerPublishVolume方法來做儲存的attach操作,並呼叫markAsAttached將VolumeAttachment的attached屬性值patch為true);
(2)當VolumeAttachment物件的.DeletionTimestamp欄位不為空,呼叫h.syncDetach(呼叫了csi plugin的ControllerUnpublishVolume方法來做儲存的dettach操作,並呼叫markAsDetached將VolumeAttachment的attached屬性值patch為false)。

// pkg/controller/csi_handler.go
func (h *csiHandler) SyncNewOrUpdatedVolumeAttachment(va *storage.VolumeAttachment) {
	klog.V(4).Infof("CSIHandler: processing VA %q", va.Name)

	var err error
	if va.DeletionTimestamp == nil {
		err = h.syncAttach(va)
	} else {
		err = h.syncDetach(va)
	}
	if err != nil {
		// Re-queue with exponential backoff
		klog.V(2).Infof("Error processing %q: %s", va.Name, err)
		h.vaQueue.AddRateLimited(va.Name)
		return
	}
	// The operation has finished successfully, reset exponential backoff
	h.vaQueue.Forget(va.Name)
	klog.V(4).Infof("CSIHandler: finished processing %q", va.Name)
}
syncAttach

syncAttach不展開分析了,感興趣的可以自己深入分析,從h.csiAttach呼叫入手。

主要邏輯為:
(1)呼叫h.csiAttach做attach操作(patch pv物件,增加該external-attacher相關的Finalizer,並最終呼叫了csi plugin的ControllerPublishVolume方法來做儲存的attach操作);
(2)呼叫markAsAttached將VolumeAttachment的attached屬性值patch為true。

// pkg/controller/csi_handler.go
func (h *csiHandler) syncAttach(va *storage.VolumeAttachment) error {
	if !h.consumeForceSync(va.Name) && va.Status.Attached {
		// Volume is attached and no force sync, there is nothing to be done.
		klog.V(4).Infof("%q is already attached", va.Name)
		return nil
	}

	// Attach and report any error
	klog.V(2).Infof("Attaching %q", va.Name)
	va, metadata, err := h.csiAttach(va)
	if err != nil {
		var saveErr error
		va, saveErr = h.saveAttachError(va, err)
		if saveErr != nil {
			// Just log it, propagate the attach error.
			klog.V(2).Infof("Failed to save attach error to %q: %s", va.Name, saveErr.Error())
		}
		// Add context to the error for logging
		err := fmt.Errorf("failed to attach: %s", err)
		return err
	}
	klog.V(2).Infof("Attached %q", va.Name)

	// Mark as attached
	if _, err := markAsAttached(h.client, va, metadata); err != nil {
		return fmt.Errorf("failed to mark as attached: %s", err)
	}
	klog.V(4).Infof("Fully attached %q", va.Name)
	return nil
}
syncDetach

同樣的,syncDetach不展開分析,這裡直接給出結果,最終呼叫了csi plugin的ControllerUnpublishVolume方法來做儲存的dettach操作,並呼叫markAsDetached將VolumeAttachment的attached屬性值patch為false,感興趣的可以自己深入分析,從h.csiDetach呼叫入手。

// pkg/controller/csi_handler.go
func (h *csiHandler) syncDetach(va *storage.VolumeAttachment) error {
	klog.V(4).Infof("Starting detach operation for %q", va.Name)
	if !h.consumeForceSync(va.Name) && !h.hasVAFinalizer(va) {
		klog.V(4).Infof("%q is already detached", va.Name)
		return nil
	}

	// Detach and report any error
	klog.V(2).Infof("Detaching %q", va.Name)
	va, err := h.csiDetach(va)
	if err != nil {
		var saveErr error
		va, saveErr = h.saveDetachError(va, err)
		if saveErr != nil {
			// Just log it, propagate the detach error.
			klog.V(2).Infof("Failed to save detach error to %q: %s", va.Name, saveErr.Error())
		}
		// Add context to the error for logging
		err := fmt.Errorf("failed to detach: %s", err)
		return err
	}
	klog.V(4).Infof("Fully detached %q", va.Name)
	return nil
}

2.syncPV

syncPV負責PV物件的處理,核心邏輯為呼叫ctrl.handler.SyncNewOrUpdatedPersistentVolume(pv)來對pv物件做進一步處理。

//external-attcher/pkg/controller/controller.go

// syncPV deals with one key off the queue.  It returns false when it's time to quit.
func (ctrl *CSIAttachController) syncPV() {
	key, quit := ctrl.pvQueue.Get()
	if quit {
		return
	}
	defer ctrl.pvQueue.Done(key)

	pvName := key.(string)
	klog.V(4).Infof("Started PV processing %q", pvName)

	// get PV to process
	pv, err := ctrl.pvLister.Get(pvName)
	if err != nil {
		if apierrs.IsNotFound(err) {
			// PV was deleted in the meantime, ignore.
			klog.V(3).Infof("PV %q deleted, ignoring", pvName)
			return
		}
		klog.Errorf("Error getting PersistentVolume %q: %v", pvName, err)
		ctrl.pvQueue.AddRateLimited(pvName)
		return
	}
	ctrl.handler.SyncNewOrUpdatedPersistentVolume(pv)
}

2.1 SyncNewOrUpdatedPersistentVolume

跟上面分析的syncVA中的SyncNewOrUpdatedVolumeAttachment一樣,ctrl.handler.SyncNewOrUpdatedPersistentVolume()也包含兩個實現,將根據CSI plugin是否支援ControllerPublish/ControllerUnpublish操作來呼叫不同的實現。(呼叫不同實現的判斷邏輯在external-attacher的main方法裡,前面分析external-attacher的main方法時已經分析過了,忘記的可以回去看下)

(1)trivialHandler.SyncNewOrUpdatedPersistentVolume:當CSI plugin不支援ControllerPublish/ControllerUnpublish操作時,external-attacher不對pv物件做任何同步處理操做。
(2)csiHandler.SyncNewOrUpdatedPersistentVolume:當CSI plugin支援ControllerPublish/ControllerUnpublish操作時,external-attacher負責處理pv物件的finalizer,patch pv物件,去除該external-attacher相關的finalizer(該external-attacher執行attach操作時新增的finalizer)。

2.1.1 trivialHandler.SyncNewOrUpdatedPersistentVolume

trivialHandler.SyncNewOrUpdatedPersistentVolume方法直接返回,可以看出對於pv物件,不做處理。

//external-attcher/pkg/controller/trivial_handler.go

func (h *trivialHandler) SyncNewOrUpdatedPersistentVolume(pv *v1.PersistentVolume) {
	return
}
與ceph-csi搭配使用的external-attacher相關日誌
I0907 03:30:18.426009       1 main.go:166] CSI driver does not support ControllerPublishUnpublish, using trivial handler

I0907 08:13:38.148672       1 controller.go:198] Started VA processing "csi-706ace7b44a2f618280cba51951595f98893b34a38015e3c4ef7d1160be7f67b"
I0907 08:13:38.148698       1 trivial_handler.go:53] Trivial sync[csi-706ace7b44a2f618280cba51951595f98893b34a38015e3c4ef7d1160be7f67b] started
I0907 08:13:38.148707       1 util.go:35] Marking as attached "csi-706ace7b44a2f618280cba51951595f98893b34a38015e3c4ef7d1160be7f67b"
I0907 08:13:38.156486       1 util.go:48] Marked as attached "csi-706ace7b44a2f618280cba51951595f98893b34a38015e3c4ef7d1160be7f67b"
I0907 08:13:38.156510       1 trivial_handler.go:61] Marked VolumeAttachment csi-706ace7b44a2f618280cba51951595f98893b34a38015e3c4ef7d1160be7f67b as attached

2.1.2 csiHandler.SyncNewOrUpdatedPersistentVolume

csiHandler.SyncNewOrUpdatedPersistentVolume主要是處理pv物件的finalizer,patch pv物件,去除該external-attacher相關的finalizer(該external-attacher執行attach操作時新增的finalizer)。

主要邏輯:
(1)判斷pv物件的DeletionTimestamp是否為空,為空則直接返回;
(2)檢查pv物件是否含有該external-attacher相關的finalizer,沒有則直接返回;
(3)查詢volumeAttachment物件列表,遍歷查詢是否有va物件記錄著該pv,有則直接返回;
(4)去除pv物件中該external-attacher相關的finalizer;
(5)patch pv物件。

// pkg/controller/csi_handler.go
func (h *csiHandler) SyncNewOrUpdatedPersistentVolume(pv *v1.PersistentVolume) {
	klog.V(4).Infof("CSIHandler: processing PV %q", pv.Name)
	// Sync and remove finalizer on given PV
	if pv.DeletionTimestamp == nil {
		// Don't process anything that has no deletion timestamp.
		klog.V(4).Infof("CSIHandler: processing PV %q: no deletion timestamp, ignoring", pv.Name)
		h.pvQueue.Forget(pv.Name)
		return
	}

	// Check if the PV has finalizer
	finalizer := GetFinalizerName(h.attacherName)
	found := false
	for _, f := range pv.Finalizers {
		if f == finalizer {
			found = true
			break
		}
	}
	if !found {
		// No finalizer -> no action required
		klog.V(4).Infof("CSIHandler: processing PV %q: no finalizer, ignoring", pv.Name)
		h.pvQueue.Forget(pv.Name)
		return
	}

	// Check that there is no VA that requires the PV
	vas, err := h.vaLister.List(labels.Everything())
	if err != nil {
		// Failed listing VAs? Try again with exp. backoff
		klog.Errorf("Failed to list VolumeAttachments for PV %q: %s", pv.Name, err.Error())
		h.pvQueue.AddRateLimited(pv.Name)
		return
	}
	for _, va := range vas {
		if va.Spec.Source.PersistentVolumeName != nil && *va.Spec.Source.PersistentVolumeName == pv.Name {
			// This PV is needed by this VA, don't remove finalizer
			klog.V(4).Infof("CSIHandler: processing PV %q: VA %q found", pv.Name, va.Name)
			h.pvQueue.Forget(pv.Name)
			return
		}
	}
	// No VA found -> remove finalizer
	klog.V(4).Infof("CSIHandler: processing PV %q: no VA found, removing finalizer", pv.Name)
	clone := pv.DeepCopy()
	newFinalizers := []string{}
	for _, f := range pv.Finalizers {
		if f == finalizer {
			continue
		}
		newFinalizers = append(newFinalizers, f)
	}
	if len(newFinalizers) == 0 {
		// Canonize empty finalizers for unit test (so we don't need to
		// distinguish nil and [] there)
		newFinalizers = nil
	}
	clone.Finalizers = newFinalizers

	if _, err = h.patchPV(pv, clone); err != nil {
		klog.Errorf("Failed to remove finalizer from PV %q: %s", pv.Name, err.Error())
		h.pvQueue.AddRateLimited(pv.Name)
		return
	}

	klog.V(2).Infof("Removed finalizer from PV %q", pv.Name)
	h.pvQueue.Forget(pv.Name)

	return
}

總結

external-attacher屬於external plugin中的一個。

external-attacher作用分析

根據CSI plugin是否支援ControllerPublish/ControllerUnpublish操作,external-attacher的作用分為如下兩種:
(1)當CSI plugin不支援ControllerPublish/ControllerUnpublish操作時,AD controller(或kubelet的volume manager)建立VolumeAttachment物件後,external-attacher僅參與VolumeAttachment物件的修改,將attached屬性值patch為true;而external-attacher對pv物件無任何同步處理操作。
(2)當CSI plugin支援ControllerPublish/ControllerUnpublish操作時,external-attacher呼叫csi plugin(ControllerPublishVolume)進行儲存的attach操作,然後更改VolumeAttachment物件,將attached屬性值patch為true,並patch pv物件,增加該external-attacher相關的Finalizer;對於pv物件,external-attacher負責處理pv物件的finalizer,patch pv物件,去除該external-attacher相關的finalizer(該external-attacher執行attach操作時新增的finalizer)。

external-attacher與ceph-csi rbd結合使用

ceph-csi不支援ControllerPublish/ControllerUnpublish操作,所以external-attacher與ceph-csi rbd結合使用,external-attacher僅參與VolumeAttachment物件的修改,將attached屬性值patch為true。

相關文章