1. 程式人生 > >kubernetes垃圾回收器GarbageCollector Controller原始碼分析(二)

kubernetes垃圾回收器GarbageCollector Controller原始碼分析(二)

kubernetes版本:1.13.2

接上一節:kubernetes垃圾回收器GarbageCollector Controller原始碼分析(一)

主要步驟

GarbageCollector Controller原始碼主要分為以下幾部分:

  1. monitors作為生產者將變化的資源放入graphChanges佇列;同時restMapper定期檢測叢集內資源型別,重新整理monitors
  2. runProcessGraphChangesgraphChanges佇列中取出變化的item,根據情況放入attemptToDelete佇列;
  3. runProcessGraphChangesgraphChanges
    佇列中取出變化的item,根據情況放入attemptToOrphan佇列;
  4. runAttemptToDeleteWorkerattemptToDelete佇列取出,嘗試刪除垃圾資源;
  5. runAttemptToOrphanWorkerattemptToDelete佇列取出,處理該孤立的資源;

    程式碼較複雜,便於講的更清楚,調整了下講解順序。上一節分析了第1部分,本節分析第2、3部分。

runProcessGraphChanges處理主流程

來到原始碼k8s.io\kubernetes\pkg\controller\garbagecollector\graph_builder.go中,runProcessGraphChanges中一直死迴圈處理變化的資源物件:

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

一個協程一直迴圈從graphChanges佇列中獲取變化的資源物件,更新圖形,填充dirty_queue。(graphChanges佇列裡資料來源於各個資源的monitors監聽資源變化回撥addFunc、updateFunc、deleteFunc)

