k8s控制器resyncPeriod機(jī)制定時(shí)把k8s apiserver內(nèi)存和cpu打得很高
近期發(fā)現(xiàn),k8s apiserver的內(nèi)存和cpu定時(shí)(每隔10h)被客戶(hù)一個(gè)控制器打的很高,有個(gè)小突刺。排查發(fā)現(xiàn),用戶(hù)的控制器開(kāi)啟了resyncPeriod,默認(rèn)值就是10h。
一般來(lái)說(shuō)controller runtime框架、knative框架,都會(huì)默認(rèn)這個(gè)值為10h。不同的是,controller runtime框架會(huì)打算到10h附近,而knative框架是嚴(yán)格10h。當(dāng)然真實(shí)時(shí)間會(huì)受到上次執(zhí)行的影響,略有偏差。

定位到問(wèn)題后,解決起來(lái)也很簡(jiǎn)單,構(gòu)建informer時(shí),將resyncPeriod設(shè)置為0就行。
但是,客戶(hù)有擔(dān)心,偶發(fā)k8s里面緩存不夠新的問(wèn)現(xiàn)象,擔(dān)心關(guān)閉resyncPeriod會(huì)影響到informer機(jī)制中緩存的更新。第一反應(yīng),客戶(hù)是不是對(duì)ResyncPeriod這個(gè)參數(shù)有什么誤解?這個(gè)參數(shù)不會(huì)重新到APIServer拉取全量數(shù)據(jù),而只是把indexer里面的緩存的所有key list一遍,重新推進(jìn)DeltaFIFO,觸發(fā)controller將所有事件重新調(diào)協(xié)一遍。
順帶把整個(gè)informer機(jī)制相關(guān)的debug一遍,確認(rèn)無(wú)誤。此處粘貼一些圖做些記錄。
1. Informer機(jī)制各種類(lèi)詳細(xì)介紹
https://blog.imoe.tech/2023/02/15/kubernetes-informer-mechanism/
這篇寫(xiě)得非常好。
網(wǎng)上有大量文章,寫(xiě)的并不準(zhǔn)確,ResyncPeriod參數(shù),竟然把它說(shuō)成可以重新從K8S APIServer全量拉取informer數(shù)據(jù),更新informer緩存,避免watch機(jī)制錯(cuò)誤導(dǎo)致緩存數(shù)據(jù)的不一致。這些文章給我?guī)?lái)了很大的干擾。所以我不斷調(diào)整ResyncPeriod,debug測(cè)試。發(fā)現(xiàn)控制器并沒(méi)有根據(jù)ResyncPeriod參數(shù),去LIST K8SAPISERVER更新整個(gè)緩存;而只是從indexer的ThreadSafeMap里,list所有的key,重新推入DeltaFIFO,觸發(fā)事件,讓控制器重新handler。

client-go 組件
- Reflector:指的是
cache包中定義的 Reflector 類(lèi),用于監(jiān)控 Kubernetes 資源變化,其功能由ListAndWatch函數(shù)實(shí)現(xiàn)。當(dāng) Reflector 接收到資源變更的事件,會(huì)獲取到變更的對(duì)象并在函數(shù)watchHandler中放到Delta Fifo隊(duì)列。 - Delta FIFO:是一個(gè) FIFO 的隊(duì)列,用來(lái)緩存
Reflector拉取到的變更事件和資源對(duì)象; - Informor:是流程中最重要的節(jié)點(diǎn),是整個(gè)流程的橋梁,
Informer也是在cache包中定義的,其功能在processLoop函數(shù)中實(shí)現(xiàn),負(fù)責(zé):- 從 Delta FIFO 中 pop 出對(duì)象并更新到 Indexer 的 cache 中;
- 調(diào)用自定義 Controller,傳遞該對(duì)象。
- Indexer:指在
cache包中定義的 Indexer 類(lèi),主要是在資源對(duì)象上提供了索引和本地緩存的功能。經(jīng)典的使用場(chǎng)景是基于對(duì)象的 Labels 創(chuàng)建索引,Indexer 可以支持使用索引函數(shù)來(lái)維護(hù)索引,同時(shí) Indexer 使用線程安全的 Data Store 來(lái)存儲(chǔ)資源對(duì)象和對(duì)應(yīng)的 Key。默認(rèn)使用的是cache包里的 MetaNamespaceKeyFunc 函數(shù)來(lái)生成對(duì)象的 Key,格式如:<namespace>/<name>。
自定義組件
上圖中 Informer reference 和 Indexer reference 是指在自定義 Controller 中需要自己創(chuàng)建的 Informer 和 Indexer 的實(shí)例,用來(lái)與整個(gè)流程進(jìn)行交互,需要根據(jù)需要的資源創(chuàng)建對(duì)應(yīng)的實(shí)例。client-go 提供了 NewIndexerInformer 函數(shù)來(lái)創(chuàng)建 Informer 和 Indexer 實(shí)例,也可以使用 SharedInformerFactory 的工廠方法來(lái)創(chuàng)建實(shí)例。
每個(gè)資源都會(huì)對(duì)應(yīng)一個(gè) Informer,每個(gè) Informer 都通過(guò) Watch 創(chuàng)建一個(gè)長(zhǎng)連接。如果一個(gè)資源創(chuàng)建了多個(gè) Informer 無(wú)疑是非常浪費(fèi)的,所以通常都使用 SharedInformerFactory 工廠方法來(lái)創(chuàng)建,這樣每種資源都復(fù)用一個(gè) Informer,從而降低開(kāi)銷(xiāo)。


