写点什么

详解 Kubernetes 垃圾收集器的实现原理

2019 年 12 月 04 日

详解 Kubernetes 垃圾收集器的实现原理

垃圾收集器在 Kubernetes 中的作用就是删除之前有所有者但是现在所有者已经不存在的对象,例如删除 ReplicaSet 时会删除它依赖的 Pod,虽然它的名字是垃圾收集器,但是它在 Kubernetes 中还是以控制器的形式进行设计和实现的。


在 Kubernetes 引入垃圾收集器之前,所有的级联删除逻辑都是在客户端完成的,kubectl 会先删除 ReplicaSet 持有的 Pod 再删除 ReplicaSet,但是垃圾收集器的引入就让级联删除的实现移到了服务端,我们在这里就会介绍垃圾收集器的设计和实现原理。


概述

垃圾收集主要提供的功能就是级联删除,它向对象的 API 中加入了 metadata.ownerReferences 字段,这一字段会包含当前对象的所有依赖者,在默认情况下,如果当前对象的所有依赖者都被删除,那么当前对象就会被删除:


Go


type ObjectMeta struct {  ...  OwnerReferences []OwnerReference}
type OwnerReference struct { APIVersion string Kind string Name string UID types.UID}
复制代码


OwnerReference 包含了足够的信息来标识当前对象的依赖者,对象的依赖者必须与当前对象位于同一个命名空间 namespace,否则两者就无法建立起依赖关系。


通过引入 metadata.ownerReferences 能够建立起不同对象的关系,但是我们依然需要其他的组件来负责处理对象之间的联系并在所有依赖者不存在时将对象删除,这个处理不同对象联系的组件就是 GarbageCollector,也是 Kubernetes 控制器的一种。


实现原理

GarbageCollector 中包含一个 GraphBuilder 结构体,这个结构体会以 Goroutine 的形式运行并使用 Informer 监听集群中几乎全部资源的变动,一旦发现任何的变更事件 — 增删改,就会将事件交给主循环处理,主循环会根据事件的不同选择将待处理对象加入不同的队列,与此同时 GarbageCollector 持有的另外两组队列会负责删除或者孤立目标对象。


