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源码分析(二)的更多相关文章

  1. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  2. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  3. Tomcat源码分析二:先看看Tomcat的整体架构

    Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...

  4. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  5. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  6. 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>

    目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...

  7. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  8. YII 的源码分析(二)

    上一篇简单分析了一下yii的流程,从创建一个应用,到屏幕上输出结果.这一次我来一个稍复杂一点的,重点在输出上,不再是简单的一行"hello world",而是要经过view(视图) ...

  9. Fresco 源码分析(二) Fresco客户端与服务端交互(3) 前后台打通

    4.2.1.2.4 PipelineDraweeControllerBuilder.obtainController()源码分析 续 上节中我们提到两个核心的步骤 obtainDataSourceSu ...

随机推荐

  1. 解决homebrew下install时出现的问题

    在homebrew下install   influxdb时出现error: Error: Failure while executing: git config --local --replace-a ...

  2. [0]尝试用Unity3d制作一个王者荣耀(持续更新)->游戏规划

    太得闲了于是想写个农药,虽然可能会失败但是还是要试一试. 因为是自学的不是Unity专业的可能表达语言会有些不标准!望见谅! 结构: 以组件式(比如说摇杆控制和玩家部分的编写是分离的,可以自由拼装)作 ...

  3. Java IO/输入与输出

    File类 File类是IO包中唯一代表磁盘文件本身信息的类,而不是文件中的内容 File类定义了一些与平台无关的方法来操纵文件,例如,创建,删除文件和重命名文件 Java中的目录被当作一种特殊文件, ...

  4. React Hooks 你不来了解下?

    前言 最近在看 React 的新语法-- React Hooks,只能一句话概括:React 语法真的是越来越强大,越写代码越少. 强烈推荐还没看 React Hooks 的同学去学习下,这会让你写r ...

  5. GO 语言学习笔记--数组切片篇

    1.对于make 数组切片,长度和容量需要理解清楚: 容量表示底层数组的大小,长度是你可以使用的大小: 容量的用处在哪?在与当你用 appen d扩展长度时,如果新的长度小于容量,不会更换底层数组,否 ...

  6. AutoCompleteTextView自动完成文本框

    AutoCompleteTextView是从EditText派生出来的,比普通编辑框多了一个功能,当用户输入一定字符后,自动完成文本框会显示一个下拉单,供用户选择,当选中一个后,被选中的内容会显示在文 ...

  7. Java中 a+=b 和 a=a+b 有什么区别?

    今天舍友突然问我"在java中 a+=b 和a=a+b 有什么区别",说这是一道面试题.当时就不假思索的回答:"一样啊",然后他说有位面试者也回答说一样,所以被 ...

  8. jupyter lab(notebook)相关配置

    安装的是Anaconda3(Python 3.6.4),自带的版本较低,这里升级版本conda update jupyterlab 一.配置jupyter lab(notebook)远程访问 1.1 ...

  9. nodejs实现聊天机器人

    技术栈 服务端: koa.koa-route.koa-websocket.request. 客户端: html.css.js.websocket. 远程聊天API: http://api.qingyu ...

  10. Tomcat 应用中并行流带来的类加载问题

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/f-X3n9cvDyU5f5NYH6mhxQ作者:肖铭轩.王道环 随着 Java8 的不断流行, ...