不過(guò),各位可能有個(gè)小小的疑問(wèn),客戶(hù)每10h全量調(diào)協(xié)處理一遍數(shù)據(jù),正常應(yīng)該客戶(hù)的pod cpu和內(nèi)存打得很高,為什么會(huì)K8S APIServer也被打得高?
這里面客戶(hù)有個(gè)錯(cuò)誤用法,他沒(méi)有進(jìn)行事件過(guò)濾,正常來(lái)說(shuō),重新全量調(diào)協(xié),是要處理那些需要處理的數(shù)據(jù),不需要的應(yīng)該用EventHandler過(guò)濾掉??蛻?hù)沒(méi)有做,導(dǎo)致每個(gè)事件,他都會(huì)調(diào)協(xié)處理,處理邏輯里面有大量list資源的操作(list加了labelSelector條件), 導(dǎo)致K8S APIServer緩存打得很高。
不過(guò),在協(xié)助處理客戶(hù)這個(gè)問(wèn)題時(shí),我也有點(diǎn)小小的疑惑:
前期客戶(hù)的處理邏輯里面有大量list資源操作,甚至部分沒(méi)有加resourceVersion=0(直查etcd)。這種情況下,把K8S APIServer內(nèi)存打得很高,可以理解。
但是后面用戶(hù)把全部list操作加上resourceVersion=0后,仍然會(huì)把K8S APIServer打得很高,這個(gè)就很奇怪。K8S APIServer在接受list請(qǐng)求時(shí),直接從內(nèi)存里面拿數(shù)據(jù),為何會(huì)內(nèi)存瞬間打得很高?難道針對(duì)大量list請(qǐng)求操作,它瞬間產(chǎn)生大量的局部變量?看來(lái)得看看k8s相關(guān)代碼,才能解答。
下面開(kāi)始上想干代碼截圖:
1. resyncPeriod的配置代碼






可以看到,resyncPeriod不是完全按照設(shè)置的值,對(duì)每個(gè)watch的CRD類(lèi)型全量重入隊(duì)調(diào)協(xié)(注意:只是把所有數(shù)據(jù)創(chuàng)建event后,重新入隊(duì)調(diào)協(xié)處理,不會(huì)重新從K8S APIServer拉取全量數(shù)據(jù))。它會(huì)偏正計(jì)算,設(shè)置為 resyncPeriod值 * [0.9-1.1)之間的一個(gè)值。啟動(dòng)控制器后,這個(gè)值一旦偏正計(jì)算過(guò)后,就不會(huì)變了,每個(gè)類(lèi)型按照各自偏正值定時(shí)全量。但是有可能會(huì)收到控制器執(zhí)行任務(wù)的一些干擾。
2. 觸發(fā)Full Resync





寫(xiě)入deltaFIFO后,就會(huì)被Informer取到事件,觸發(fā)Reconciler重新調(diào)協(xié)處理了。
浙公網(wǎng)安備 33010602011771號(hào)