// Dequeueing an event from graphChanges, updating graph, populating dirty_queue.
//從graphChanges中獲取事件,更新圖形,填充dirty_queue。(graphChanges佇列裡資料來源於各個資源的monitors監聽資源變化回撥addFunc、updateFunc、deleteFunc)
func (gb *GraphBuilder) processGraphChanges() bool {
    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
    //獲取該變化資源obj的accessor
    accessor, err := meta.Accessor(obj)
    if err != nil {
        utilruntime.HandleError(fmt.Errorf("cannot access obj: %v", err))
        return true
    }
    klog.V(5).Infof("GraphBuilder process object: %s/%s, namespace %s, name %s, uid %s, event type %v", event.gvk.GroupVersion().String(), event.gvk.Kind, accessor.GetNamespace(), accessor.GetName(), string(accessor.GetUID()), event.eventType)
    // Check if the node already exists
    // 檢查節點是否已存在
    //根據該變化資源obj的UID
    //uidToNode維護著資源物件依賴關係圖表結構
    existingNode, found := gb.uidToNode.Read(accessor.GetUID())
    if found {
        // this marks the node as having been observed via an informer event
        // 1. this depends on graphChanges only containing add/update events from the actual informer
        // 2. this allows things tracking virtual nodes' existence to stop polling and rely on informer events
        //這標誌著節點已經通過informer事件
        // 1.進行了觀察。這取決於僅包含來自實際informer的新增/更新事件的graphChange
        // 2.這允許跟蹤虛擬節點的存在以停止輪詢和依賴informer事件
        existingNode.markObserved()
    }
    switch {
    //gc第一次執行時,uidToNode尚且沒有初始化資源物件依賴關係圖表結構,所以found為false,會新增節點
    case (event.eventType == addEvent || event.eventType == updateEvent) && !found:
        newNode := &node{
            identity: objectReference{
                OwnerReference: metav1.OwnerReference{
                    APIVersion: event.gvk.GroupVersion().String(),
                    Kind:       event.gvk.Kind,
                    UID:        accessor.GetUID(),
                    Name:       accessor.GetName(),
                },
                Namespace: accessor.GetNamespace(),
            },
            dependents:         make(map[*node]struct{}),
            owners:             accessor.GetOwnerReferences(),
            deletingDependents: beingDeleted(accessor) && hasDeleteDependentsFinalizer(accessor),
            beingDeleted:       beingDeleted(accessor),
        }
        gb.insertNode(newNode)
        // the underlying delta_fifo may combine a creation and a deletion into
        // one event, so we need to further process the event.
        //底層delta_fifo可以將建立和刪除組合成一個事件,因此我們需要進一步處理事件。
        gb.processTransitions(event.oldObj, accessor, newNode)
    //uidToNode已經初始化資源物件依賴關係圖表結構,所以found為true
    case (event.eventType == addEvent || event.eventType == updateEvent) && found:
        // handle changes in ownerReferences
        //處理ownerReferences中的更改
        added, removed, changed := referencesDiffs(existingNode.owners, accessor.GetOwnerReferences())
        if len(added) != 0 || len(removed) != 0 || len(changed) != 0 {
            // check if the changed dependency graph unblock owners that are
            // waiting for the deletion of their dependents.
            //檢查更改的依賴關係圖是否取消阻止等待刪除其依賴項的所有者。
            gb.addUnblockedOwnersToDeleteQueue(removed, changed)
            // update the node itself
            //更新node的owner
            existingNode.owners = accessor.GetOwnerReferences()
            // Add the node to its new owners' dependent lists.
            //給新owner新增依賴資源列表
            gb.addDependentToOwners(existingNode, added)
            // remove the node from the dependent list of node that are no longer in
            // the node's owners list.
            //從不再屬於該資源owner列表中刪除該節點。
            gb.removeDependentFromOwners(existingNode, removed)
        }

        // 該物件正在被刪除中
        if beingDeleted(accessor) {
            existingNode.markBeingDeleted()
        }
        gb.processTransitions(event.oldObj, accessor, existingNode)
    //處理資源物件被刪除的場景,涉及垃圾。比如,owner被刪除,其依賴資源(從資源)也需要被刪除掉,除非設定了Orphan
    case event.eventType == deleteEvent:
        if !found {
            klog.V(5).Infof("%v doesn't exist in the graph, this shouldn't happen", accessor.GetUID())
            return true
        }
        // 從圖示中移除item資源,同時遍歷owners,移除owner下的item資源
        gb.removeNode(existingNode)
        existingNode.dependentsLock.RLock()
        defer existingNode.dependentsLock.RUnlock()
        //如果該資源的從資源數大於0,則將該資源被刪除資訊加入absentOwnerCache快取
        if len(existingNode.dependents) > 0 {
            gb.absentOwnerCache.Add(accessor.GetUID())
        }
        //遍歷該資源的從資源加到刪除佇列裡
        for dep := range existingNode.dependents {
            gb.attemptToDelete.Add(dep)
        }
        for _, owner := range existingNode.owners {
            ownerNode, found := gb.uidToNode.Read(owner.UID)
            //owner沒發現 或者 owner的從資源不是正在被刪除(只有該資源物件的終結器為foregroundDeletion Finalizer時deletingDependents被設為true,因為後臺刪除owner直接被刪除,不會被其從資源block,故這裡都不需要去嘗試刪除owner了)
            if !found || !ownerNode.isDeletingDependents() {
                continue
            }
            
            // 這是讓attempToDeleteItem檢查是否刪除了owner的依賴項,如果是,則刪除所有者。
            gb.attemptToDelete.Add(ownerNode)
        }
    }
    return true
}

該方法功能主要將物件、owner、從資源加入到attemptToDelete或attemptToOrphan。

1、 出隊

從graphChanges佇列取出資源物件,從GraphBuilder.uidToNode中讀取該資源節點(uidToNode維護著資源物件依賴關係圖表結構),found為true時表示圖表存在該資源節點;

2、switch的第一個case

如果該資源是新增或者更新觸發,且該資源物件不存在於圖表中,gb.uidToNode.Write(n)會將其寫入圖示;
gb.insertNode(newNode)中的gb.addDependentToOwners(n, n.owners)方法則會遍歷該資源的owner,如果其owner不存在於圖示中,則新增owner的虛擬節點到圖示中,並將該資源和owner產生關聯。如果owner不存在時,則嘗試將owner加入到attemptToDelete佇列中去;