#mermaid-1575353277522 .label{font-family:trebuchet ms,verdana,arial;color:#333}#mermaid-1575353277522 .node circle,#mermaid-1575353277522 .node ellipse,#mermaid-1575353277522 .node polygon,#mermaid-1575353277522 .node rect{fill:#ececff;stroke:#9370db;stroke-width:1px}#mermaid-1575353277522 .node.clickable{cursor:pointer}#mermaid-1575353277522 .arrowheadPath{fill:#333}#mermaid-1575353277522 .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-1575353277522 .edgeLabel{background-color:#e8e8e8}#mermaid-1575353277522 .cluster rect{fill:#ffffde!important;stroke:#aa3!important;stroke-width:1px!important}#mermaid-1575353277522 .cluster text{fill:#333}#mermaid-1575353277522 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:trebuchet ms,verdana,arial;font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-1575353277522 .actor{stroke:#ccf;fill:#ececff}#mermaid-1575353277522 text.actor{fill:#000;stroke:none}#mermaid-1575353277522 .actor-line{stroke:grey}#mermaid-1575353277522 .messageLine0{marker-end:“url(#arrowhead)”}#mermaid-1575353277522 .messageLine0,#mermaid-1575353277522 .messageLine1{stroke-width:1.5;stroke-dasharray:“2 2”;stroke:#333}#mermaid-1575353277522 #arrowhead{fill:#333}#mermaid-1575353277522 #crosshead path{fill:#333!important;stroke:#333!important}#mermaid-1575353277522 .messageText{fill:#333;stroke:none}#mermaid-1575353277522 .labelBox{stroke:#ccf;fill:#ececff}#mermaid-1575353277522 .labelText,#mermaid-1575353277522 .loopText{fill:#000;stroke:none}#mermaid-1575353277522 .loopLine{stroke-width:2;stroke-dasharray:“2 2”;marker-end:“url(#arrowhead)”;stroke:#ccf}#mermaid-1575353277522 .note{stroke:#aa3;fill:#fff5ad}#mermaid-1575353277522 .noteText{fill:#000;stroke:none;font-family:trebuchet ms,verdana,arial;font-size:14px}#mermaid-1575353277522 .section{stroke:none;opacity:.2}#mermaid-1575353277522 .section0{fill:rgba(102,102,255,.49)}#mermaid-1575353277522 .section2{fill:#fff400}#mermaid-1575353277522 .section1,#mermaid-1575353277522 .section3{fill:#fff;opacity:.2}#mermaid-1575353277522 .sectionTitle0,#mermaid-1575353277522 .sectionTitle1,#mermaid-1575353277522 .sectionTitle2,#mermaid-1575353277522 .sectionTitle3{fill:#333}#mermaid-1575353277522 .sectionTitle{text-anchor:start;font-size:11px;text-height:14px}#mermaid-1575353277522 .grid .tick{stroke:#d3d3d3;opacity:.3;shape-rendering:crispEdges}#mermaid-1575353277522 .grid path{stroke-width:0}#mermaid-1575353277522 .today{fill:none;stroke:red;stroke-width:2px}#mermaid-1575353277522 .task{stroke-width:2}#mermaid-1575353277522 .taskText{text-anchor:middle;font-size:11px}#mermaid-1575353277522 .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px}#mermaid-1575353277522 .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-1575353277522 .taskText0,#mermaid-1575353277522 .taskText1,#mermaid-1575353277522 .taskText2,#mermaid-1575353277522 .taskText3{fill:#fff}#mermaid-1575353277522 .task0,#mermaid-1575353277522 .task1,#mermaid-1575353277522 .task2,#mermaid-1575353277522 .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-1575353277522 .taskTextOutside0,#mermaid-1575353277522 .taskTextOutside1,#mermaid-1575353277522 .taskTextOutside2,#mermaid-1575353277522 .taskTextOutside3{fill:#000}#mermaid-1575353277522 .active0,#mermaid-1575353277522 .active1,#mermaid-1575353277522 .active2,#mermaid-1575353277522 .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-1575353277522 .activeText0,#mermaid-1575353277522 .activeText1,#mermaid-1575353277522 .activeText2,#mermaid-1575353277522 .activeText3{fill:#000!important}#mermaid-1575353277522 .done0,#mermaid-1575353277522 .done1,#mermaid-1575353277522 .done2,#mermaid-1575353277522 .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-1575353277522 .doneText0,#mermaid-1575353277522 .doneText1,#mermaid-1575353277522 .doneText2,#mermaid-1575353277522 .doneText3{fill:#000!important}#mermaid-1575353277522 .crit0,#mermaid-1575353277522 .crit1,#mermaid-1575353277522 .crit2,#mermaid-1575353277522 .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-1575353277522 .activeCrit0,#mermaid-1575353277522 .activeCrit1,#mermaid-1575353277522 .activeCrit2,#mermaid-1575353277522 .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-1575353277522 .doneCrit0,#mermaid-1575353277522 .doneCrit1,#mermaid-1575353277522 .doneCrit2,#mermaid-1575353277522 .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-1575353277522 .activeCritText0,#mermaid-1575353277522 .activeCritText1,#mermaid-1575353277522 .activeCritText2,#mermaid-1575353277522 .activeCritText3,#mermaid-1575353277522 .doneCritText0,#mermaid-1575353277522 .doneCritText1,#mermaid-1575353277522 .doneCritText2,#mermaid-1575353277522 .doneCritText3{fill:#000!important}#mermaid-1575353277522 .titleText{text-anchor:middle;font-size:18px;fill:#000}#mermaid-1575353277522 g.classGroup text{fill:#9370db;stroke:none;font-family:trebuchet ms,verdana,arial;font-size:10px}#mermaid-1575353277522 g.classGroup rect{fill:#ececff;stroke:#9370db}#mermaid-1575353277522 g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-1575353277522 .classLabel .box{stroke:none;stroke-width:0;fill:#ececff;opacity:.5}#mermaid-1575353277522 .classLabel .label{fill:#9370db;font-size:10px}#mermaid-1575353277522 .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-1575353277522 #compositionEnd,#mermaid-1575353277522 #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-1575353277522 #aggregationEnd,#mermaid-1575353277522 #aggregationStart{fill:#ececff;stroke:#9370db;stroke-width:1}#mermaid-1575353277522 #dependencyEnd,#mermaid-1575353277522 #dependencyStart,#mermaid-1575353277522 #extensionEnd,#mermaid-1575353277522 #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-1575353277522 .branch-label,#mermaid-1575353277522 .commit-id,#mermaid-1575353277522 .commit-msg{fill:#d3d3d3;color:#d3d3d3}#mermaid-1575353277522 {


