garbage collector controller 源碼分析

在前面幾篇關(guān)于 controller 源碼分析的文章中多次提到了當(dāng)刪除一個(gè)對(duì)象時(shí),其對(duì)應(yīng)的 controller 并不會(huì)執(zhí)行刪除對(duì)象的操作,在 kubernetes 中對(duì)象的回收操作是由 GarbageCollectorController 負(fù)責(zé)的,其作用就是當(dāng)刪除一個(gè)對(duì)象時(shí),會(huì)根據(jù)指定的刪除策略回收該對(duì)象及其依賴對(duì)象,本文會(huì)深入分析垃圾收集背后的實(shí)現(xiàn)。

kubernetes 中的刪除策略

kubernetes 中有三種刪除策略:OrphanForegroundBackground,三種刪除策略的意義分別為:

  • Orphan 策略:非級(jí)聯(lián)刪除,刪除對(duì)象時(shí),不會(huì)自動(dòng)刪除它的依賴或者是子對(duì)象,這些依賴被稱(chēng)作是原對(duì)象的孤兒對(duì)象,例如當(dāng)執(zhí)行以下命令時(shí)會(huì)使用 Orphan 策略進(jìn)行刪除,此時(shí) ds 的依賴對(duì)象 ontrollerrevision 不會(huì)被刪除;
$ kubectl delete ds/nginx-ds --cascade=false
  • Background 策略:在該模式下,kubernetes 會(huì)立即刪除該對(duì)象,然后垃圾收集器會(huì)在后臺(tái)刪除這些該對(duì)象的依賴對(duì)象;

  • Foreground 策略:在該模式下,對(duì)象首先進(jìn)入“刪除中”狀態(tài),即會(huì)設(shè)置對(duì)象的 deletionTimestamp 字段并且對(duì)象的 metadata.finalizers 字段包含了值 “foregroundDeletion”,此時(shí)該對(duì)象依然存在,然后垃圾收集器會(huì)刪除該對(duì)象的所有依賴對(duì)象,垃圾收集器在刪除了所有“Blocking” 狀態(tài)的依賴對(duì)象(指其子對(duì)象中 ownerReference.blockOwnerDeletion=true的對(duì)象)之后,然后才會(huì)刪除對(duì)象本身;

在 v1.9 以前的版本中,大部分 controller 默認(rèn)的刪除策略為 Orphan,從 v1.9 開(kāi)始,對(duì)于 apps/v1 下的資源默認(rèn)使用 Background 模式。以上三種刪除策略都可以在刪除對(duì)象時(shí)通過(guò)設(shè)置 deleteOptions.propagationPolicy 字段進(jìn)行指定,如下所示:

$ curl -k -v -XDELETE  -H "Accept: application/json" -H "Content-Type: application/json" -d '{"propagationPolicy":"Foreground"}' 'https://192.168.99.108:8443/apis/apps/v1/namespaces/default/daemonsets/nginx-ds'

finalizer 機(jī)制

finalizer 是在刪除對(duì)象時(shí)設(shè)置的一個(gè) hook,其目的是為了讓對(duì)象在刪除前確認(rèn)其子對(duì)象已經(jīng)被完全刪除,k8s 中默認(rèn)有兩種 finalizer:OrphanFinalizerForegroundFinalizer,finalizer 存在于對(duì)象的 ObjectMeta 中,當(dāng)一個(gè)對(duì)象的依賴對(duì)象被刪除后其對(duì)應(yīng)的 finalizers 字段也會(huì)被移除,只有 finalizers 字段為空時(shí),apiserver 才會(huì)刪除該對(duì)象。

{
    ......
    "metadata": {
       ......
        "finalizers": [
            "foregroundDeletion"
        ]
    }
    ......
}

此外,finalizer 不僅僅支持以上兩種字段,在使用自定義 controller 時(shí)也可以在 CR 中設(shè)置自定義的 finalizer 標(biāo)識(shí)。

GarbageCollectorController 源碼分析

kubernetes 版本:v1.16

GarbageCollectorController 負(fù)責(zé)回收 kubernetes 中的資源,要回收 kubernetes 中所有資源首先得監(jiān)控所有資源,GarbageCollectorController 會(huì)監(jiān)聽(tīng)集群中所有可刪除資源產(chǎn)生的所有事件,這些事件會(huì)被放入到一個(gè)隊(duì)列中,然后 controller 會(huì)啟動(dòng)多個(gè) goroutine 處理隊(duì)列中的事件,若為刪除事件會(huì)根據(jù)對(duì)象的刪除策略刪除關(guān)聯(lián)的對(duì)象,對(duì)于非刪除事件會(huì)更新對(duì)象之間的依賴關(guān)系。

startGarbageCollectorController

首先還是看 GarbageCollectorController 的啟動(dòng)方法 startGarbageCollectorController,其主要邏輯為:

  • 1、初始化 discoveryClient,discoveryClient 主要用來(lái)獲取集群中的所有資源;

  • 2、調(diào)用 garbagecollector.GetDeletableResources 獲取集群內(nèi)所有可刪除的資源對(duì)象,支持 "delete", "list", "watch" 三種操作的 resource 稱(chēng)為 deletableResource

  • 3、調(diào)用 garbagecollector.NewGarbageCollector 初始化 garbageCollector 對(duì)象;

  • 4、調(diào)用 garbageCollector.Run 啟動(dòng) garbageCollector;

  • 5、調(diào)用 garbageCollector.Sync 監(jiān)聽(tīng)集群中的 DeletableResources ,當(dāng)出現(xiàn)新的 DeletableResources 時(shí)同步到 monitors 中,確保監(jiān)控集群中的所有資源;

  • 6、調(diào)用 garbagecollector.NewDebugHandler 注冊(cè) debug 接口,用來(lái)提供集群內(nèi)所有對(duì)象的關(guān)聯(lián)關(guān)系;

k8s.io/kubernetes/cmd/kube-controller-manager/app/core.go:443

func startGarbageCollectorController(ctx ControllerContext) (http.Handler, bool, error) {
    if !ctx.ComponentConfig.GarbageCollectorController.EnableGarbageCollector {
        return nil, false, nil
    }
        // 1、初始化 discoveryClient
    gcClientset := ctx.ClientBuilder.ClientOrDie("generic-garbage-collector")
    discoveryClient := cacheddiscovery.NewMemCacheClient(gcClientset.Discovery())

    config := ctx.ClientBuilder.ConfigOrDie("generic-garbage-collector")
    metadataClient, err := metadata.NewForConfig(config)
    if err != nil {
        return nil, true, err
    }

    // 2、獲取 deletableResource
    deletableResources := garbagecollector.GetDeletableResources(discoveryClient)
    ignoredResources := make(map[schema.GroupResource]struct{})
    for _, r := range ctx.ComponentConfig.GarbageCollectorController.GCIgnoredResources {
        ignoredResources[schema.GroupResource{Group: r.Group, Resource: r.Resource}] = struct{}{}
    }
    
    // 3、初始化 garbageCollector 對(duì)象
    garbageCollector, err := garbagecollector.NewGarbageCollector(
                ......
    )
    if err != nil {
        return nil, true, fmt.Errorf("failed to start the generic garbage collector: %v", err)
    }
    // 4、啟動(dòng) garbage collector
    workers := int(ctx.ComponentConfig.GarbageCollectorController.ConcurrentGCSyncs)
    go garbageCollector.Run(workers, ctx.Stop)

    // 5、監(jiān)聽(tīng)集群中的 DeletableResources
    go garbageCollector.Sync(gcClientset.Discovery(), 30*time.Second, ctx.Stop)

        // 6、注冊(cè) debug 接口
    return garbagecollector.NewDebugHandler(garbageCollector), true, nil
}

