到精通】第63篇:Informer機(jī)制——K8s高性能事件驅(qū)動(dòng)的核心,沒(méi)有它控制器早累死了)
上一篇【第62篇】Controller Manager——K8s的自動(dòng)駕駛儀下一篇【第64篇】Scheduler深度解析——Pod調(diào)度算法的高考閱卷摘要上篇我們講了控制器的協(xié)調(diào)循環(huán)提到它靠Informer第一時(shí)間知道資源變了。但I(xiàn)nformer到底怎么做到的為什么幾十個(gè)控制器并發(fā)跑API Server沒(méi)被查爆答案藏在兩個(gè)設(shè)計(jì)里L(fēng)ist-Watch先全量列一遍再增量監(jiān)聽(tīng)變化和本地緩存每個(gè)組件在內(nèi)存里存一份資源副本查狀態(tài)不用打API Server。這篇文章拆開(kāi)Informer的內(nèi)部架構(gòu)——Reflector偵察兵、DeltaFIFO變化隊(duì)列、Indexer本地索引緩存三大件講清增量同步和Resync兜底機(jī)制以及SharedInformerFactory怎么讓多個(gè)控制器共用一份緩存。一、為什么需要Informer1.1 沒(méi)有Informer的災(zāi)難【如果控制器直接查API Server】 50個(gè)控制器每個(gè)每秒查一次: GET /api/v1/pods → API Server GET /api/v1/services → API Server GET /api/v1/endpoints → API Server ... → API Server被輪詢(xún)打死 → 網(wǎng)絡(luò)帶寬爆炸 → 延遲高、吞吐低 而且每次都返回全量列表(可能幾千個(gè)對(duì)象) → 極其浪費(fèi)1.2 Informer的解法【Informer 本地緩存 增量通知】 每個(gè)組件(controller/kubelet)啟動(dòng)一個(gè)Informer: 1. List: 一次性拉全量(sync cache) 2. Watch: 之后只收變化事件(ADDED/MODIFIED/DELETED) 3. 本地維護(hù)一份緩存(Indexer) 控制器查狀態(tài) → 直接讀本地緩存(不打API Server!) 資源變化 → Watch事件秒級(jí)通知 → 觸發(fā)調(diào)諧 → API Server壓力驟降 → 控制器響應(yīng)飛快要點(diǎn)Informer的核心價(jià)值是**“把讀壓力從API Server轉(zhuǎn)移到本地內(nèi)存”**。全量List只在啟動(dòng)時(shí)做一次之后全是輕量的Watch增量事件。這就是為什么K8s能輕松支撐成百上千個(gè)控制器和數(shù)以萬(wàn)計(jì)的Pod——它們大部分讀操作都命中本地緩存。二、Informer內(nèi)部架構(gòu)2.1 三大件【Informer 內(nèi)部流水線】 API Server │ │ List-Watch ▼ ┌─────────────────────────────────────────┐ │ 1. Reflector (偵察兵) │ │ ? List: 拉全量 → 存Indexer │ │ ? Watch: 收增量事件 │ │ ? 把事件投進(jìn) DeltaFIFO │ └─────────────────┬───────────────────────┘ ▼ ┌─────────────────────────────────────────┐ │ 2. DeltaFIFO (變化隊(duì)列) │ │ ? 存放發(fā)生了什么變化 │ │ ? 類(lèi)型: Added/Updated/Deleted/Sync │ │ ? FIFO: 先來(lái)先處理 │ └─────────────────┬───────────────────────┘ ▼ ┌─────────────────────────────────────────┐ │ 3. Indexer (本地索引緩存) │ │ ? 內(nèi)存里存對(duì)象(按namespace/name索引) │ │ ? 控制器查狀態(tài)從這里讀(不打API Server) │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ 4. EventHandler (你的回調(diào)) │ │ ? OnAdd / OnUpdate / OnDelete │ │ ? 把 key 塞進(jìn) WorkQueue → 觸發(fā)Reconcile│ └─────────────────────────────────────────┘2.2 代碼視角// 一個(gè)典型的Informer使用(偽代碼)informer:cache.NewSharedIndexInformer(cache.ListWatch{// Reflector用的List/Watch函數(shù)ListFunc:func(){returnclient.List(...)},WatchFunc:func(){returnclient.Watch(...)},},v1.Pod{},// 關(guān)心Podtime.Minute*30,// resync周期cache.Indexers{cache.NamespaceIndex:cache.MetaNamespaceIndexFunc},)// 注冊(cè)事件回調(diào)informer.AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc:func(obj){key:getKey(obj)workqueue.Add(key)// 塞進(jìn)工作隊(duì)列},UpdateFunc:func(old,new){workqueue.Add(getKey(new))},DeleteFunc:func(obj){workqueue.Add(getKey(obj))},})informer.Run(stopCh)// 啟動(dòng)開(kāi)始List-Watch三、List-Watch與增量同步3.1 全量增量的配合【List-Watch 時(shí)序圖】 T0: Informer啟動(dòng) ├─ List /pods → 拿到全部1000個(gè)Pod │ → 寫(xiě)入Indexer緩存 │ → 注意記住 resourceVersion1000 │ T1: Watch /pods?resourceVersion1000 (從此版本開(kāi)始監(jiān)聽(tīng)) ├─ 收到 ADDED pod-1001 → 緩存1, 通知handler ├─ 收到 MODIFIED pod-5 → 緩存更新, 通知handler ├─ 收到 DELETED pod-3 → 緩存-1, 通知handler │ (后續(xù)只收增量永遠(yuǎn)不拉全量了) ?? 如果Watch連接斷了: 從斷點(diǎn)resourceVersion重新Watch 若斷太久(etcd已壓縮舊歷史) → 重新List要點(diǎn)resourceVersion是這里的靈魂——它像數(shù)據(jù)庫(kù)的日志偏移量。List時(shí)記下當(dāng)前的resourceVersionWatch從那個(gè)點(diǎn)繼續(xù)保證不丟事件、不重復(fù)。如果連接斷了從斷點(diǎn)續(xù)傳斷太久etcd的歷史被壓縮了就重新List一把。這套機(jī)制保證了最終一致性——緩存最終和etcd完全一致。四、Resync兜底糾正4.1 為什么需要Resync【Resync 解決事件丟了的問(wèn)題】 場(chǎng)景某個(gè)UPDATE事件因?yàn)榫W(wǎng)絡(luò)抖動(dòng)丟了 → Indexer緩存和實(shí)際狀態(tài)不一致 → 但沒(méi)人通知handler去調(diào)諧 Resync機(jī)制: ? 每隔一段時(shí)間(如30分鐘)定時(shí)觸發(fā) ? 把Indexer里所有對(duì)象假裝成Updated事件 ? 重新塞進(jìn)WorkQueue → 重新調(diào)諧 ? 即使沒(méi)真的變化也強(qiáng)制重新核對(duì)一遍 → 保證即使丟了事件最終也會(huì)糾正 → 這是K8s最終一致性的保險(xiǎn)絲五、SharedInformerFactory共享緩存5.1 多個(gè)控制器共用一份【SharedInformerFactory——?jiǎng)e各搞各的】 沒(méi)有共享(浪費(fèi)): Controller A 起一個(gè) Pod Informer (一份緩存) Controller B 起一個(gè) Pod Informer (又一份緩存) Controller C 起一個(gè) Pod Informer (又又一份) → 3份相同緩存3次List-Watch浪費(fèi)! 有共享(省): SharedInformerFactory 起一個(gè) Pod Informer 多個(gè)控制器共用這一份緩存 這一個(gè)Watch → 1份緩存1次List-Watch所有控制器受益// 共享Informer工廠factory:informers.NewSharedInformerFactory(client,0)podInformer:factory.Core().V1().Pods()// 所有控制器共用這個(gè)// Controller A 注冊(cè)handlerpodInformer.Informer().AddEventHandler(handlerA)// Controller B 注冊(cè)handlerpodInformer.Informer().AddEventHandler(handlerB)// 只啟動(dòng)一次 Watch但兩個(gè)handler都能收到事件factory.Start(stopCh)要點(diǎn)SharedInformerFactory是K8s性能優(yōu)化的關(guān)鍵一招——讓同一個(gè)進(jìn)程里的多個(gè)控制器共享一份Informer一份緩存、一次Watch。controller-manager、kubelet、Operator框架(client-go)全都用它。既省API Server壓力又省內(nèi)存。理解Informer是讀懂K8s源碼和寫(xiě)Operator的必經(jīng)之路。本篇小結(jié)Informer是K8s高性能事件驅(qū)動(dòng)的核心用List-Watch啟動(dòng)全量之后增量維護(hù)本地緩存讓控制器讀狀態(tài)不打API Server、變化秒級(jí)感知。內(nèi)部三件套R(shí)eflectorList-Watch偵察兵把事件投進(jìn)DeltaFIFO變化隊(duì)列再更新Indexer本地索引緩存最后觸發(fā)你的EventHandler把key塞進(jìn)WorkQueue。resourceVersion保證增量不丟不重Resync定時(shí)兜底糾正丟事件保證最終一致。SharedInformerFactory讓同進(jìn)程多控制器共享一份緩存——既省API壓力又省內(nèi)存。下篇深入Scheduler——Pod調(diào)度算法的高考閱卷。上一篇【第62篇】Controller Manager——K8s的自動(dòng)駕駛儀下一篇【第64篇】Scheduler深度解析——Pod調(diào)度算法的高考閱卷