color: rgb(58, 65, 69);


font: normal normal 400 normal 18px / 33.3px “Hiragino Sans GB”, “Heiti SC”, “Microsoft YaHei”, sans-serif, Merriweather, serif;


}


event


event


owns


owns


event


owns


item


item


owns


owns


owns


PodMonitor


WorkQueue


ReplicaSetMonitor


GraphBuilder


ProcessGraphChanges


AttemptsToDelete


AttemptsToOrphan


GarbageCollector


DeleteWorker


OrphanWorker


接下来我们会从几个关键点介绍垃圾收集器是如何删除 Kubernetes 集群中的对象以及它们的依赖的。


删除策略

多个资源的 Informer 共同构成了垃圾收集器中的 Propagator,它监听所有的资源更新事件并将它们投入到工作队列中,这些事件会更新内存中的 DAG,这个 DAG 表示了集群中不同对象之间的从属关系,垃圾收集器的多个 Worker 会从两个队列中获取待处理的对象并调用 attemptToDeleteItemattempteToOrphanItem 方法,这里我们主要介绍 attemptToDeleteItem 的实现:


Go


func (gc *GarbageCollector) attemptToDeleteItem(item *node) error {  latest, _ := gc.getObject(item.identity)  ownerReferences := latest.GetOwnerReferences()
solid, dangling, waitingForDependentsDeletion, _ := gc.classifyReferences(item, ownerReferences)
复制代码


该方法会先获取待处理的对象以及所有者的引用列表,随后使用 classifyReferences 方法将引用进行分类并按照不同的条件分别进行处理:


Go


switch {  case len(solid) != 0:    ownerUIDs := append(ownerRefsToUIDs(dangling), ownerRefsToUIDs(waitingForDependentsDeletion)...)    patch := deleteOwnerRefStrategicMergePatch(item.identity.UID, ownerUIDs...)    gc.patch(item, patch, func(n *node) ([]byte, error) {      return gc.deleteOwnerRefJSONMergePatch(n, ownerUIDs...)    })    return err
复制代码


如果当前对象的所有者还有存在于集群中的,那么当前的对象就不会被删除,上述代码会将已经被删除或等待删除的引用从对象中删掉。


当正在被删除的所有者不存在任何的依赖并且该对象的 ownerReference.blockOwnerDeletion 属性为 true 时会阻止依赖方的删除,所以当前的对象会等待属性 ownerReference.blockOwnerDeletion=true 的所有对象的删除后才会被删除。


Go


// ...  case len(waitingForDependentsDeletion) != 0 && item.dependentsLength() != 0:    deps := item.getDependents()    for _, dep := range deps {      if dep.isDeletingDependents() {        patch, _ := item.unblockOwnerReferencesStrategicMergePatch()        gc.patch(item, patch, gc.unblockOwnerReferencesJSONMergePatch)                break      }    }    policy := metav1.DeletePropagationForeground    return gc.deleteObject(item.identity, &policy)  // ...
复制代码


在默认情况下,也就是当前对象已经不包含任何依赖,那么如果当前对象可能会选择三种不同的策略处理依赖:


Go


// ...  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)  }}
复制代码


  1. 如果当前对象有 FinalizerOrphanDependents 终结器,DeletePropagationOrphan 策略会让对象所有的依赖变成孤立的;

  2. 如果当前对象有 FinalizerDeleteDependents 终结器,DeletePropagationBackground 策略在前台等待所有依赖被删除后才会删除,整个删除过程都是同步的;

  3. 默认情况下会使用 DeletePropagationDefault 策略在后台删除当前对象的全部依赖;


终结器

对象的终结器是在对象删除之前需要执行的逻辑,所有的对象在删除之前,它的终结器字段必须为空,终结器提供了一个通用的 API,它的功能不只是用于阻止级联删除,还能过通过它在对象删除之前加入钩子:


Go


type ObjectMeta struct {  // ...  Finalizers []string}
复制代码


终结器在对象被删之前运行,每当终结器成功运行之后,就会将它自己从 Finalizers 数组中删除,当最后一个终结器被删除之后,API Server 就会删除该对象。


在默认情况下,删除一个对象会删除它的全部依赖,但是我们在一些特定情况下我们只是想删除当前对象本身并不想造成复杂的级联删除,垃圾回收机制在这时引入了 OrphanFinalizer,它会在对象被删除之前向 Finalizers 数组添加或者删除 OrphanFinalizer


该终结器会监听对象的更新事件并将它自己从它全部依赖对象的 OwnerReferences 数组中删除,与此同时会删除所有依赖对象中已经失效的 OwnerReferences 并将 OrphanFinalizerFinalizers 数组中删除。


通过 OrphanFinalizer 我们能够在删除一个 Kubernetes 对象时保留它的全部依赖,为使用者提供一种更灵活的办法来保留和删除对象。


总结

Kubernetes 中垃圾收集器的实现还是比较容易理解的,它的主要作用就是监听集群中对象的变更事件并根据两个字段 OwnerReferencesFinalizers 确定对象的删除策略,其中包括同步和后台的选择、是否应该触发级联删除移除当前对象的全部依赖;在默认情况下,当我们删除 Kubernetes 集群中的 ReplicaSet、Deployment 对象时都会删除这些对象的全部依赖,不过我们也可以通过 OrphanFinalizer 终结器删除单独的对象。


相关文章


Reference


2019 年 12 月 04 日 08:00225

评论

发布
暂无评论
发现更多内容

能助我拿3家大厂offer的神级Java面试宝典,你值得拥有

Java架构之路

Java 程序员 架构 面试 编程语言

时间管理的三个版本

三界

时间管理 职场经验

覆盖全产业!海尔智家一口气发7个标准,衣食住娱全包了

DT极客

14|颜色搭配原则

青城

深圳正探索利用区块链技术理念打造“数字政府“

CECBC区块链专委会

大数据

Go sync.Map 源码解读

werben

go golang

人工智能会不会最先在智慧家庭领域落地?

DT极客

区块链技术在医疗保健领域的应用展望

CECBC区块链专委会

医疗

为什么海尔智慧家庭能引领行业?软件硬件都没有短板!

DT极客

本科毕业,六年Java开发经验,阿里技术三面+HR面,拿下38*16薪资P7offer

Java架构之路

Java 程序员 架构 面试 编程语言

如何革命社交媒体、实现去中心化?丝绸之路创始人在狱中提出了构想

CECBC区块链专委会

社交网络

ProxmoxVE系列:Ubuntu服务器版系统安装

Bob

虚拟机 系统 proxmoxve PVE

C++ socket通讯详解及注意事项

赖猫

c++ 后台开发 后端 服务器开发

一篇文章带你熟知:软件公司的分类及人员构成

程序员一凡

互联网 面试 职业规划 软件测试 测试工程师

如何使用docker-compose快速部署SpringCloud项目

皮特王

Docker nacos Docker-compose spring-cloud

知乎、B站为何成「中国社区」概念股?

吴俊宇

知乎

寻找被遗忘的勇气(二十四)

Changing Lin

3月日更

ProxmoxVE 系列:如何巧妙的用Xshell连接Ubuntu server服务主机

Bob

虚拟机 系统 proxmoxve PVE

技术中台在企业数字化转型中的践行

EAWorld

ETHAT云矿机系统开发案例丨ETHAT云矿机开发源码

系统开发咨询1357O98O718

TouChain系统开发案例介绍

系统开发咨询1357O98O718

大厂喜欢什么样的软件测试人才?

程序员一凡

程序员 互联网 软件测试 测试开发 测试工程师

3种加强身份和访问管理的方法

龙归科技

解决方案 去中心化 零信任

多线程-基础

胖啊

C++ 中的 task based 并发

赖猫

c++ 后端 多线程 并发 服务器开发

接口测试--自定义断言设置

测试人生路

接口测试

阿里云盘上线了,2T空间免费领

和牛

软件推荐

c++11&14-智能指针

赖猫

c++ 后端

同样做软件测试,和月收入3W的学弟聊了一晚上,我崩溃了

程序员一凡

程序员 软件测试 测试开发 测试工程师

ProxmoxVE系列:上传系统镜像&&创建虚拟机

Bob

虚拟机 proxmoxve PVE

员工离职的注意事项

石云升

离职 28天写作 职场经验 3月日更

2021年全国大学生计算机系统能力大赛操作系统设计赛 技术报告会

2021年全国大学生计算机系统能力大赛操作系统设计赛 技术报告会

详解 Kubernetes 垃圾收集器的实现原理-InfoQ