在前面幾篇關(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 中有三種刪除策略:Orphan、Foreground 和 Background,三種刪除策略的意義分別為:
-
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:OrphanFinalizer 和 ForegroundFinalizer,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.Sync 和 garbagecollector.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.Run,garbageCollector.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.runAttemptToDeleteWorker 和 gc.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.startMonitors 和 gb.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字段刪除,三是將該 nodeOwnerReferences字段中對(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í)需要將dangling和waitingForDependentsDeletion列表中的 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 所有 dependentsOwnerReferences中的 owner 字段; - 2、調(diào)用
gc.removeFinalizer刪除 owner 的orphanFinalizer; - 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.Sync 是 startGarbageCollectorController 中的第三個(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 的syncMonitors和startMonitors兩個(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ò)/apiURL 獲取當(dāng)前版本下所有可用的APIVersions,再通過(guò)/apisURL 獲取 所有可用的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 的 syncMonitors 和 startMonitors 方法在前面的流程中已經(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í)將 rsownerReferences中關(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)