Kubernetes Controller 如何管理資源?

一、Introduction
起因:工作上要重構(gòu)一個(gè)現(xiàn)有的組件管理工具,要求實(shí)現(xiàn)全生命周期管理,還有上下游解耦,我心里一想這不就是 k8s controller 嘛!所以決定在動(dòng)手前先學(xué)習(xí)一下 k8s 的先進(jìn)理念。
此文就是通過對(duì)代碼的簡單分析,以及一些經(jīng)驗(yàn)總結(jié),來描述 k8s controller 管理資源的主要流程。
二、Concepts
resource: 資源,k8s 中定義的每一個(gè)實(shí)例都是一個(gè)資源,比如一個(gè) rs、一個(gè) deployment。資源有不同的 kind,比如 rs、deployment。資源間存在上下游關(guān)系。
注意:下文中提到的所有“資源”,都是指 k8s 中抽象的資源聲明,而不是指 CPU、存儲(chǔ)等真實(shí)的物理資源。
高度抽象 k8s 的話其實(shí)就三大件:
apiserver: 負(fù)責(zé)存儲(chǔ)資源,并且提供查詢、修改資源的接口 controller: 負(fù)責(zé)管理本級(jí)和下級(jí)資源。比如 deploymentController 就負(fù)責(zé)管理 deployment 資源 和下一級(jí)的 rs 資源。 kubelet: 安裝在 node 節(jié)點(diǎn)上,負(fù)責(zé)部署資源
controller 和 kubelet 都只和 apiserver 通訊。controller 不斷監(jiān)聽本級(jí)資源,然后修改下級(jí)資源的聲明。kubelet 查詢當(dāng)前 node 所應(yīng)部署的資源,然后執(zhí)行清理和部署。

1、術(shù)語
metadata: 每一個(gè)資源都有的元數(shù)據(jù),包括 label、owner、uid 等UID: 每一個(gè)被創(chuàng)建(提交給 apiserver)的資源都有一個(gè)全局唯一的 UUID。label: 每個(gè)資源都要定義的標(biāo)簽selector: 父資源通過 labelSelector 查詢歸其管理的子資源。不允許指定空 selector(全匹配)。owner: 子資源維護(hù)一個(gè) owner UID 的列表 OwnerReferences, 指向其父級(jí)資源。列表中第一個(gè)有效的指針會(huì)被視為生效的父資源。selector 實(shí)際上只是一個(gè) adoption 的機(jī)制, 真實(shí)起作用的父子級(jí)關(guān)系是靠 owner 來維持的, 而且 owner 優(yōu)先級(jí)高于 selector。replicas: 副本數(shù),pod 數(shù) 父/子資源的相關(guān): orphan: 沒有 owner 的資源(需要被 adopt 或 GC) adopt: 將 orphan 納入某個(gè)資源的管理(成為其 owner) match: 父子資源的 label/selector 匹配 release: 子資源的 label 不再匹配父資源的 selector,將其釋放 RS 相關(guān): saturated: 飽和,意指某個(gè)資源的 replicas 已符合要求 surge: rs 的 replicas 不能超過 spec.replicas + surge proportion: 每輪 rolling 時(shí),rs 的變化量(小于 maxSurge) fraction: scale 時(shí) rs 期望的變化量(可能大于 maxSurge)
三、Controller
sample-controller@a40ea2c/controller.go kubernetes@59c0523b/pkg/controller/deployment/deployment_controller.go kubernetes@59c0523b/pkg/controller/controller_ref_manager.go
控制器,負(fù)責(zé)管理自己所對(duì)應(yīng)的資源(resource),并創(chuàng)建下一級(jí)資源,拿 deployment 來說:
用戶創(chuàng)建 deployment 資源 deploymentController 監(jiān)聽到 deployment 資源,然后創(chuàng)建 rs 資源 rsController 監(jiān)聽到 rs 資源,然后創(chuàng)建 pod 資源 調(diào)度器(scheduler)監(jiān)聽到 pod 資源,將其與 node 資源建立關(guān)聯(lián)
(node 資源是 kubelet 安裝后上報(bào)注冊(cè)的)