// addDependentToOwners將n新增到所有者的從屬列表中。如果所有者不存在於gb.uidToNode中,則將建立"虛擬"節點以表示
// 所有者。 "虛擬"節點將入隊到attemptToDelete,因此
// attemptToDeleteItem()將根據API伺服器驗證所有者是否存在。
func (gb *GraphBuilder) addDependentToOwners(n *node, owners []metav1.OwnerReference) {
    //遍歷owner
    for _, owner := range owners {
        //獲取owner node如果不存在於圖中,則加虛擬owner節點
        ownerNode, ok := gb.uidToNode.Read(owner.UID)
        if !ok {
            // Create a "virtual" node in the graph for the owner if it doesn't
            // exist in the graph yet.
            //如果圖形中尚未存在,則在圖表中為所有者建立“虛擬”節點。
            ownerNode = &node{
                identity: objectReference{
                    OwnerReference: owner,
                    Namespace:      n.identity.Namespace,
                },
                dependents: make(map[*node]struct{}),
                virtual:    true,
            }
            klog.V(5).Infof("add virtual node.identity: %s\n\n", ownerNode.identity)
            gb.uidToNode.Write(ownerNode)
        }
        //給owner加該資源作為依賴
        ownerNode.addDependent(n)
        //owner不存在於圖中時,才往刪除佇列新增
        if !ok {
            // Enqueue the virtual node into attemptToDelete.
            // The garbage processor will enqueue a virtual delete
            // event to delete it from the graph if API server confirms this
            // owner doesn't exist.
            //將虛擬節點排入attemptToDelete。
            // 如果API伺服器確認owner不存在,垃圾處理器將排隊虛擬刪除事件以將其從圖中刪除。
            gb.attemptToDelete.Add(ownerNode)
        }
    }
}

gb.processTransitions方法:
新item正在被刪,舊item沒開始被刪除,且終結器為Orphan Finalizer加入到attemptToOrphan佇列;
新item正在被刪,舊item沒開始被刪除,且終結器為foregroundDeletion Finalizer,則加入到attemptToDelete佇列。

func (gb *GraphBuilder) processTransitions(oldObj interface{}, newAccessor metav1.Object, n *node) {
    //新的正在被刪,舊的沒開始被刪除,且終結器為Orphan Finalizer
    if startsWaitingForDependentsOrphaned(oldObj, newAccessor) {
        klog.V(5).Infof("add %s to the attemptToOrphan", n.identity)
        //加入到Orphan佇列
        gb.attemptToOrphan.Add(n)
        return
    }

    //新的正在被刪,舊的沒開始被刪除,且終結器為foregroundDeletion Finalizer
    if startsWaitingForDependentsDeleted(oldObj, newAccessor) {
        klog.V(2).Infof("add %s to the attemptToDelete, because it's waiting for its dependents to be deleted", n.identity)
        // if the n is added as a "virtual" node, its deletingDependents field is not properly set, so always set it here.
        n.markDeletingDependents()
        for dep := range n.dependents {
            gb.attemptToDelete.Add(dep)
        }
        gb.attemptToDelete.Add(n)
    }
}

3、switch的第二個case

如果該資源是新增或者更新觸發,且該資源物件存在於圖表中。對比owneReferences是否有變更,referencesDiffs方法裡會根據uid對比,added表示新owner裡有,舊owner裡沒有的, removed表示舊owner裡有,新owner裡沒有的, changed表示相同uid的owner不deepEqual的。