startGarbageCollectorController 中主要調(diào)用了四種方法garbagecollector.NewGarbageCollector、garbageCollector.Run、garbageCollector.Syncgarbagecollector.NewDebugHandler 來(lái)完成核心功能,下面主要針對(duì)這四種方法進(jìn)行說(shuō)明。

garbagecollector.NewGarbageCollector

NewGarbageCollector 的主要功能是初始化 GarbageCollector 和 GraphBuilder 對(duì)象,并調(diào)用 gb.syncMonitors方法初始化 deletableResources 中所有 resource controller 的 informer。GarbageCollector 的主要作用是啟動(dòng) GraphBuilder 以及啟動(dòng)所有的消費(fèi)者,GraphBuilder 的主要作用是啟動(dòng)所有的生產(chǎn)者。

k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:74

func NewGarbageCollector(......) (*GarbageCollector, error) {
        ......
    gc := &GarbageCollector{
                ......
    }
    gb := &GraphBuilder{
                ......
    }
    if err := gb.syncMonitors(deletableResources); err != nil {
        utilruntime.HandleError(fmt.Errorf("failed to sync all monitors: %v", err))
    }
    gc.dependencyGraphBuilder = gb

    return gc, nil
}
gb.syncMonitors

syncMonitors 的主要作用是初始化各個(gè)資源對(duì)象的 informer,并調(diào)用 gb.controllerFor 為每種資源注冊(cè) eventHandler,此處每種資源被稱(chēng)為 monitors,因?yàn)闉槊糠N資源注冊(cè) eventHandler 時(shí),對(duì)于 AddFunc、UpdateFunc 和 DeleteFunc 都會(huì)將對(duì)應(yīng)的 event push 到 graphChanges 隊(duì)列中,每種資源對(duì)象的 informer 都作為生產(chǎn)者。

k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:179

func (gb *GraphBuilder) syncMonitors(resources map[schema.GroupVersionResource]struct{}) error {
    gb.monitorLock.Lock()
    defer gb.monitorLock.Unlock()

        ......
    for resource := range resources {
        if _, ok := gb.ignoredResources[resource.GroupResource()]; ok {
            continue
        }
                ......
        kind, err := gb.restMapper.KindFor(resource)
        if err != nil {
            errs = append(errs, fmt.Errorf("couldn't look up resource %q: %v", resource, err))
            continue
        }
        // 為 resource 的 controller 注冊(cè) eventHandler 
        c, s, err := gb.controllerFor(resource, kind)
        if err != nil {
            errs = append(errs, fmt.Errorf("couldn't start monitor for resource %q: %v", resource, err))
            continue
        }
        current[resource] = &monitor{store: s, controller: c}
        added++
    }
    gb.monitors = current

    for _, monitor := range toRemove {
        if monitor.stopCh != nil {
            close(monitor.stopCh)
        }
    }
    return utilerrors.NewAggregate(errs)
}
gb.controllerFor

gb.controllerFor中主要是為每個(gè) deletableResources 的 informer 注冊(cè) eventHandler,此處就可以看到真正的生產(chǎn)者了。

k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:127

func (gb *GraphBuilder) controllerFor(resource schema.GroupVersionResource, kind schema.GroupVersionKind) (cache.Controller, cache.Store, error) {
    handlers := cache.ResourceEventHandlerFuncs{
        AddFunc: func(obj interface{}) {
            event := &event{
                eventType: addEvent,
                obj:       obj,
                gvk:       kind,
            }
            // 將對(duì)應(yīng)的 event push 到 graphChanges 隊(duì)列中
            gb.graphChanges.Add(event)
        },
        UpdateFunc: func(oldObj, newObj interface{}) {
            event := &event{
                eventType: updateEvent,
                obj:       newObj,
                oldObj:    oldObj,
                gvk:       kind,
            }
            // 將對(duì)應(yīng)的 event push 到 graphChanges 隊(duì)列中
            gb.graphChanges.Add(event)
        },
        DeleteFunc: func(obj interface{}) {
            if deletedFinalStateUnknown, ok := obj.(cache.DeletedFinalStateUnknown); ok {
                obj = deletedFinalStateUnknown.Obj
            }
            event := &event{
                eventType: deleteEvent,
                obj:       obj,
                gvk:       kind,
            }
            // 將對(duì)應(yīng)的 event push 到 graphChanges 隊(duì)列中
            gb.graphChanges.Add(event)
        },
    }
    shared, err := gb.sharedInformers.ForResource(resource)
    if err != nil {
        return nil, nil, err
    }
    shared.Informer().AddEventHandlerWithResyncPeriod(handlers, ResourceResyncTime)
    return shared.Informer().GetController(), shared.Informer().GetStore(), nil
}

至此 NewGarbageCollector 的功能已經(jīng)分析完了,在 NewGarbageCollector 中初始化了兩個(gè)對(duì)象 GarbageCollector 和 GraphBuilder,然后在 gb.syncMonitors 中初始化了所有 deletableResources 的 informer,為每個(gè) informer 添加 eventHandler 并將監(jiān)聽(tīng)到的所有 event push 到 graphChanges 隊(duì)列中,此處每個(gè) informer 都被稱(chēng)為 monitor,所有 informer 都被稱(chēng)為生產(chǎn)者。graphChanges 是 GraphBuilder 中的一個(gè)對(duì)象,GraphBuilder 的主要功能是作為一個(gè)生產(chǎn)者,其會(huì)處理 graphChanges 中的所有事件并進(jìn)行分類(lèi),將事件放入到 attemptToDelete 和 attemptToOrphan 兩個(gè)隊(duì)列中,具體處理邏輯下文講述。

NewGarbageCollector 中的調(diào)用邏輯如下所示:

                     |--> ctx.ClientBuilder.
                     |    ClientOrDie
                     |
                     |
                     |--> cacheddiscovery.
                     |    NewMemCacheClient
                     |                                                                  |--> gb.sharedInformers.
                     |                                                                  |       ForResource
                     |                                                                  |
startGarbage     ----|--> garbagecollector.  --> gb.syncMonitors --> gb.controllerFor --|
CollectorController  |    NewGarbageCollector                                           |
                     |                                                                  |
                     |                                                                  |--> shared.Informer().
                     |                                                                    AddEventHandlerWithResyncPeriod
                     |--> garbageCollector.Run
                     |
                     |
                     |--> garbageCollector.Sync
                     |
                     |
                     |--> garbagecollector.NewDebugHandler

garbageCollector.Run