理想中,每一層管理器只管理本級(jí)和子兩層資源。但因?yàn)槊恳粋€(gè)資源都是被上層創(chuàng)建的, 所以實(shí)際上每一層資源都對(duì)下層資源的定義有完全的了解,即有一個(gè)由下至上的強(qiáng)耦合關(guān)系。
比如 A -> B -> C -> D 這樣的生成鏈,A 當(dāng)然是知道 D 資源的全部定義的, 所以從理論上說,A 是可以去獲取 D 的。但是需要注意的是,如果出現(xiàn)了跨級(jí)的操作,A 也只能只讀的獲取 D,而不要對(duì) D 有任何改動(dòng), 因?yàn)榭缂?jí)修改數(shù)據(jù)的話會(huì)干擾下游的控制器。
k8s 中所有的控制器都在同一個(gè)進(jìn)程(controller-manager)中啟動(dòng), 然后以 goroutine 的形式啟動(dòng)各個(gè)不同的 controller。所有的 contorller 共享同一個(gè) informer,不過可以注冊(cè)不同的 filter 和 handler, 監(jiān)聽自己負(fù)責(zé)的資源的事件。
(informer 是對(duì) apiserver 的封裝,是 controller 查詢、訂閱資源消息的組件,后文有介紹)
注:如果是用戶自定義 controller(CRD)的話,需要以單獨(dú)進(jìn)程的形式啟動(dòng),需要自己另行實(shí)例化一套 informer, 不過相關(guān)代碼在 client-go 這一項(xiàng)目中都做了封裝,編寫起來并不會(huì)很復(fù)雜。
控制器的核心代碼可以概括為:
for?{
????for?{
????????//?從?informer?中取出訂閱的資源消息
????????key,?empty?:=?queue.Get()
????????if?empty?{
????????????break
????????}
????????defer?queue.Done(key)
????????//?處理這一消息:更新子資源的聲明,使其匹配父資源的要求。
????????//?所有的 controller 中,這一函數(shù)都被命名為?`syncHandler`。
????????syncHandler(key)
????}
????//?消息隊(duì)列被消費(fèi)殆盡,等待下一輪運(yùn)行
????time.sleep(time.Second)
}
通過 informer(indexer)監(jiān)聽資源事件,事件的格式是字符串 / 控制器通過 namespace 和 name 去查詢自己負(fù)責(zé)的資源和下級(jí)資源 比對(duì)當(dāng)前資源聲明的狀態(tài)和下級(jí)資源可用的狀態(tài)是否匹配,并通過增刪改讓下級(jí)資源匹配上級(jí)聲明。比如 deployments 控制器就查詢 deployment 資源和 rs 資源,并檢驗(yàn)其中的 replicas 副本數(shù)是否匹配。
controller 內(nèi)包含幾個(gè)核心屬性/方法:
informer: sharedIndexer,用于獲取資源的消息,支持注冊(cè) Add/Update/Delete 事件觸發(fā),或者調(diào)用 lister遍歷。clientset: apiserver 的客戶端,用來對(duì)資源進(jìn)行增刪改。 syncHandler: 執(zhí)行核心邏輯代碼(更新子資源的聲明,使其匹配父資源的要求)。
1、syncHandler
syncHandler 像是一個(gè)約定,所有的 controller 內(nèi)執(zhí)行核心邏輯的函數(shù)都叫這個(gè)名字。該函數(shù)負(fù)責(zé)處理收到的資源消息,比如更新子資源的聲明,使其匹配父資源的要求。
以 deploymentController 為例,當(dāng)收到一個(gè)事件消息,syncHandler 被調(diào)用后:
注:
de: 觸發(fā)事件的某個(gè) deployment 資源dc: deploymentController 控制器自己rs: replicaset,deployment 對(duì)應(yīng)的 replicaset 子資源
注:事件是一個(gè)字符串,形如 namespace/name,代表觸發(fā)事件的資源的名稱以及所在的 namespace。因?yàn)槭录皇莻€(gè)名字,所以 syncHandler 需要自己去把當(dāng)前觸發(fā)的資源及其子資源查詢出來。這里面涉及很多查詢和遍歷,不過這些查詢都不會(huì)真實(shí)的調(diào)用 apiserver,而是在 informer 的內(nèi)存存儲(chǔ)里完成的。
graph TD
A1[將 key 解析為 namespace 和 name] --> A2[查詢 de]
A2 --> A3[查詢關(guān)聯(lián)子資源 rs]
A3 --> A31{de 是否 paused}
A31 --> |yes| A32[調(diào)用 dc.sync 部署 rs]
A31 --> |no| A4{是否設(shè)置了 rollback}
A4 --> |yes| A41[按照 annotation 設(shè)置執(zhí)行 rollback]
A4 --> |no| A5[rs 是否匹配 de 聲明]
A5 --> |no| A32
A5 --> |yes| A6{de.spec.strategy.type}
A6 --> |recreate| A61[dc.rolloutRecreate]
A6 --> |rolling| A62[dc.rolloutRolling]