func referencesDiffs(old []metav1.OwnerReference, new []metav1.OwnerReference) (added []metav1.OwnerReference, removed []metav1.OwnerReference, changed []ownerRefPair) {
    //key為uid, value為OwnerReference
    oldUIDToRef := make(map[string]metav1.OwnerReference)
    for _, value := range old {
        oldUIDToRef[string(value.UID)] = value
    }
    oldUIDSet := sets.StringKeySet(oldUIDToRef)

    //key為uid, value為OwnerReference
    newUIDToRef := make(map[string]metav1.OwnerReference)
    for _, value := range new {
        newUIDToRef[string(value.UID)] = value
    }
    newUIDSet := sets.StringKeySet(newUIDToRef)

    //新的裡有,舊的裡沒有的為新增(根據uid判斷)
    addedUID := newUIDSet.Difference(oldUIDSet)

    //舊的裡有,新的裡沒有的為刪除(根據uid判斷)
    removedUID := oldUIDSet.Difference(newUIDSet)

    //取交集, 舊的和新的裡都有的owner(根據uid判斷)
    intersection := oldUIDSet.Intersection(newUIDSet)

    for uid := range addedUID {
        added = append(added, newUIDToRef[uid])
    }
    for uid := range removedUID {
        removed = append(removed, oldUIDToRef[uid])
    }

    //根據uid判斷,兩個uid相等的OwnerReference是否deepEqual,不等則加到changed
    for uid := range intersection {
        if !reflect.DeepEqual(oldUIDToRef[uid], newUIDToRef[uid]) {
            changed = append(changed, ownerRefPair{oldRef: oldUIDToRef[uid], newRef: newUIDToRef[uid]})
        }
    }
    return added, removed, changed
}

整體來說,owner發生變化,addUnblockedOwnersToDeleteQueue方法會判斷:如果阻塞ownerReference指向某個物件被刪除,或者設定為BlockOwnerDeletion=false,則將該物件新增到attemptToDelete佇列;

// if an blocking ownerReference points to an object gets removed, or gets set to
// "BlockOwnerDeletion=false", add the object to the attemptToDelete queue.
//如果阻塞ownerReference指向某個物件被刪除,或者設定為
// "BlockOwnerDeletion = false",則將該物件新增到attemptToDelete佇列。
func (gb *GraphBuilder) addUnblockedOwnersToDeleteQueue(removed []metav1.OwnerReference, changed []ownerRefPair) {
    for _, ref := range removed {
        //被移除的OwnersReferences,BlockOwnerDeletion為true
        if ref.BlockOwnerDeletion != nil && *ref.BlockOwnerDeletion {
            //依賴圖表中發現,則加入刪除佇列
            node, found := gb.uidToNode.Read(ref.UID)
            if !found {
                klog.V(5).Infof("cannot find %s in uidToNode", ref.UID)
                continue
            }
            //加入嘗試刪除佇列刪除這個owner
            gb.attemptToDelete.Add(node)
        }
    }

    // Owners存在且發生變化,舊的BlockOwnerDeletion為true, 新的BlockOwnerDeletion為空或者BlockOwnerDeletion為false則刪除owner(父節點)
    for _, c := range changed {
        wasBlocked := c.oldRef.BlockOwnerDeletion != nil && *c.oldRef.BlockOwnerDeletion
        isUnblocked := c.newRef.BlockOwnerDeletion == nil || (c.newRef.BlockOwnerDeletion != nil && !*c.newRef.BlockOwnerDeletion)
        if wasBlocked && isUnblocked {
            node, found := gb.uidToNode.Read(c.newRef.UID)
            if !found {
                klog.V(5).Infof("cannot find %s in uidToNode", c.newRef.UID)
                continue
            }
            gb.attemptToDelete.Add(node)
        }
    }
}

更新node的owner;
在依賴圖表中給新owner新增該node;
在依賴圖表中,被刪除的owner列表下刪除該節點。

gb.processTransitions方法:
新item正在被刪,舊item沒開始被刪除,且終結器為Orphan Finalizer加入到attemptToOrphan佇列;
新item正在被刪,舊item沒開始被刪除,且終結器為foregroundDeletion Finalizer,則加入到attemptToDelete佇列。

4、switch的第三個case

如果該資源是刪除時觸發,從圖表中移除item資源,同時遍歷owners,移除owner下的item資源;
如果該資源的從資源數大於0,則將該資源被刪除資訊(uid)加入absentOwnerCache快取,這樣處理該資源的從資源時,就知道owner不存在了。
遍歷該資源的從資源加到刪除佇列裡;
如果從圖表中發現 owner或者 owner的從資源正在被刪除,則嘗試將owner加入到attemptToDelete佇列中,去嘗試刪除owner。