上文已經(jīng)詳述了 NewGarbageCollector 的主要功能,然后繼續(xù)分析 startGarbageCollectorController 中的第二個(gè)核心方法 garbageCollector.RungarbageCollector.Run 的主要作用是啟動(dòng)所有的生產(chǎn)者和消費(fèi)者,其首先會(huì)調(diào)用 gc.dependencyGraphBuilder.Run 啟動(dòng)所有的生產(chǎn)者,即 monitors,然后再啟動(dòng)一個(gè) goroutine 處理 graphChanges 隊(duì)列中的事件并分別放到 attemptToDelete 和 attemptToOrphan 兩個(gè)隊(duì)列中,dependencyGraphBuilder 即上文提到的 GraphBuilder,run 方法會(huì)調(diào)用 gc.runAttemptToDeleteWorkergc.runAttemptToOrphanWorker 啟動(dòng)多個(gè) goroutine 處理 attemptToDelete 和 attemptToOrphan 兩個(gè)隊(duì)列中的事件。

k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:124

func (gc *GarbageCollector) Run(workers int, stopCh <-chan struct{}) {
    defer utilruntime.HandleCrash()
    defer gc.attemptToDelete.ShutDown()
    defer gc.attemptToOrphan.ShutDown()
    defer gc.dependencyGraphBuilder.graphChanges.ShutDown()

    defer klog.Infof("Shutting down garbage collector controller")

        // 1、調(diào)用 gc.dependencyGraphBuilder.Run 啟動(dòng)所有的 monitors 即 informers,并且啟動(dòng)一個(gè) goroutine 處理 graphChanges 中的事件將其分別放到 GraphBuilder 的 attemptToDelete 和 attemptToOrphan 兩個(gè) 隊(duì)列中;
    go gc.dependencyGraphBuilder.Run(stopCh)

        // 2、等待 informers 的 cache 同步完成
    if !cache.WaitForNamedCacheSync("garbage collector", stopCh, gc.dependencyGraphBuilder.IsSynced) {
        return
    }

    for i := 0; i < workers; i++ {
            // 3、啟動(dòng)多個(gè) goroutine 調(diào)用 gc.runAttemptToDeleteWorker 處理 attemptToDelete 中的事件
        go wait.Until(gc.runAttemptToDeleteWorker, 1*time.Second, stopCh)
        // 4、啟動(dòng)多個(gè) goroutine 調(diào)用 gc.runAttemptToOrphanWorker 處理 attemptToDelete 中的事件
        go wait.Until(gc.runAttemptToOrphanWorker, 1*time.Second, stopCh)
    }

    <-stopCh
}

Run 方法中調(diào)用了 gc.dependencyGraphBuilder.Run 來(lái)完成 GraphBuilder 的啟動(dòng)。

gc.dependencyGraphBuilder.Run

GraphBuilder 在 garbageCollector 整個(gè)環(huán)節(jié)中起到承上啟下的作用,首先看一下 GraphBuilder 對(duì)象的結(jié)構(gòu):

type GraphBuilder struct {
    restMapper meta.RESTMapper

        // informers
    monitors    monitors
    monitorLock sync.RWMutex

    // 當(dāng) kube-controller-manager 中所有的 controllers 都啟動(dòng)后,informersStarted 會(huì)被 close 掉
    // informersStarted 會(huì)被 close 掉的調(diào)用程序在 kube-controller-manager 的啟動(dòng)流程中
    informersStarted <-chan struct{}

    stopCh <-chan struct{}

    // 當(dāng)調(diào)用 GraphBuilder 的 run 方法時(shí),running 會(huì)被設(shè)置為 true
    running bool

    metadataClient metadata.Interface
    
    // informers 監(jiān)聽(tīng)到的事件會(huì)放在 graphChanges 中
    graphChanges workqueue.RateLimitingInterface
    
    // 維護(hù)所有對(duì)象的依賴關(guān)系 
    uidToNode *concurrentUIDToNode
    
    // GarbageCollector 作為消費(fèi)者要處理 attemptToDelete 和 attemptToOrphan 兩個(gè)隊(duì)列中的事件
    attemptToDelete workqueue.RateLimitingInterface
    attemptToOrphan workqueue.RateLimitingInterface

    absentOwnerCache *UIDCache
    sharedInformers  controller.InformerFactory
    // 不需要被 gc 的資源
    ignoredResources map[schema.GroupResource]struct{}
}
uidToNode

此處有必要先說(shuō)明一下 uidToNode 的功能,uidToNode 數(shù)據(jù)結(jié)構(gòu)中維護(hù)著所有對(duì)象的依賴關(guān)系,此處的依賴關(guān)系是指比如當(dāng)創(chuàng)建一個(gè) deployment 時(shí)會(huì)創(chuàng)建對(duì)應(yīng)的 rs 以及 pod,pod 的 owner 就是 rs,rs 的 owner 是 deployment,rs 的 dependents 是其關(guān)聯(lián)的所有 pod,deployment 的 dependents 是其關(guān)聯(lián)的所有 rs。

uidToNode 中的 node 不是指 k8s 中的 node 節(jié)點(diǎn),而是將 graphChanges 中的 event 轉(zhuǎn)換為 node 對(duì)象,k8s 中所有 object 之間的級(jí)聯(lián)關(guān)系是通過(guò) node 的概念來(lái)維護(hù)的,garbageCollector 在后續(xù)的處理中會(huì)直接使用 node 對(duì)象,node 對(duì)象定義如下:

type concurrentUIDToNode struct {
    uidToNodeLock sync.RWMutex
    uidToNode     map[types.UID]*node
}

type node struct {
    identity objectReference
    
    dependentsLock sync.RWMutex
    // 其依賴項(xiàng)指 metadata.ownerReference 中的對(duì)象
    dependents map[*node]struct{}

    deletingDependents     bool
    deletingDependentsLock sync.RWMutex

    beingDeleted     bool
    beingDeletedLock sync.RWMutex
    
    // 當(dāng) virtual 值為 true 時(shí),此時(shí)不確定該對(duì)象是否存在于 apiserver 中
    virtual     bool
    virtualLock sync.RWMutex
    
    // 對(duì)象本身的 OwnerReference 列表
    owners []metav1.OwnerReference
}

GraphBuilder 主要有三個(gè)功能:

  • 1、監(jiān)控集群中所有的可刪除資源;
  • 2、基于 informers 中的資源在 uidToNode 數(shù)據(jù)結(jié)構(gòu)中維護(hù)著所有對(duì)象的依賴關(guān)系;
  • 3、處理 graphChanges 中的事件并放到 attemptToDelete 和 attemptToOrphan 兩個(gè)隊(duì)列中;

上文已經(jīng)說(shuō)了 gc.dependencyGraphBuilder.Run 的功能,啟動(dòng)所有的 informers 然后再啟動(dòng)一個(gè) goroutine 處理 graphChanges 隊(duì)列中的事件并分別放到 attemptToDelete 和 attemptToOrphan 兩個(gè)隊(duì)列中,代碼如下所示:

k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:281

