九張圖帶你理解 Kubernetes Controller 工作機(jī)制
一、Introduction
起因:工作上要重構(gòu)一個(gè)現(xiàn)有的組件管理工具,要求實(shí)現(xiàn)全生命周期管理,還有上下游解耦,我心里一想這不就是 k8s controller 嘛!所以決定在動(dòng)手前先學(xué)習(xí)一下 k8s 的先進(jìn)理念。
此文就是通過(guò)對(duì)代碼的簡(jiǎn)單分析,以及一些經(jīng)驗(yàn)總結(jié),來(lái)描述 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ù)語(yǔ)
- ?
?metadata?
?: 每一個(gè)資源都有的元數(shù)據(jù),包括 label、owner、uid 等 - ?
?UID?
?: 每一個(gè)被創(chuàng)建(提交給 apiserver)的資源都有一個(gè)全局唯一的 UUID。 - ?
?label?
?: 每個(gè)資源都要定義的標(biāo)簽 - ?
?selector?
?: 父資源通過(guò) labelSelector 查詢歸其管理的子資源。不允許指定空 selector(全匹配)。 - owner: 子資源維護(hù)一個(gè) owner UID 的列表?
?OwnerReferences?
?, 指向其父級(jí)資源。列表中第一個(gè)有效的指針會(huì)被視為生效的父資源。selector 實(shí)際上只是一個(gè) adoption 的機(jī)制, 真實(shí)起作用的父子級(jí)關(guān)系是靠 owner 來(lái)維持的, 而且 owner 優(yōu)先級(jí)高于 selector。 - replicas: 副本數(shù),pod 數(shù)
- 父/子資源的相關(guān):
- orphan: 沒(méi)有 owner 的資源(需要被 adopt 或 GC)
- adopt: 將 orphan 納入某個(gè)資源的管理(成為其 owner)
- match: 父子資源的 label/selector 匹配
- release: 子資源的 label 不再匹配父資源的 selector,將其釋放
- RS 相關(guān):
- saturated: 飽和,意指某個(gè)資源的 replicas 已符合要求
- surge: rs 的 replicas 不能超過(guò) 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 來(lái)說(shuō):
- 用戶創(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 資源的全部定義的, 所以從理論上說(shuō),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,不過(guò)可以注冊(cè)不同的 filter 和 handler,監(jiān)聽自己負(fù)責(zé)的資源的事件。
(informer 是對(duì) apiserver 的封裝,是 controller 查詢、訂閱資源消息的組件,后文有介紹)注:如果是用戶自定義 controller(CRD)的話,需要以單獨(dú)進(jìn)程的形式啟動(dòng),需要自己另行實(shí)例化一套 informer, 不過(guò)相關(guān)代碼在 client-go 這一項(xiàng)目中都做了封裝,編寫起來(lái)并不會(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)
}
- 通過(guò) informer(indexer)監(jiān)聽資源事件,事件的格式是字符串?
?<namespace>/<name>?
? - 控制器通過(guò) namespace 和 name 去查詢自己負(fù)責(zé)的資源和下級(jí)資源
- 比對(duì)當(dāng)前資源聲明的狀態(tài)和下級(jí)資源可用的狀態(tài)是否匹配,并通過(guò)增刪改讓下級(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 的客戶端,用來(lái)對(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ā)的資源及其子資源查詢出來(lái)。這里面涉及很多查詢和遍歷,不過(guò)這些查詢都不會(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)前資源和子資源,父資源通過(guò) 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)行同步(修改)的操作。簡(jiǎn)而言之,這一步驟實(shí)際上做的是通過(guò)對(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)用來(lá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)確的說(shuō):
- 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ù)。不過(guò)每次變更都涉及到對(duì) rs 和 deployment 的 maxSurge 的檢查,所以代碼較為復(fù)雜。)
- 計(jì)算所有 RS replicas 總和?
?allRSsReplicas?
?。 - 計(jì)算滾動(dòng)升級(jí)過(guò)程中最多允許出現(xiàn)的副本數(shù)?
?allowedSize?
??。??allowedSize = de.Spec.Replicas + maxSurge?
? - ?
?deploymentReplicasToAdd = allowedSize - allRSsReplicas?
? - 遍歷所有當(dāng)前 rs,計(jì)算每一個(gè) rs 的 replicas 變化量(proportion), 計(jì)算的過(guò)程中需要做多次檢查,不能溢出 rs 和 deployment 的 maxSurge。
- 更新所有 rs 的 replicas,然后調(diào)用?
?dc.scaleReplicaSet?
? 提交更改。
四、Object
- apimachinery@v0.0.0-20210708014216-0dafcb48b31e/pkg/apis/meta/v1/meta.go
- apimachinery@v0.0.0-20210708014216-0dafcb48b31e/pkg/apis/meta/v1/types.go
ObjectMeta 定義了 k8s 中資源對(duì)象的標(biāo)準(zhǔn)方法。
雖然 resource 定義里是通過(guò) labelSelector 建立從上到下的關(guān)聯(lián), 但其實(shí)內(nèi)部實(shí)現(xiàn)的引用鏈?zhǔn)菑南碌缴系?。每一個(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
}
// 猜測(cè):UID 是任何資源在 apiserver 注冊(cè)的時(shí)候,由 k8s 生成的 uuid
return ref.UID == owner.GetUID()
}
五、Informer
- A deep dive into Kubernetes controllers[1]
- client-go@v0.0.0-20210708094636-69e00b04ba4c/informers/factory.go
Informer 也經(jīng)歷了兩代演進(jìn),從最早各管各的 Informer,到后來(lái)統(tǒng)一監(jiān)聽,各自 filter 的 sharedInformer。
所有的 controller 都在一個(gè) controller-manager 進(jìn)程內(nèi),所以完全可以共享同一個(gè) informer, 不同的 controller 注冊(cè)不同的 filter(kind、labelSelector),來(lái)訂閱自己需要的消息。
簡(jiǎn)而言之,現(xiàn)在的 sharedIndexer,就是一個(gè)統(tǒng)一的消息訂閱器,而且內(nèi)部還維護(hù)了一個(gè)資源存儲(chǔ),對(duì)外提供可過(guò)濾的消息分發(fā)和資源查詢功能。
sharedIndexer 和 sharedInformer 的區(qū)別就是多了個(gè) threadsafe 的 map 存儲(chǔ),用來(lái)存 shared resource object。
現(xiàn)在的 informer 中由幾個(gè)主要的組件構(gòu)成:
- reflecter:查詢器,負(fù)責(zé)從 apiserver 定期輪詢資源,更新 informer 的 store。
- store: informer 內(nèi)部對(duì)資源的存儲(chǔ),用來(lái)提供 lister 遍歷等查詢操作。
- queue:支持 controller 的事件訂閱。
各個(gè) controller 的訂閱和查詢絕大部分都在 sharedIndexer 的內(nèi)存內(nèi)完成,提高資源利用率和效率。
一般 controller 的消息來(lái)源就通過(guò)兩種方式:
- lister: controller 注冊(cè)監(jiān)聽特定類型的資源事件,事件格式是字符串,?
?<namespace>/<name>?
? - handler: controller 通過(guò) informer 的?
?AddEventHandler?
?? 方法注冊(cè)??Add/Update/Delete?
? 事件的處理函數(shù)。
這里有個(gè)值得注意的地方是,資源事件的格式是字符串,形如 ??<namespace>/<name>?
?,這其中沒(méi)有包含版本信息。
那么某個(gè)版本的 controller 拿到這個(gè)信息后,并不知道查詢出來(lái)的資源是否匹配自己的版本,也許會(huì)查出一個(gè)很舊版本的資源。
所以 controller 對(duì)于資源必須是向后兼容的,新版本的 controller 必須要能夠處理舊版資源。這樣的話,只需要保證運(yùn)行的是最新版的 controller 就行了。
1、Queue
controller 內(nèi)有大量的隊(duì)列,最重要的就是注冊(cè)到 informer 的三個(gè) add/update/delete 隊(duì)列。
RateLimitingQueue
- client-go@v0.0.0-20210708094636-69e00b04ba4c/util/workqueue/rate_limiting_queue.go
實(shí)際使用的是隊(duì)列類型是 RateLimitingQueue,繼承于 Queue。
Queue
- client-go@v0.0.0-20210708094636-69e00b04ba4c/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ì)列傳遞的資源事件是以字符串來(lái)表示的,格式形如 ??namespace/name?
?。
正因?yàn)橘Y源是字符串來(lái)表示,這導(dǎo)致了很多問(wèn)題。其中對(duì)于隊(duì)列的一個(gè)問(wèn)題就是:沒(méi)法為事件設(shè)置狀態(tài),標(biāo)記其是否已完成。為了實(shí)現(xiàn)這個(gè)狀態(tài),queue 中通過(guò) queue、dirty、processing 三個(gè)集合來(lái)表示。具體實(shí)現(xiàn)可以參考上面的注釋和代碼。
另一個(gè)問(wèn)題就是資源中沒(méi)有包含版本信息。
那么某個(gè)版本的 controller 拿到這個(gè)信息后,并不知道查詢出來(lái)的資源是否匹配自己的版本,也許會(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è)像語(yǔ)言 runtime 里的回收資源的自動(dòng)垃圾收集器。但其實(shí) k8s 里的 GC 的工作相對(duì)比較簡(jiǎn)單,更像是只是一個(gè)被動(dòng)的函數(shù)調(diào)用,當(dāng)用戶試圖刪除一個(gè)資源的時(shí)候, 就會(huì)把這個(gè)資源提交給 GC,然后 GC 執(zhí)行一系列既定的刪除流程,一般來(lái)說(shuō)包括:
- 刪除子資源
- 執(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?
?: 指向父資源的 UID - ?
?deletionTimestamp?
?: 如果不為空,表明該資源正在被刪除中 - ?
?finalizers?
?: 一個(gè)字符串?dāng)?shù)組,列舉刪除前必須執(zhí)行的操作 - ?
?blockOwnerDeletion?
?: 布爾,當(dāng)前資源是否會(huì)阻塞父資源的刪除流程
每一個(gè)資源都有 ??metadata.finalizers?
??,這是一個(gè) ??[]string?
?, 內(nèi)含一些預(yù)定義的字符串,表明了在刪除資源前必須要做的操作。每執(zhí)行完一個(gè)操作,就從 finalizers 中移除這個(gè)字符串。
無(wú)論是什么刪除策略,都需要先把所有的 finalizer 逐一執(zhí)行完,每完成一個(gè),就從 finalizers 中移除一個(gè)。在 finalizers 為空后,才能正式的刪除資源。
foreground、orphan 刪除就是通過(guò) finalizer 來(lái)實(shí)現(xiàn)的。
const (
FinalizerOrphanDependents = "orphan"
FinalizerDeleteDependents = "foregroundDeletion"
)
注:有一種讓資源永不刪除的黑魔法,就是為資源注入一個(gè)不存在的 finalizer。因?yàn)?GC 無(wú)法找到該 finalizer 匹配的函數(shù)來(lái)執(zhí)行,就導(dǎo)致這個(gè) finalizer 始終無(wú)法被移除, 而 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)前資源被刪除以前,該資源都通過(guò) 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 刪除策略是通過(guò) 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)前資源無(wú)法刪除。實(shí)際上真正刪除當(dāng)前資源(父資源),j是在刪除最后一個(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 都被抽象出來(lái)在 https://github.com/kubernetes/client-go 庫(kù)中,供各個(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]
引用鏈接
[1]
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/