掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
可以先設(shè)想一下如果自己去實現(xiàn)的話,該如何設(shè)計。Client 和 Server 端都要去適配這是必然的,因為 Informer 現(xiàn)在是 ListWatch 機制,服務(wù)端并不支持流式 List。因此可以有個初步的方向:

成都創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價比昆都侖網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式昆都侖網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋昆都侖地區(qū)。費用合理售后完善,10余年實體公司更值得信賴。
客戶端的適配相對簡單,重點還是放在 Server 端如何實現(xiàn)。先回顧下之前 List 的邏輯,在前一篇 Stale Read 里面已經(jīng)介紹過了。
為方面描述,下文統(tǒng)一使用 RV 代指 Resourceversion,本節(jié)邏輯均基于 v1.26.9 版本,且忽略分頁查詢,因為分頁是直接走 Etcd 的。
無論是 List 還是 Watch 請求,其 query 均支持傳入 RV,服務(wù)端會根據(jù)請求的 RV 的不同做相應(yīng)的處理,根據(jù) RV 的值可以分為三種情況
- 未設(shè)置或者顯示設(shè)置 RV=""
- RV = "0"
- RV = "非 0 值"
對于前兩種情況,List 會直接返回 WatchCache Store 中的內(nèi)容,即服務(wù)端緩存好的 Etcd 的全部相關(guān)數(shù)據(jù)。
對于第三種情況,會等待服務(wù)端緩存數(shù)據(jù)的最大版本要超過傳入的 RV 之后再返回緩存內(nèi)的數(shù)據(jù),如果等待了一段時間(3s)后緩存中的數(shù)據(jù)仍然沒有達到指定版本,則會報錯返回 "Too large resource version",并告訴客戶端可以在 1s 之后重試。
新版中已經(jīng)修復(fù)了 List Stale Read 的問題,對于前兩種情況,其會先從 kube-apiserver 獲取 Etcd 最新的 RV,等待 WatchCache Store 內(nèi)容追平 RV 后再一次性的返回。
也就是說服務(wù)端是可以知道自己是否已經(jīng)包含最新全量數(shù)據(jù)的,在這個基礎(chǔ)上再以流式方式返回即可。當(dāng)前已有的流式 API 就是 Watch,所以可以在此基礎(chǔ)上支持 List 的效果。為什么不直接在 List 請求基礎(chǔ)上改呢,因為改 List 的話,會涉及到太多的客戶端側(cè)的適配,List 會經(jīng)常單獨使用,而 Watch 基本是在 Informer 里面使用。
所以最終的工作就會變成如何使用 Watch API 實現(xiàn) List 的效果,但數(shù)據(jù)仍然以流式返回給客戶端,同時 Informer 修改 ListWatch 方式為只使用 Watch API 實現(xiàn)之前的效果。下文以詳細介紹服務(wù)端實現(xiàn)為主,客戶端適配的部分會比較簡單的介紹下。
通過為 Watch API 添加一個 SendInitialEvents=true 參數(shù)來支持 List 的效果。Server 端接收到 Watch 請求后判斷哪些數(shù)據(jù)是應(yīng)該作為 InitEvents 發(fā)送給客戶端,同時在發(fā)送完這些數(shù)據(jù)之后發(fā)送一個特定的 BOOKMARK Event(帶特定 Annotation 的 BOOKMARK,其 RV 對應(yīng)下文的 bookmarkAfterRV)給客戶端作為服務(wù)端通知客戶端 InitEvents 發(fā)送完畢的標(biāo)志,客戶端在接收到指定 BOOKMARK Event 后,將之前接收到的所有 InitEvent 作為 List 的結(jié)果處理。
下面是基于 v1.29 代碼的分析,此時 v1.29 還在 alpha 狀態(tài),提到的舊版代表 1.27 之前的版本,新版代表 v1.29。如果你看到的代碼和下面描述的不一致,有可能是代碼版本導(dǎo)致的。
圖片
從 WatchCache 開始右面四個藍色的是在 kube-apiserver 啟動的時候開始執(zhí)行的,G1 G2 代表兩個 goroutine,分別用來從 Etcd 獲取數(shù)據(jù),以及發(fā)送數(shù)據(jù)給客戶端 CacheWatcher 的 input chan
上述過程描述了服務(wù)端啟動時的數(shù)據(jù)處理流程,接下來看有客戶端請求時的處理流程
2a 開始從 WatchCache Store 中獲取需要返回的數(shù)據(jù),此時的處理邏輯舊版本相同,返回 Store 中的全部數(shù)據(jù),并記錄 Store 數(shù)據(jù)的最大 RV 供下一步使用;
2b 消費 input chan 中的事件,對比其 RV 是否比 2a 傳入的 RV 大,或者如果是 BOOKMARK 類型并且 RV 等與 2a 傳入的 RV,且尚未發(fā)送 bookmarkAfterRV 的事件,則此 BOOKMARK 事件就會被當(dāng)做 List 結(jié)束的標(biāo)志,為其設(shè)置 Annotation: k8s.io/initial-events-end,最后發(fā)送給客戶端;
至此,服務(wù)端的主要流程已經(jīng)介紹完,客戶端 Informer 也做了對應(yīng)的適配,如果開啟 WathList 功能的話,會發(fā)送 Watch 請求來獲取一遍全量數(shù)據(jù),等到接收到攜帶 Annotation: k8s.io/initial-events-end 的 BOOKMARK 事件后,記錄其 RV,將在此期間接受并處理后的對象作為 List 的結(jié)果。最后再次以上述 RV 作為參數(shù)調(diào)用 Watch 請求,從這一步開始就是 Informer 傳統(tǒng)意義上的 Watch 邏輯了。
圖片
圖片來自 KEP 3157 watch-list,其實里面也包含時序圖,不過里面的書序圖畫的有一些問題,和代碼不一致,所以這里并沒有直接使用他的時序圖,而是重新畫了。
可以結(jié)合上面兩個圖理解整個過程,上圖中的 a 對應(yīng)時序圖中的 2a,b 對應(yīng)時序圖中的 2b,c 對應(yīng)時序圖中的 G2.1。最下面白色部分對應(yīng)時序圖中 G1 的邏輯,即從 Etcd 獲取數(shù)據(jù),客戶端請求的處理是自上到下的,而數(shù)據(jù)返回是自下而上的。
上述處理邏輯中存在很多的細節(jié),需要額外注意下
本篇主要分析了 WatchList 的實現(xiàn)原理和邏輯,其中不乏一些細節(jié)處理,后續(xù)也會和社區(qū)就有關(guān)細節(jié)進一步討論。在此 KEP 中同時還介紹了另外兩個用來降低 kube-apiserver 內(nèi)存壓力的修改,篇幅有限,將會在下一篇中進行介紹,同時也會給出所有優(yōu)化工作做完前后的效果對比。敬請期待~

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流