func (gb *GraphBuilder) Run(stopCh <-chan struct{}) {
    klog.Infof("GraphBuilder running")
    defer klog.Infof("GraphBuilder stopping")

    gb.monitorLock.Lock()
    gb.stopCh = stopCh
    gb.running = true
    gb.monitorLock.Unlock()

    gb.startMonitors()
    
    // 調(diào)用 gb.runProcessGraphChanges
    // 此處為死循環(huán),除非收到 stopCh 信號(hào),否則下面的代碼不會(huì)被執(zhí)行到
    wait.Until(gb.runProcessGraphChanges, 1*time.Second, stopCh)

        // 若執(zhí)行到此處說(shuō)明收到了 stopCh 的信號(hào),此時(shí)需要停止所有的 running monitors
    gb.monitorLock.Lock()
    defer gb.monitorLock.Unlock()
    monitors := gb.monitors
    stopped := 0
    for _, monitor := range monitors {
        if monitor.stopCh != nil {
            stopped++
            close(monitor.stopCh)
        }
    }

    gb.monitors = nil
}

gc.dependencyGraphBuilder.Run的核心是調(diào)用了 gb.startMonitorsgb.runProcessGraphChanges 兩個(gè)方法來(lái)完成主要功能,繼續(xù)看這兩個(gè)方法的主要邏輯。

gb.startMonitors

startMonitors 的功能很簡(jiǎn)單就是啟動(dòng)所有的 informers,代碼如下所示:

k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:232

func (gb *GraphBuilder) startMonitors() {
    gb.monitorLock.Lock()
    defer gb.monitorLock.Unlock()
        
        // 1、當(dāng) GraphBuilder 調(diào)用 run 方法后,running 會(huì)設(shè)置為 true
    if !gb.running {
        return
    }

        // 2、當(dāng) kube-controller-manager 中所有的 controllers 在啟動(dòng)流程中都啟動(dòng)后
        //    會(huì) close 掉 informersStarted
    <-gb.informersStarted

        // 3、啟動(dòng)所有 informer
    monitors := gb.monitors
    started := 0
    for _, monitor := range monitors {
        if monitor.stopCh == nil {
            monitor.stopCh = make(chan struct{})
            gb.sharedInformers.Start(gb.stopCh)
            go monitor.Run()
            started++
        }
    }
}
gb.runProcessGraphChanges

runProcessGraphChanges 方法的主要功能是處理 graphChanges 中的事件將其分別放到 GraphBuilder 的 attemptToDelete 和 attemptToOrphan 兩個(gè)隊(duì)列中,代碼主要邏輯為:

  • 1、從 graphChanges 隊(duì)列中取出一個(gè) item 即 event;

  • 2、獲取 event 的 accessor,accessor 是一個(gè) object 的 meta.Interface,里面包含訪問(wèn) object meta 中所有字段的方法;

  • 3、通過(guò) accessor 獲取 UID 判斷 uidToNode 中是否存在該 object;

  • 4、若 uidToNode 中不存在該 node 且該事件是 addEvent 或 updateEvent,則為該 object 創(chuàng)建對(duì)應(yīng)的 node,并調(diào)用 gb.insertNode 將該 node 加到 uidToNode 中,然后將該 node 添加到其 owner 的 dependents 中,執(zhí)行完 gb.insertNode 中的操作后再調(diào)用 gb.processTransitions 方法判斷該對(duì)象是否處于刪除狀態(tài),若處于刪除狀態(tài)會(huì)判斷該對(duì)象是以 orphan 模式刪除還是以 foreground 模式刪除,若以 orphan 模式刪除,則將該 node 加入到 attemptToOrphan 隊(duì)列中,若以 foreground 模式刪除則將該對(duì)象以及其所有 dependents 都加入到 attemptToDelete 隊(duì)列中;

  • 5、若 uidToNode 中存在該 node 且該事件是 addEvent 或 updateEvent 時(shí),此時(shí)可能是一個(gè) update 操作,調(diào)用 referencesDiffs 方法檢查該對(duì)象的 OwnerReferences 字段是否有變化,若有變化(1)調(diào)用 gb.addUnblockedOwnersToDeleteQueue 將被刪除以及更新的 owner 對(duì)應(yīng)的 node 加入到 attemptToDelete 中,因?yàn)榇藭r(shí)該 node 中已被刪除或更新的 owner 可能處于刪除狀態(tài)且阻塞在該 node 處,此時(shí)有三種方式避免該 node 的 owner 處于刪除阻塞狀態(tài),一是等待該 node 被刪除,二是將該 node 自身對(duì)應(yīng) owner 的 OwnerReferences 字段刪除,三是將該 node OwnerReferences 字段中對(duì)應(yīng) owner 的 BlockOwnerDeletion 設(shè)置為 false;(2)更新該 node 的 owners 列表;(3)若有新增的 owner,將該 node 加入到新 owner 的 dependents 中;(4) 若有被刪除的 owner,將該 node 從已刪除 owner 的 dependents 中刪除;以上操作完成后,檢查該 node 是否處于刪除狀態(tài)并進(jìn)行標(biāo)記,最后調(diào)用 gb.processTransitions 方法檢查該 node 是否要被刪除;

    舉個(gè)例子,若以 foreground 模式刪除 deployment 時(shí),deployment 的 dependents 列表中有對(duì)應(yīng)的 rs,那么 deployment 的刪除會(huì)阻塞住等待其依賴 rs 的刪除,此時(shí) rs 有三種方法不阻塞 deployment 的刪除操作,一是 rs 對(duì)象被刪除,二是刪除 rs 對(duì)象 OwnerReferences 字段中對(duì)應(yīng)的 deployment,三是將 rs 對(duì)象OwnerReferences 字段中對(duì)應(yīng)的 deployment 配置 BlockOwnerDeletion 設(shè)置為 false,文末會(huì)有示例演示該操作。

  • 6、若該事件為 deleteEvent,首先從 uidToNode 中刪除該對(duì)象,然后從該 node 所有 owners 的 dependents 中刪除該對(duì)象,將該 node 所有的 dependents 加入到 attemptToDelete 隊(duì)列中,最后檢查該 node 的所有 owners,若有處于刪除狀態(tài)的 owner,此時(shí)該 owner 可能處于刪除阻塞狀態(tài)正在等待該 node 的刪除,將該 owner 加入到 attemptToDelete 中;

總結(jié)一下,當(dāng)從 graphChanges 中取出 event 時(shí),不管是什么 event,主要完成三件時(shí),首先都會(huì)將 event 轉(zhuǎn)化為 uidToNode 中的 node 對(duì)象,其次一是更新 uidToNode 中維護(hù)的依賴關(guān)系,二是更新該 node 的 owners 以及 owners 的 dependents,三是檢查該 node 的 owners 是否要被刪除以及該 node 的 dependents 是否要被刪除,若需要?jiǎng)h除則根據(jù) node 的刪除策略將其添加到 attemptToOrphan 或者 attemptToDelete 隊(duì)列中;

k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:526

func (gb *GraphBuilder) runProcessGraphChanges() {
    for gb.processGraphChanges() {
    }
}