整理流程

  • 當controllermanager重啟時,會全量listwatch一遍所有物件,gc collector維護的uidToNode圖表裡各個資源物件node是不存在的,此時會走第一個switch case,構建完整關係圖表,如果owner不存在則先構建虛擬owner節點,同時加入attemptToDelete佇列,嘗試去刪除這個owner,其實即使加入到attemptToDelete佇列,也不一定會被刪除,還會進行一系列判斷,這個下一節再分析;將正在刪除的資源,同時Finalizer為Orphan的加入到attemptToOrphan佇列;為foreground的資源以及其從資源加入到attemptToDelete佇列,並將deletingDependents設定為true;
  • 新增或者更新事件時,且圖表中存在item資源物件時,會走第二個switch case,對item的owner變化進行判斷,並維護更新圖表;同理將正在刪除的資源,同時Finalizer為Orphan的加入到attemptToOrphan佇列;Finalizer為foreground的資源以及其從資源加入到attemptToDelete佇列,並將deletingDependents設定為true;
  • 如果是刪除事件,則會更新圖表,並處理和其相關的從資源和其owner加入到attemptToDelete佇列。

參考:

k8s官方文件garbage-collection英文版:
https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/

依賴圖示生成庫gonum Api文件:
https://godoc.org/gonum.org/v1/gonum/graph

graphviz下載:
https://graphviz.gitlab.io/_pages/Download/Download_windows.html



本公眾號免費提供csdn下載服務,海量IT學習資源,如果你準備入IT坑,勵志成為優秀的程式猿,那麼這些資源很適合你,包括但不限於java、go、python、springcloud、elk、嵌入式 、大資料、面試資料、前端 等資源。同時我們組建了一個技術交流群,裡面有很多大佬,會不定時分享技術文章,如果你想來一起學習提高,可以公眾號後臺回覆【2】,免費邀請加技術交流群互相學習提高,會不定期分享程式設計IT相關資源。


掃碼關注,精彩內容第一時間推給你

相關推薦

kubernetes垃圾回收GarbageCollector Controller原始碼分析

kubernetes版本:1.13.2 接上一節:kubernetes垃圾回收器GarbageCollector Controller原始碼分析(一) 主要步驟 GarbageCollector Controller原始碼主要分為以下幾部分: monitors作為生產者將變化的資源放入graphChan

kubernetes垃圾回收GarbageCollector原始碼分析

kubernetes版本:1.13.2 背景 由於operator建立的redis叢集,在kubernetes apiserver重啟後,redis叢集被異常刪除(包括redis exporter statefulset、redis statefulset)。刪除後operator將其重建,重新組建叢集,

Muduo網路庫原始碼分析 定時TimeQueue,Timer,TimerId

首先,我們先要明白為什麼需要設計這樣一個定時器類? 在開發Linux網路程式時,通常需要維護多個定時器,如維護客戶端心跳時間、檢查多個數據包的超時重傳等。如果採用linux的SIGALARM訊號實現,則會帶來較大的系統開銷,且不便於管理。 Muduo 的 Timer

零基礎讀懂視訊播放控制原理——ffplay播放原始碼分析

圖7音視訊解碼分析 圖7為輸出的音訊幀和視訊幀序列,每一幀都有PTS和DTS標籤,這兩個標籤究竟是什麼意思呢? DTS(Decode Time Stamp)和PTS(Presentation Time Stamp)都是時間戳,前者是解碼時間,後者是顯示時間,都是為視訊幀、音訊幀打上的時間標籤,以更

SpringMVC原始碼分析之請求如何轉發到對應的Controller

        在前一篇對DispatcherServlet的分析中,初略的過了下請求是如何處理的,本文將重點分析,HandlerMapping與HandlerAdapter是如何工作的          在web容器啟動的過程中,會初初始化一系列SpringMVC所需

Picasso原始碼分析:預設的下載、快取、執行緒池和轉換

下載器 當用戶沒有為Picasso指定下載器的時候Picasso會通過Utils.createDefaultDownloader(context)方法建立一個預設的下載器 static Downloader createDefaultDownlo

Flume NG原始碼分析支援執行時動態修改配置的配置模組