查詢關(guān)聯(lián)子資源
kubernetes@59c0523b/pkg/controller/deployment/deployment_controller.go:getReplicaSetsForDeployment
k8s 中,資源間可以有上下級(jí)(父子)關(guān)系。
理論上 每一個(gè) controller 都負(fù)責(zé)創(chuàng)建當(dāng)前資源和子資源,父資源通過 labelSelector 查詢應(yīng)該匹配的子資源。
一個(gè) deployment 的定義:
apiVersion:?apps/v1
kind:?Deployment
metadata:
??name:?nginx-deployment
??labels:
????app:?nginx
spec:
??replicas:?3
??selector:
????matchLabels:
??????app:?nginx
??template:
????metadata:
??????labels:
????????app:?nginx
????spec:
??????containers:
??????-?name:?nginx
????????image:?nginx:1.14.2
????????ports:
????????-?containerPort:?80
上文中講到 syncHandler 的時(shí)候,提到需要“查詢關(guān)聯(lián)子資源”。其實(shí)這一步驟很復(fù)雜,不僅僅是查詢,還包含對(duì)現(xiàn)有子資源進(jìn)行同步(修改)的操作。簡而言之,這一步驟實(shí)際上做的是通過對(duì) owner、label 的比對(duì),確認(rèn)并更新當(dāng)前真實(shí)的父子資源關(guān)系。
對(duì)用戶呈現(xiàn)的資源關(guān)聯(lián)是依靠 label/selector。但實(shí)際上 k8s 內(nèi)部使用的是 owner 指針。(owner 指針是資源 metadata 內(nèi)用來標(biāo)記其父資源的 OwnerReferences)。
查詢匹配子資源的方法是:
遍歷 namespace 內(nèi)所有對(duì)應(yīng)類型的子資源 (比如 deployment controller 就會(huì)遍歷所有的 rs) 匹配校驗(yàn) owner 和 label
(父是當(dāng)前操作的資源,子是查詢出的子資源)
還是用 deployment 舉例,比如此時(shí)收到了一個(gè) deployment 事件,需要查詢出該 de 匹配的所有 rs:
graph LR
A(遍歷 namespace 內(nèi)所有 rs) --> A1{子.owner == nil}
A1 --> |false| A2{子.owner == 父.uid}
A2 --> |false| A21[skip]
A2 --> |true| A3{labels matched}
A3 --> |true| A5
A3 --> |false| A31[release]
A1 --> |true| A4{labels matched}
A4 --> |false| A21
A4 --> |true| A41[adopt]
A41 --> A5[標(biāo)記為父子]