func (gb *GraphBuilder) processGraphChanges() bool {
        // 1、從 graphChanges 取出一個(gè) event
    item, quit := gb.graphChanges.Get()
    if quit {
        return false
    }
    defer gb.graphChanges.Done(item)
    event, ok := item.(*event)
    if !ok {
        utilruntime.HandleError(fmt.Errorf("expect a *event, got %v", item))
        return true
    }
    obj := event.obj
    accessor, err := meta.Accessor(obj)
    if err != nil {
        utilruntime.HandleError(fmt.Errorf("cannot access obj: %v", err))
        return true
    }

        // 2、若存在 node 對(duì)象,從 uidToNode 中取出該 event 的 node 對(duì)象
    existingNode, found := gb.uidToNode.Read(accessor.GetUID())
    if found {
        existingNode.markObserved()
    }
    switch {
    // 3、若 event 為 add 或 update 類(lèi)型以及對(duì)應(yīng)的 node 對(duì)象不存在時(shí)
    case (event.eventType == addEvent || event.eventType == updateEvent) && !found:
        // 4、為 node 創(chuàng)建 event 對(duì)象
        newNode := &node{
                    ......
        }
        // 5、在 uidToNode 中添加該 node 對(duì)象
        gb.insertNode(newNode)

                // 6、檢查并處理 node 的刪除操作 
        gb.processTransitions(event.oldObj, accessor, newNode)
        
    // 7、若 event 為 add 或 update 類(lèi)型以及對(duì)應(yīng)的 node 對(duì)象存在時(shí)
    case (event.eventType == addEvent || event.eventType == updateEvent) && found:
        added, removed, changed := referencesDiffs(existingNode.owners, accessor.GetOwnerReferences())
        // 8、若 node 的 owners 有變化
        if len(added) != 0 || len(removed) != 0 || len(changed) != 0 {

            gb.addUnblockedOwnersToDeleteQueue(removed, changed)
            // 9、更新 uidToNode 中的 owners
            existingNode.owners = accessor.GetOwnerReferences()
            // 10、添加更新后 Owners 對(duì)應(yīng)的 dependent
            gb.addDependentToOwners(existingNode, added)
            // 11、移除舊 owners 對(duì)應(yīng)的 dependents
            gb.removeDependentFromOwners(existingNode, removed)
        }
                
                // 12、檢查是否處于刪除狀態(tài)
        if beingDeleted(accessor) {
            existingNode.markBeingDeleted()
        }
        // 13、檢查并處理 node 的刪除操作 
        gb.processTransitions(event.oldObj, accessor, existingNode)
        
    // 14、若為 delete event
    case event.eventType == deleteEvent:
        if !found {
            return true
        }
        // 15、從 uidToNode 中刪除該 node
        gb.removeNode(existingNode)
        existingNode.dependentsLock.RLock()
        defer existingNode.dependentsLock.RUnlock()
        if len(existingNode.dependents) > 0 {
            gb.absentOwnerCache.Add(accessor.GetUID())
        }
        // 16、刪除該 node 的 dependents
        for dep := range existingNode.dependents {
            gb.attemptToDelete.Add(dep)
        }
        // 17、刪除該 node 處于刪除阻塞狀態(tài)的 owner 
        for _, owner := range existingNode.owners {
            ownerNode, found := gb.uidToNode.Read(owner.UID)
            if !found || !ownerNode.isDeletingDependents() {
                continue
            }
            gb.attemptToDelete.Add(ownerNode)
        }
    }
    return true
}
processTransitions

上述在處理 add 或 update event 時(shí)最后都調(diào)用了 processTransitions 方法檢查 node 是否處于刪除狀態(tài),若處于刪除狀態(tài)會(huì)通過(guò)其刪除策略將 node 放到 attemptToOrphan 或 attemptToDelete 隊(duì)列中。

k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:509

func (gb *GraphBuilder) processTransitions(oldObj interface{}, newAccessor metav1.Object, n *node) {
    if startsWaitingForDependentsOrphaned(oldObj, newAccessor) {
        gb.attemptToOrphan.Add(n)
        return
    }
    if startsWaitingForDependentsDeleted(oldObj, newAccessor) {
        n.markDeletingDependents()
        for dep := range n.dependents {
            gb.attemptToDelete.Add(dep)
        }
        gb.attemptToDelete.Add(n)
    }
}
gc.runAttemptToDeleteWorker

runAttemptToDeleteWorker 是執(zhí)行刪除 attemptToDelete 中 node 的方法,其主要邏輯為:

  • 1、調(diào)用 gc.attemptToDeleteItem 刪除 node;
  • 2、若刪除失敗則重新加入到 attemptToDelete 隊(duì)列中進(jìn)行重試;

k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:280

func (gc *GarbageCollector) runAttemptToDeleteWorker() {
    for gc.attemptToDeleteWorker() {
    }
}

func (gc *GarbageCollector) attemptToDeleteWorker() bool {
    item, quit := gc.attemptToDelete.Get()
    gc.workerLock.RLock()
    defer gc.workerLock.RUnlock()
    if quit {
        return false
    }
    defer gc.attemptToDelete.Done(item)
    n, ok := item.(*node)
    if !ok {
        utilruntime.HandleError(fmt.Errorf("expect *node, got %#v", item))
        return true
    }
    err := gc.attemptToDeleteItem(n)
    if err != nil {
        if _, ok := err.(*restMappingError); ok {
            klog.V(5).Infof("error syncing item %s: %v", n, err)
        } else {
            utilruntime.HandleError(fmt.Errorf("error syncing item %s: %v", n, err))
        }
        gc.attemptToDelete.AddRateLimited(item)
    } else if !n.isObserved() {
        gc.attemptToDelete.AddRateLimited(item)
    }
    return true
}

gc.runAttemptToDeleteWorker 中調(diào)用了 gc.attemptToDeleteItem 執(zhí)行實(shí)際的刪除操作。

gc.attemptToDeleteItem

gc.attemptToDeleteItem 的主要邏輯為:

  • 1、判斷 node 是否處于刪除狀態(tài);

  • 2、從 apiserver 獲取該 node 最新的狀態(tài),該 node 可能為 virtual node,若為 virtual node 則從 apiserver 中獲取不到該 node 的對(duì)象,此時(shí)會(huì)將該 node 重新加入到 graphChanges 隊(duì)列中,再次處理該 node 時(shí)會(huì)將其從 uidToNode 中刪除;

  • 3、判斷該 node 最新?tīng)顟B(tài)的 uid 是否等于本地緩存中的 uid,若不匹配說(shuō)明該 node 已更新過(guò)此時(shí)將其設(shè)置為 virtual node 并重新加入到 graphChanges 隊(duì)列中,再次處理該 node 時(shí)會(huì)將其從 uidToNode 中刪除;

  • 4、通過(guò) node 的 deletingDependents 字段判斷該 node 當(dāng)前是否處于刪除 dependents 的狀態(tài),若該 node 處于刪除 dependents 的狀態(tài)則調(diào)用 processDeletingDependentsItem 方法檢查 node 的 blockingDependents 是否被完全刪除,若 blockingDependents 已完全被刪除則刪除該 node 對(duì)應(yīng)的 finalizer,若 blockingDependents 還未刪除完,將未刪除的 blockingDependents 加入到 attemptToDelete 中;

    上文中在 GraphBuilder 處理 graphChanges 中的事件時(shí),若發(fā)現(xiàn) node 處于刪除狀態(tài),會(huì)將 node 的 dependents 加入到 attemptToDelete 中并標(biāo)記 node 的 deletingDependents 為 true;

  • 5、調(diào)用 gc.classifyReferences 將 node 的 ownerReferences 分類(lèi)為 solid, dangling, waitingForDependentsDeletion 三類(lèi):dangling(owner 不存在)、waitingForDependentsDeletion(owner 存在,owner 處于刪除狀態(tài)且正在等待其 dependents 被刪除)、solid(至少有一個(gè) owner 存在且不處于刪除狀態(tài));

  • 6、對(duì)以上分類(lèi)進(jìn)行不同的處理,若 solid不為 0 即當(dāng)前 node 至少存在一個(gè) owner,該對(duì)象還不能被回收,此時(shí)需要將 danglingwaitingForDependentsDeletion 列表中的 owner 從 node 的 ownerReferences 刪除,即已經(jīng)被刪除或等待刪除的引用從對(duì)象中刪掉;

  • 7、第二種情況是該 node 的 owner 處于 waitingForDependentsDeletion 狀態(tài)并且 node 的 dependents 未被完全刪除,該 node 需要等待刪除完所有的 dependents 后才能被刪除;

  • 8、第三種情況就是該 node 已經(jīng)沒(méi)有任何 dependents 了,此時(shí)按照 node 中聲明的刪除策略調(diào)用 apiserver 的接口刪除即可;