在上一篇中講了Flume NG配置模組基本的介面的類,PropertiesConfigurationProvider提供了基於properties配置檔案的靜態配置的能力,這篇細說一下PollingPropertiesFileConfigurationProvider提供的執行時動態修改配置並生效的

GCC原始碼分析——前端

原文連結:http://blog.csdn.net/sonicling/article/details/6706152   從這一篇開始,我們將從原始碼的角度來分析GCC如何完成對C語言原始檔的處理。GCC的內部構架在GCC Internals(搜“gccint.pdf”,或者見[

Glide原始碼分析——從用法來看之load&into方法

上一篇,我們分析了with方法,文章連結: https://blog.csdn.net/qq_36391075/article/details/82833260 在with方法中,進行了Glide的初始化,建立了RequesManger,並且綁定了生命週期,最終返回了一個Reques

YOLOv2原始碼分析

文章全部YOLOv2原始碼分析 接著上一講沒有講完的make_convolutional_layer函式 0x01 make_convolutional_layer //make_convolutional_laye

zigbee 之ZStack-2.5.1a原始碼分析 無線接收控制LED

本文描述ZStack-2.5.1a 模板及無線接收移植相關內容。 main HAL_BOARD_INIT // HAL_TURN_OFF_LED1 InitBoard HalDriverInit HalAdcInit

兄弟連區塊鏈入門教程eth原始碼分析p2p-udp.go原始碼分析

ping方法與pending的處理,之前談到了pending是等待一個reply。 這裡通過程式碼來分析是如何實現等待reply的。pending方法把pending結構體傳送給addpending. 然後等待訊息的處理和接收。 // ping sends a ping message to the giv

Spring原始碼分析IoC容器的實現1

    Ioc(Inversion of Control)——“控制反轉”,不是什麼技術,而是一種設計思想。在Java開發中,Ioc意味著將你設計好的物件交給容器控制,而不是傳統的在你的物件內部直接控制。理解好Ioc的關鍵是要明確“誰控制誰,控制什麼,為何是反轉(有

tornado原始碼分析之iostream

在事件驅動模型中,所有任務都是以某個事件的回撥函式的方式新增至事件迴圈中的,如:HTTPServer要從socket中讀取客戶端傳送的request訊息,就必須將該socket新增至ioloop中,並設定回掉函式,在回掉函式中從socket中讀取資料,並且檢查request訊息是否全部接收到了,如果

Cat原始碼分析:Server端

初始化 服務端消費客戶端發來的訊息進行分析和展示,所以這個的初始化指的是CatHomeModule的初始化 CatHomeModule依賴TcpSocketReceiver和MessageConsumer,前者用來接收客戶端傳送的訊息,後者用來消費訊息。 TcpSocket

subsampling-scale-image-view載入長圖原始碼分析

subsampling-scale-image-view原始碼分析概要分析總結 概要 subsampling-scale-image-view是一個支援部分載入大圖長圖的圖片庫,並且還支援縮放,在subsampling-scale-image-view載入長圖原

Spring component-scan原始碼分析 -- @Configuration註解處理

上篇文章Spring component-scan原始碼分析(一) – XML解析分析了Spring解析<context:component-scan …/>標籤時,把掃描到的合適的類封裝成BeanDefinition加入Sping容器中,本篇分析S

Spring原始碼分析IoC容器的實現3

BeanDefinition的載入和解析     這個載入過程,相當於把定義的BeanDefinition在IoC容器中轉化成一個Spring內部表示的資料結構的過程。IoC容器對Bean的管理和依賴注入功能的實現,是通過對其持有的BeanDefinition進

Spring原始碼分析IoC容器的實現2

IoC容器的初始化過程     簡單來說IoC容器的初始化是由refresh()方法啟動的,這個方法標誌著IoC容器的正式啟動。這個啟動包括BeanDefinition的Resouce定位、載入和註冊三個基本過程。     第一

groupcache 原始碼分析-- LRU

lru部分的程式碼在lru/lru.go檔案中,它主要是封裝了一系列lru演算法相關的介面,供groupcahe進行快取置換相關的呼叫。 它主要封裝了下面幾個介面: // 建立一個Cache func New(maxEntries int) *Cache /