如上圖所示,其實(shí)只有兩個(gè) case 下,rs 會(huì)被視為是 de 的子資源:
rs owner 指向 de,且 labels 匹配 rs owner 為空,且 labels 匹配
注意:如果 rs owner 指向了其他父資源,即使 label 匹配,也不會(huì)被視為當(dāng)前 de 的子資源。
dc.sync
kubernetes@59c0523b/pkg/controller/deployment/sync.go:sync
這是 deployment controller 中執(zhí)行“檢查和子資源,令其匹配父資源聲明”的一步。準(zhǔn)確的說:
dc.sync: 檢查子資源是否符合父資源聲明 dc.scale: 操作更新子資源,使其符合父資源聲明
graph TD
A1[查詢 de 下所有舊的 rs] --> A2{當(dāng)前 rs 是否符合 de}
A2 --> |no| A21[newRS = nil]
A2 --> |yes| A22[NewRS = 當(dāng)前 rs]
A22 --> A23[將 de 的 metadata 拷貝給 newRS]
A23 --> A231[newRS.revision=maxOldRevision+1]
A231 --> A3[調(diào)用 dc.scale]
A21 --> A33
A3 --> A31{是否有 active/latest rs}
A31 --> |yes| A311[dc.scaleReplicaSet 擴(kuò)縮容]
A31 --> |no| A32{newRS 是否已飽和}
A32 --> |yes|A321[把所有 oldRS 清零]
A32 --> |no|A33{de 是否允許 rolling}
A33 --> |no|A331[return]
A33 --> |yes|A34[執(zhí)行滾動(dòng)更新]

滾動(dòng)更新的流程為:
(if deploymentutil.IsRollingUpdate(deployment) {...} 內(nèi)的大量代碼,實(shí)際做的事情就是按照 deployment 的要求更新 rs 的 replicas 數(shù)。不過每次變更都涉及到對(duì) rs 和 deployment 的 maxSurge 的檢查,所以代碼較為復(fù)雜。)
計(jì)算所有 RS replicas 總和 allRSsReplicas。計(jì)算滾動(dòng)升級(jí)過程中最多允許出現(xiàn)的副本數(shù) allowedSize。allowedSize = de.Spec.Replicas + maxSurgedeploymentReplicasToAdd = allowedSize - allRSsReplicas遍歷所有當(dāng)前 rs,計(jì)算每一個(gè) rs 的 replicas 變化量(proportion), 計(jì)算的過程中需要做多次檢查,不能溢出 rs 和 deployment 的 maxSurge。 更新所有 rs 的 replicas,然后調(diào)用 dc.scaleReplicaSet提交更改。
四、Object
[email protected]/pkg/apis/meta/v1/meta.go [email protected]/pkg/apis/meta/v1/types.go
ObjectMeta 定義了 k8s 中資源對(duì)象的標(biāo)準(zhǔn)方法。
雖然 resource 定義里是通過 labelSelector 建立從上到下的關(guān)聯(lián), 但其實(shí)內(nèi)部實(shí)現(xiàn)的引用鏈?zhǔn)菑南碌缴系摹C恳粋€(gè)資源都會(huì)保存一個(gè) Owner UID 的 slice。
每個(gè)資源的 metadata 中都有一個(gè) ownerReferences 列表,保存了其父資源(遍歷時(shí)遇到的第一個(gè)有效的資源會(huì)被認(rèn)為是其父資源)。
type?ObjectMeta?struct?{
????OwnerReferences?[]OwnerReference?`json:"ownerReferences,omitempty"?patchStrategy:"merge"?patchMergeKey:"uid"?protobuf:"bytes,13,rep,name=ownerReferences"`
}
判斷 owner 靠的是比對(duì)資源的 UID
func?IsControlledBy(obj?Object,?owner?Object)?bool?{
?ref?:=?GetControllerOfNoCopy(obj)
?if?ref?==?nil?{
??return?false
?}
????//?猜測:UID 是任何資源在 apiserver 注冊(cè)的時(shí)候,由 k8s 生成的 uuid
?return?ref.UID?==?owner.GetUID()
}
五、Informer
A deep dive into Kubernetes controllers[1] [email protected]/informers/factory.go
Informer 也經(jīng)歷了兩代演進(jìn),從最早各管各的 Informer,到后來統(tǒng)一監(jiān)聽,各自 filter 的 sharedInformer。
所有的 controller 都在一個(gè) controller-manager 進(jìn)程內(nèi),所以完全可以共享同一個(gè) informer, 不同的 controller 注冊(cè)不同的 filter(kind、labelSelector),來訂閱自己需要的消息。
簡而言之,現(xiàn)在的 sharedIndexer,就是一個(gè)統(tǒng)一的消息訂閱器,而且內(nèi)部還維護(hù)了一個(gè)資源存儲(chǔ),對(duì)外提供可過濾的消息分發(fā)和資源查詢功能。
sharedIndexer 和 sharedInformer 的區(qū)別就是多了個(gè) threadsafe 的 map 存儲(chǔ),用來存 shared resource object。
現(xiàn)在的 informer 中由幾個(gè)主要的組件構(gòu)成:
reflecter:查詢器,負(fù)責(zé)從 apiserver 定期輪詢資源,更新 informer 的 store。 store: informer 內(nèi)部對(duì)資源的存儲(chǔ),用來提供 lister 遍歷等查詢操作。 queue:支持 controller 的事件訂閱。