k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:404

func (gc *GarbageCollector) attemptToDeleteItem(item *node) error {
        // 1、判斷 node 是否處于刪除狀態(tài)
    if item.isBeingDeleted() && !item.isDeletingDependents() {
        return nil
    }
        
        // 2、從 apiserver 獲取該 node 最新的狀態(tài)
    latest, err := gc.getObject(item.identity)
    switch {
    case errors.IsNotFound(err):
        gc.dependencyGraphBuilder.enqueueVirtualDeleteEvent(item.identity)
        item.markObserved()
        return nil
    case err != nil:
        return err
    }
    
    // 3、判斷該 node 最新?tīng)顟B(tài)的 uid 是否等于本地緩存中的 uid
    if latest.GetUID() != item.identity.UID {
        gc.dependencyGraphBuilder.enqueueVirtualDeleteEvent(item.identity)
        item.markObserved()
        return nil
    }

        // 4、判斷該 node 當(dāng)前是否處于刪除 dependents 狀態(tài)中
    if item.isDeletingDependents() {
        return gc.processDeletingDependentsItem(item)
    }

    // 5、檢查 node 是否還存在 ownerReferences
    ownerReferences := latest.GetOwnerReferences()
    if len(ownerReferences) == 0 {
        return nil
    }

        // 6、對(duì) ownerReferences 進(jìn)行分類(lèi)
    solid, dangling, waitingForDependentsDeletion, err := gc.classifyReferences(item, ownerReferences)
    if err != nil {
        return err
    }
    switch {
    // 7、存在不處于刪除狀態(tài)的 owner
    case len(solid) != 0:
        if len(dangling) == 0 && len(waitingForDependentsDeletion) == 0 {
            return nil
        }
        ownerUIDs := append(ownerRefsToUIDs(dangling), ownerRefsToUIDs(waitingForDependentsDeletion)...)
        patch := deleteOwnerRefStrategicMergePatch(item.identity.UID, ownerUIDs...)
        _, err = gc.patch(item, patch, func(n *node) ([]byte, error) {
            return gc.deleteOwnerRefJSONMergePatch(n, ownerUIDs...)
        })
        return err
    // 8、node 的 owner 處于 waitingForDependentsDeletion 狀態(tài)并且 node 
    //   的 dependents 未被完全刪除
    case len(waitingForDependentsDeletion) != 0 && item.dependentsLength() != 0:
        deps := item.getDependents()
        // 9、刪除 dependents
        for _, dep := range deps {
            if dep.isDeletingDependents() {
                patch, err := item.unblockOwnerReferencesStrategicMergePatch()
                if err != nil {
                    return err
                }
                if _, err := gc.patch(item, patch, gc.unblockOwnerReferencesJSONMergePatch); err != nil {
                    return err
                }
                break
            }
        }
        // 10、以 Foreground 模式刪除 node 對(duì)象
        policy := metav1.DeletePropagationForeground
        return gc.deleteObject(item.identity, &policy)
    // 11、該 node 已經(jīng)沒(méi)有任何依賴了,按照 node 中聲明的刪除策略調(diào)用 apiserver 的接口刪除
    default:
        var policy metav1.DeletionPropagation
        switch {
        case hasOrphanFinalizer(latest):
            policy = metav1.DeletePropagationOrphan
        case hasDeleteDependentsFinalizer(latest):
            policy = metav1.DeletePropagationForeground
        default:
            policy = metav1.DeletePropagationBackground
        }
        return gc.deleteObject(item.identity, &policy)
    }
}
gc.runAttemptToOrphanWorker

runAttemptToOrphanWorker 是處理以 orphan 模式刪除的 node,主要邏輯為:

  • 1、調(diào)用 gc.orphanDependents 刪除 owner 所有 dependents OwnerReferences 中的 owner 字段;
  • 2、調(diào)用 gc.removeFinalizer 刪除 owner 的 orphan Finalizer;
  • 3、以上兩步中若有失敗的會(huì)進(jìn)行重試;

k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:574

func (gc *GarbageCollector) runAttemptToOrphanWorker() {
    for gc.attemptToOrphanWorker() {
    }
}

func (gc *GarbageCollector) attemptToOrphanWorker() bool {
    item, quit := gc.attemptToOrphan.Get()
    gc.workerLock.RLock()
    defer gc.workerLock.RUnlock()
    if quit {
        return false
    }
    defer gc.attemptToOrphan.Done(item)
    owner, ok := item.(*node)
    if !ok {
        return true
    }
    owner.dependentsLock.RLock()
    dependents := make([]*node, 0, len(owner.dependents))
    for dependent := range owner.dependents {
        dependents = append(dependents, dependent)
    }
    owner.dependentsLock.RUnlock()
    err := gc.orphanDependents(owner.identity, dependents)
    if err != nil {
        gc.attemptToOrphan.AddRateLimited(item)
        return true
    }
    // 更新 owner, 從 finalizers 列表中移除 "orphaningFinalizer"
    err = gc.removeFinalizer(owner, metav1.FinalizerOrphanDependents)
    if err != nil {
        gc.attemptToOrphan.AddRateLimited(item)
    }
    return true
}

garbageCollector.Sync

garbageCollector.SyncstartGarbageCollectorController 中的第三個(gè)核心方法,主要功能是周期性的查詢集群中所有的資源,過(guò)濾出 deletableResources,然后對(duì)比已經(jīng)監(jiān)控的 deletableResources 和當(dāng)前獲取到的 deletableResources 是否一致,若不一致則更新 GraphBuilder 的 monitors 并重新啟動(dòng) monitors 監(jiān)控所有的 deletableResources,該方法的主要邏輯為:

  • 1、通過(guò)調(diào)用 GetDeletableResources 獲取集群內(nèi)所有的 deletableResources 作為 newResources,deletableResources 指支持 "delete", "list", "watch" 三種操作的 resource,包括 CR;
  • 2、檢查 oldResources, newResources 是否一致,不一致則需要同步;
  • 3、調(diào)用 gc.resyncMonitors 同步 newResources,在 gc.resyncMonitors 中會(huì)重新調(diào)用 GraphBuilder 的 syncMonitorsstartMonitors 兩個(gè)方法完成 monitors 的刷新;
  • 4、等待 newResources informer 中的 cache 同步完成;
  • 5、將 newResources 作為 oldResources,繼續(xù)進(jìn)行下一輪的同步;

