kubernetes垃圾回收器GarbageCollector Controller原始碼分析(二)
kubernetes版本:1.13.2
接上一節:kubernetes垃圾回收器GarbageCollector Controller原始碼分析(一)
主要步驟
GarbageCollector Controller原始碼主要分為以下幾部分:
monitors
作為生產者將變化的資源放入graphChanges
佇列;同時restMapper
定期檢測叢集內資源型別,重新整理monitors
runProcessGraphChanges
從graphChanges
佇列中取出變化的item
,根據情況放入attemptToDelete
佇列;runProcessGraphChanges
從graphChanges
item
,根據情況放入attemptToOrphan
佇列;runAttemptToDeleteWorker
從attemptToDelete
佇列取出,嘗試刪除垃圾資源;runAttemptToOrphanWorker
從attemptToDelete
佇列取出,處理該孤立的資源;
程式碼較複雜,便於講的更清楚,調整了下講解順序。上一節分析了第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 /