各個(gè) controller 的訂閱和查詢絕大部分都在 sharedIndexer 的內(nèi)存內(nèi)完成,提高資源利用率和效率。
一般 controller 的消息來源就通過兩種方式:
lister: controller 注冊(cè)監(jiān)聽特定類型的資源事件,事件格式是字符串, / handler: controller 通過 informer 的 AddEventHandler方法注冊(cè)Add/Update/Delete事件的處理函數(shù)。
這里有個(gè)值得注意的地方是,資源事件的格式是字符串,形如 ,這其中沒有包含版本信息。
那么某個(gè)版本的 controller 拿到這個(gè)信息后,并不知道查詢出來的資源是否匹配自己的版本,也許會(huì)查出一個(gè)很舊版本的資源。
所以 controller 對(duì)于資源必須是向后兼容的,新版本的 controller 必須要能夠處理舊版資源。這樣的話,只需要保證運(yùn)行的是最新版的 controller 就行了。

1、Queue
controller 內(nèi)有大量的隊(duì)列,最重要的就是注冊(cè)到 informer 的三個(gè) add/update/delete 隊(duì)列。
RateLimitingQueue
[email protected]/util/workqueue/rate_limiting_queue.go
實(shí)際使用的是隊(duì)列類型是 RateLimitingQueue,繼承于 Queue。
Queue
[email protected]/util/workqueue/queue.go
type?Interface?interface?{
????//?Add?增加任務(wù),可能是增加新任務(wù),可能是處理失敗了重新放入
????//
????//?調(diào)用 Add 時(shí),t 直接插入 dirty。然后會(huì)判斷一下 processing,
????//???是否存在于?processing???返回?:?放入?queue
????Add(item?interface{})
????Len()?int
????Get()?(item?interface{},?shutdown?bool)
????Done(item?interface{})
????ShutDown()
????ShuttingDown()?bool
}
type?Type?struct?{
????//?queue?所有未被處理的任務(wù)
????queue?[]t
????//?dirty?所有待處理的任務(wù)
????//
????//?從定義上看和 queue 有點(diǎn)相似,可以理解為 queue 的緩沖區(qū)。
????//?比如調(diào)用?Add?時(shí),如果?t?存在于?processing,就只會(huì)插入?dirty,不會(huì)插入?queue,
????//?這種情況表明外部程序處理失敗了,所以再次插入了 t。
????dirty?set
????//?processing?正在被處理的任務(wù)
????//
????//?一個(gè)正在被處理的 t 應(yīng)該從 queue 移除,然后添加到 processing。
????//
????//?如果 t 處理失敗需要重新處理,那么這個(gè) t 會(huì)被再次放入 dirty。
????//?所以調(diào)用?Done?從?processing?移除?t?的時(shí)候需要同步檢查一下?dirty,
????//?如果 t 存在于 dirty,則將其再次放入 queue。
????processing?set
????cond?*sync.Cond
????shuttingDown?bool
????metrics?queueMetrics
????unfinishedWorkUpdatePeriod?time.Duration
????clock??????????????????????clock.Clock
}
隊(duì)列傳遞的資源事件是以字符串來表示的,格式形如 namespace/name。
正因?yàn)橘Y源是字符串來表示,這導(dǎo)致了很多問題。其中對(duì)于隊(duì)列的一個(gè)問題就是:沒法為事件設(shè)置狀態(tài),標(biāo)記其是否已完成。為了實(shí)現(xiàn)這個(gè)狀態(tài),queue 中通過 queue、dirty、processing 三個(gè)集合來表示。具體實(shí)現(xiàn)可以參考上面的注釋和代碼。
另一個(gè)問題就是資源中沒有包含版本信息。
那么某個(gè)版本的 controller 拿到這個(gè)信息后,并不知道查詢出來的資源是否匹配自己的版本,也許會(huì)查出一個(gè)很舊版本的資源。
所以 controller 對(duì)于資源必須是向后兼容的,新版本的 controller 必須要能夠處理舊版資源。這樣的話,只需要保證運(yùn)行的是最新版的 controller 就行了。
六、GC
Garbage Collection[2] Using Finalizers to Control Deletion[3] kubernetes@59c0523b/pkg/controller/garbagecollector/garbagecollector.go
1、Concepts
我看到 GC 的第一印象是一個(gè)像語言 runtime 里的回收資源的自動(dòng)垃圾收集器。但其實(shí) k8s 里的 GC 的工作相對(duì)比較簡單,更像是只是一個(gè)被動(dòng)的函數(shù)調(diào)用,當(dāng)用戶試圖刪除一個(gè)資源的時(shí)候, 就會(huì)把這個(gè)資源提交給 GC,然后 GC 執(zhí)行一系列既定的刪除流程,一般來說包括:
刪除子資源 執(zhí)行刪除前清理工作(finalizer) 刪除資源
k8s 的資源間存在上下游依賴,當(dāng)你刪除一個(gè)上游資源時(shí),其下游資源也需要被刪除,這被稱為級(jí)聯(lián)刪除 cascading deletion。
刪除一個(gè)資源有三種策略(propagationPolicy/DeletionPropagation):
Foreground(default): Children are deleted before the parent (post-order)Background: Parent is deleted before the children (pre-order)Orphan: 忽略 owner references
可以在運(yùn)行 kubectl delete --cascade=??? 的時(shí)候指定刪除的策略,默認(rèn)為 foreground。
2、Deletion
k8s 中,資源的 metadata 中有幾個(gè)對(duì)刪除比較重要的屬性:
ownerRerences: 指向父資源的 UIDdeletionTimestamp: 如果不為空,表明該資源正在被刪除中finalizers: 一個(gè)字符串?dāng)?shù)組,列舉刪除前必須執(zhí)行的操作blockOwnerDeletion: 布爾,當(dāng)前資源是否會(huì)阻塞父資源的刪除流程
每一個(gè)資源都有 metadata.finalizers,這是一個(gè) []string, 內(nèi)含一些預(yù)定義的字符串,表明了在刪除資源前必須要做的操作。每執(zhí)行完一個(gè)操作,就從 finalizers 中移除這個(gè)字符串。
無論是什么刪除策略,都需要先把所有的 finalizer 逐一執(zhí)行完,每完成一個(gè),就從 finalizers 中移除一個(gè)。在 finalizers 為空后,才能正式的刪除資源。
foreground、orphan 刪除就是通過 finalizer 來實(shí)現(xiàn)的。
const?(
????FinalizerOrphanDependents?=?"orphan"
????FinalizerDeleteDependents?=?"foregroundDeletion"
)