k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:164

func (gc *GarbageCollector) Sync(discoveryClient discovery.ServerResourcesInterface, period time.Duration, stopCh <-chan struct{}) {
    oldResources := make(map[schema.GroupVersionResource]struct{})
    wait.Until(func() {
        // 1、獲取集群內(nèi)所有的 DeletableResources 作為 newResources
        newResources := GetDeletableResources(discoveryClient)

        if len(newResources) == 0 {
            return
        }

        // 2、判斷集群中的資源是否有變化
        if reflect.DeepEqual(oldResources, newResources) {
            return
        }

        gc.workerLock.Lock()
        defer gc.workerLock.Unlock()
       
        // 3、開(kāi)始更新 GraphBuilder 中的 monitors      
        attempt := 0
        wait.PollImmediateUntil(100*time.Millisecond, func() (bool, error) {
            attempt++

            if attempt > 1 {
                newResources = GetDeletableResources(discoveryClient)
                if len(newResources) == 0 {
                    return false, nil
                }
            }

            gc.restMapper.Reset()
                        // 4、調(diào)用 gc.resyncMonitors 同步 newResources
            if err := gc.resyncMonitors(newResources); err != nil {
                return false, nil
            }

                        // 5、等待所有 monitors 的 cache 同步完成
            if !cache.WaitForNamedCacheSync("garbage collector", waitForStopOrTimeout(stopCh, period), gc.dependencyGraphBuilder.IsSynced) {
                return false, nil
            }

            return true, nil
        }, stopCh)

                // 6、更新 oldResources
        oldResources = newResources
    }, period, stopCh)
}

garbageCollector.Sync 中主要調(diào)用了兩個(gè)方法,一是調(diào)用 GetDeletableResources 獲取集群中所有的可刪除資源,二是調(diào)用 gc.resyncMonitors 更新 GraphBuilder 中 monitors。

GetDeletableResources

GetDeletableResources 中首先通過(guò)調(diào)用 discoveryClient.ServerPreferredResources 方法獲取集群內(nèi)所有的 resource 信息,然后通過(guò)調(diào)用 discovery.FilteredBy 過(guò)濾出支持 "delete", "list", "watch" 三種方法的 resource 作為 deletableResources

k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:636

func GetDeletableResources(discoveryClient discovery.ServerResourcesInterface) map[schema.GroupVersionResource]struct{} {
        // 1、調(diào)用 discoveryClient.ServerPreferredResources 方法獲取集群內(nèi)所有的 resource 信息
    preferredResources, err := discoveryClient.ServerPreferredResources()
    if err != nil {
        if discovery.IsGroupDiscoveryFailedError(err) {     
            ......
        } else {
            ......
        }
    }
    if preferredResources == nil {
        return map[schema.GroupVersionResource]struct{}{}
    }
        // 2、調(diào)用 discovery.FilteredBy 過(guò)濾出 deletableResources
    deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete", "list", "watch"}}, preferredResources)
    deletableGroupVersionResources := map[schema.GroupVersionResource]struct{}{}
    for _, rl := range deletableResources {
        gv, err := schema.ParseGroupVersion(rl.GroupVersion)
        if err != nil {
            continue
        }
        for i := range rl.APIResources {
            deletableGroupVersionResources[schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: rl.APIResources[i].Name}] =       struct{}{}
        }
    }

    return deletableGroupVersionResources
}
ServerPreferredResources

ServerPreferredResources 的主要功能是獲取集群內(nèi)所有的 resource 以及其 group、version、verbs 信息,該方法的主要邏輯為:

  • 1、調(diào)用 ServerGroups 方法獲取集群內(nèi)所有的 GroupList,ServerGroups 方法首先從 apiserver 通過(guò) /api URL 獲取當(dāng)前版本下所有可用的 APIVersions,再通過(guò) /apis URL 獲取 所有可用的 APIVersions 以及其下的所有 APIGroupList;
  • 2、調(diào)用 fetchGroupVersionResources 通過(guò) serverGroupList 再獲取到對(duì)應(yīng)的 resource;
  • 3、將獲取到的 version、group、resource 構(gòu)建成標(biāo)準(zhǔn)格式添加到 metav1.APIResourceList 中;

k8s.io/kubernetes/staging/src/k8s.io/client-go/discovery/discovery_client.go:285

func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {
    // 1、獲取集群內(nèi)所有的 GroupList
    serverGroupList, err := d.ServerGroups()
    if err != nil {
        return nil, err
    }

        // 2、通過(guò) serverGroupList 獲取到對(duì)應(yīng)的 resource
    groupVersionResources, failedGroups := fetchGroupVersionResources(d, serverGroupList)

    result := []*metav1.APIResourceList{}
    grVersions := map[schema.GroupResource]string{}                         // selected version of a GroupResource
    grAPIResources := map[schema.GroupResource]*metav1.APIResource{}        // selected APIResource for a GroupResource
    gvAPIResourceLists := map[schema.GroupVersion]*metav1.APIResourceList{} // blueprint for a APIResourceList for later grouping

        // 3、格式化 resource 
    for _, apiGroup := range serverGroupList.Groups {
        for _, version := range apiGroup.Versions {
            groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}

            apiResourceList, ok := groupVersionResources[groupVersion]
            if !ok {
                continue
            }

            emptyAPIResourceList := metav1.APIResourceList{
                GroupVersion: version.GroupVersion,
            }
            gvAPIResourceLists[groupVersion] = &emptyAPIResourceList
            result = append(result, &emptyAPIResourceList)
            
            for i := range apiResourceList.APIResources {
                apiResource := &apiResourceList.APIResources[i]
                if strings.Contains(apiResource.Name, "/") {
                    continue
                }
                gv := schema.GroupResource{Group: apiGroup.Name, Resource: apiResource.Name}
                if _, ok := grAPIResources[gv]; ok && version.Version != apiGroup.PreferredVersion.Version {
                    continue
                }
                grVersions[gv] = version.Version
                grAPIResources[gv] = apiResource
            }
        }
    }

    for groupResource, apiResource := range grAPIResources {
        version := grVersions[groupResource]
        groupVersion := schema.GroupVersion{Group: groupResource.Group, Version: version}
        apiResourceList := gvAPIResourceLists[groupVersion]
        apiResourceList.APIResources = append(apiResourceList.APIResources, *apiResource)
    }

    if len(failedGroups) == 0 {
        return result, nil
    }

    return result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
}

GetDeletableResources 方法中的調(diào)用流程為:

                                                      |--> d.ServerGroups
                                                      |
                        |--> discoveryClient.       --|
                        |  ServerPreferredResources   |
                        |                             |--> fetchGroupVersionResources
GetDeletableResources --|
                        |
                        |--> discovery.FilteredBy
gc.resyncMonitors

gc.resyncMonitors 的主要功能是更新 GraphBuilder 的 monitors 并重新啟動(dòng) monitors 監(jiān)控所有的 deletableResources,GraphBuilder 的 syncMonitorsstartMonitors 方法在前面的流程中已經(jīng)分析過(guò),此處不再詳細(xì)說(shuō)明。

k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:116

func (gc *GarbageCollector) resyncMonitors(deletableResources map[schema.                          GroupVersionResource]struct{}) error {
    if err := gc.dependencyGraphBuilder.syncMonitors(deletableResources); err != nil {
        return err
    }
    gc.dependencyGraphBuilder.startMonitors()
    return nil
}

garbagecollector.NewDebugHandler

garbagecollector.NewDebugHandler 主要功能是對(duì)外提供一個(gè)接口供用戶查詢當(dāng)前集群中所有資源的依賴關(guān)系,依賴關(guān)系可以以圖表的形式展示。

func startGarbageCollectorController(ctx ControllerContext) (http.Handler, bool, error) {
    ......
    return garbagecollector.NewDebugHandler(garbageCollector), true, nil
}

具體使用方法如下所示:

$ curl http://192.168.99.108:10252/debug/controllers/garbagecollector/graph  > tmp.dot

$ curl http://192.168.99.108:10252/debug/controllers/garbagecollector/graph\?uid=f9555d53-2b5f-4702-9717-54a313ed4fe8 > tmp.dot

// 生成 svg 文件
$ dot -Tsvg -o graph.svg tmp.dot

// 然后在瀏覽器中打開(kāi) svg 文件

依賴關(guān)系圖如下所示:

示例

在此處會(huì)有一個(gè)小示例驗(yàn)證一下源碼中的刪除阻塞邏輯,當(dāng)以 Foreground 策略刪除一個(gè)對(duì)象時(shí),該對(duì)象會(huì)處于阻塞狀態(tài)等待其依依賴被刪除,此時(shí)有三種方式避免該對(duì)象處于刪除阻塞狀態(tài),一是將依賴對(duì)象直接刪除,二是將依賴對(duì)象自身的 OwnerReferences 中 owner 字段刪除,三是將該依賴對(duì)象 OwnerReferences 字段中對(duì)應(yīng) owner 的 BlockOwnerDeletion 設(shè)置為 false,下面會(huì)驗(yàn)證下這三種方式,首先創(chuàng)建一個(gè) deployment,deployment 創(chuàng)建出的 rs 默認(rèn)不會(huì)有 foregroundDeletion finalizers,此時(shí)使用 kubectl edit 手動(dòng)加上 foregroundDeletion finalizers,當(dāng) deployment 正常運(yùn)行時(shí),如下所示:

$ kubectl get deployment  nginx-deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2/2     2            2           43s

$ kubectl get rs nginx-deployment-69b6b4c5cd
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-69b6b4c5cd   2         2         2       57s

$ kubectl get pod
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-69b6b4c5cd-26dsn   1/1     Running   0          66s
nginx-deployment-69b6b4c5cd-6rqqc   1/1     Running   0          64s

$ kubectl edit rs nginx-deployment-69b6b4c5cd   
  
// deployment 關(guān)聯(lián)的 rs 對(duì)象  
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx-deployment-69b6b4c5cd
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: Deployment
    name: nginx-deployment
    uid: 40a1044e-03d1-48bc-8806-cb79d781c946
  finalizers:
  - foregroundDeletion       // 為 rs 手動(dòng)添加的 Foreground 策略
  ......
spec:
  replicas: 2
  ......
status:
  ......

當(dāng) deployment、rs、pod 都處于正常運(yùn)行狀態(tài)且 deployment 關(guān)聯(lián)的 rs 使用 Foreground 刪除策略時(shí),然后驗(yàn)證源碼中提到的三種方法,驗(yàn)證時(shí)需要模擬一個(gè)依賴對(duì)象無(wú)法刪除的場(chǎng)景,當(dāng)然這個(gè)也很好模擬,三種場(chǎng)景如下所示:

  • 1、當(dāng) pod 所在的 node 處于 Ready 狀態(tài)時(shí),以 Foreground 策略刪除 deploment,因?yàn)?rs 關(guān)聯(lián)的 pod 會(huì)直接被刪除,rs 也會(huì)被正常刪除,此時(shí) deployment 也會(huì)直接被刪除;

  • 2、當(dāng) pod 所在的 node 處于 NotReady 狀態(tài)時(shí),以 Foreground 策略刪除 deploment,此時(shí)因 rs 關(guān)聯(lián)的 pod 無(wú)法被刪除,rs 會(huì)一直處于刪除阻塞狀態(tài),deployment 由于 rs 無(wú)法被刪除也會(huì)處于刪除阻塞狀態(tài),此時(shí)更新 rs 去掉其 ownerReferences 中對(duì)應(yīng)的 deployment 部分,deployment 會(huì)因無(wú)依賴對(duì)象被成功刪除;

  • 3、和 2 同樣的場(chǎng)景,node 處于 NotReady 狀態(tài)時(shí),以 Foreground 策略刪除 deploment,deployment 和 rs 將處于刪除阻塞狀態(tài),此時(shí)將 rs ownerReferences 中關(guān)聯(lián) deployment 的 blockOwnerDeletion 字段設(shè)置為 false,可以看到 deployment 會(huì)因無(wú) block 依賴對(duì)象被成功刪除;

$ systemctl stop kubelet

// node 處于 NotReady 狀態(tài)
$ kubectl get node
NAME       STATUS     ROLES    AGE     VERSION
minikube   NotReady   master   6d11h   v1.16.2

// 以 Foreground 策略刪除 deployment
$ curl -k -v -XDELETE  -H "Accept: application/json" -H "Content-Type: application/json" -d '{"propagationPolicy":"Foreground"}' 'https://192.168.99.108:8443/apis/apps/v1/namespaces/default/deployments/nginx-deployment'

總結(jié)

GarbageCollectorController 是一種典型的生產(chǎn)者消費(fèi)者模型,所有 deletableResources 的 informer 都是生產(chǎn)者,每種資源的 informer 監(jiān)聽(tīng)到變化后都會(huì)將對(duì)應(yīng)的事件 push 到 graphChanges 中,graphChanges 是 GraphBuilder 對(duì)象中的一個(gè)數(shù)據(jù)結(jié)構(gòu),GraphBuilder 會(huì)啟動(dòng)另外的 goroutine 對(duì) graphChanges 中的事件進(jìn)行分類(lèi)并放在其 attemptToDelete 和 attemptToOrphan 兩個(gè)隊(duì)列中,garbageCollector 會(huì)啟動(dòng)多個(gè) goroutine 對(duì) attemptToDelete 和 attemptToOrphan 兩個(gè)隊(duì)列中的事件進(jìn)行處理,處理的結(jié)果就是回收一些需要被刪除的對(duì)象。最后,再用一個(gè)流程圖總結(jié)一下 GarbageCollectorController 的主要流程:

                      monitors (producer)
                            |
                            |
                            ∨
                    graphChanges queue
                            |
                            |
                            ∨
                    processGraphChanges
                            |
                            |
                            ∨
            -------------------------------
            |                             |
            |                             |
            ∨                             ∨
  attemptToDelete queue         attemptToOrphan queue
            |                             |
            |                             |
            ∨                             ∨
    AttemptToDeleteWorker       AttemptToOrphanWorker
        (consumer)                    (consumer)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容