注:有一種讓資源永不刪除的黑魔法,就是為資源注入一個(gè)不存在的 finalizer。因?yàn)?GC 無法找到該 finalizer 匹配的函數(shù)來執(zhí)行,就導(dǎo)致這個(gè) finalizer 始終無法被移除, 而 finalizers 為空清空的資源是不允許被刪除的。
3、Foreground cascading deletion
設(shè)置資源的 deletionTimestamp,表明該資源的狀態(tài)為正在刪除中("deletion in progress")。設(shè)置資源的 metadata.finalizers為"foregroundDeletion"。刪除所有 ownerReference.blockOwnerDeletion=true的子資源刪除當(dāng)前資源
每一個(gè)子資源的 owner 列表的元素里,都有一個(gè)屬性 ownerReference.blockOwnerDeletion,這是一個(gè) bool, 表明當(dāng)前資源是否會(huì)阻塞父資源的刪除流程。刪除父資源前,應(yīng)該把所有標(biāo)記為阻塞的子資源都刪光。
在當(dāng)前資源被刪除以前,該資源都通過 apiserver 持續(xù)可見。
4、Orphan deletion
觸發(fā) FinalizerOrphanDependents,將所有子資源的 owner 清空,也就是令其成為 orphan。然后再刪除當(dāng)前資源。
5、Background cascading deletion
立刻刪除當(dāng)前資源,然后在后臺(tái)任務(wù)中刪除子資源。
graph LR
A1{是否有 finalizers} --> |Yes: pop, execute| A1
A1 --> |No| A2[刪除自己]
A2 --> A3{父資源是否在等待刪除}
A3 --> |No| A4[刪除所有子資源]
A3 --> |Yes| A31[在刪除隊(duì)列里提交父資源]
A31 --> A4

foreground 和 orphan 刪除策略是通過 finalizer 實(shí)現(xiàn)的 因?yàn)檫@兩個(gè)策略有一些刪除前必須要做的事情:
foreground finalizer: 將所有的子資源放入刪除事件隊(duì)列 orphan finalizer: 將所有的子資源的 owner 設(shè)為空
而 background 則就是走標(biāo)準(zhǔn)刪除流程:刪自己 -> 刪依賴。
這個(gè)流程里有一些很有趣(繞)的設(shè)計(jì)。比如 foreground 刪除,finalizer 里把所有的子資源都放入了刪除隊(duì)列, 然后下一步在刪除當(dāng)前資源的時(shí)候,會(huì)發(fā)現(xiàn)子資源依然存在,導(dǎo)致當(dāng)前資源無法刪除。實(shí)際上真正刪除當(dāng)前資源(父資源),實(shí)在刪除最后一個(gè)子資源的時(shí)候,每次都會(huì)去檢查下父資源的狀態(tài)是否是刪除中, 如果是,就把父資源放入刪除隊(duì)列,此時(shí),父資源才會(huì)被真正刪除。
6、Owner References
每個(gè)資源的 metadata 中都有一個(gè) ownerReferences 列表,保存了其父資源(遍歷時(shí)遇到的第一個(gè)有效的資源會(huì)被認(rèn)為是其父資源)。
owner 決定了資源會(huì)如何被刪除。刪除子資源不會(huì)影響到父資源。刪除父資源會(huì)導(dǎo)致子資源被聯(lián)動(dòng)刪除。(默認(rèn) kubectl delete --cascade=foreground)
七、參考資料
關(guān)于本主題的內(nèi)容,我制作了一個(gè) slides,可用于內(nèi)部分享:https://s3.laisky.com/public/slides/k8s-controller.slides.html#/
1、如何閱讀源碼
核心代碼:https://github.com/kubernetes/kubernetes,所有的 controller 代碼都在 pkg/controller/ 中。
所有的 clientset、informer 都被抽象出來在 https://github.com/kubernetes/client-go 庫中,供各個(gè)組件復(fù)用。
學(xué)習(xí)用示例項(xiàng)目:https://github.com/kubernetes/sample-controller
2、參考文章
Garbage Collection[4] Using Finalizers to Control Deletion[5] A deep dive into Kubernetes controllers[6] kube-controller-manager[7]
引用鏈接
A deep dive into Kubernetes controllers: https://app.yinxiang.com/shard/s17/nl/2006464/674c3d83-f011-49b8-9135-413588c22c0f/
[2]Garbage Collection: https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/
[3]Using Finalizers to Control Deletion: https://kubernetes.io/blog/2021/05/14/using-finalizers-to-control-deletion/
[4]Garbage Collection: https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/
[5]Using Finalizers to Control Deletion: https://kubernetes.io/blog/2021/05/14/using-finalizers-to-control-deletion/
[6]A deep dive into Kubernetes controllers: https://engineering.bitnami.com/articles/a-deep-dive-into-kubernetes-controllers.html
[7]kube-controller-manager: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/
原文鏈接:https://blog.laisky.com/p/kubernetes-controller/


你可能還喜歡
點(diǎn)擊下方圖片即可閱讀

云原生是一種信仰???
關(guān)注公眾號(hào)
后臺(tái)回復(fù)?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!


點(diǎn)擊?"閱讀原文"?獲取更好的閱讀體驗(yàn)!
發(fā)現(xiàn)朋友圈變“安靜”了嗎?


