突破etcd限制 位元組自研K8s儲存KubeBrain

陶然陶然發表於2022-11-25

  KubeBrain 是位元組跳動針對 Kubernetes 元資訊儲存的使用需求,基於分散式 KV 儲存引擎設計並實現的取代 etcd 的元資訊儲存系統,支撐線上超過 20,000 節點的超大規模 Kubernetes 叢集的穩定執行。

   1. 背景

  分散式應用編排排程系統 Kubernetes 已經成為雲原生應用基座的事實標準,但是其官方的穩定執行規模僅僅侷限在 5,000 節點。這對於大部分的應用場景已經足夠,但是對於百萬規模機器節點的超大規模應用場景, Kubernetes 難以提供穩定的支撐。

  尤其隨著“數字化””雲原生化”的發展,全球整體 IT 基礎設施規模仍在加速增長,對於分散式應用編排排程系統,有兩種方式來適應這種趨勢:

  水平擴充套件 : 即構建管理多個叢集的能力,在叢集故障隔離、混合雲等方面更具優勢,主要透過叢集聯邦(Cluster Federation)來實現;

  垂直擴充套件 : 即提高單個叢集的規模,在降低叢集運維管理成本、減少資源碎片、提高整體資源利用率方面更具優勢。  

  K8s 採用的是一種中心化的架構,所有元件都與 APIServer 互動,而 APIServer 則需要將叢集後設資料持久化到元資訊儲存系統中。當前,etcd 是 APIServer 唯一支援的元資訊儲存系統,隨著單個叢集規模的逐漸增大,儲存系統的讀寫吞吐以及總資料量都會不斷攀升,etcd 不可避免地會成為整個分散式系統的瓶頸。

  1.1 Kubernetes元資訊儲存需求

  APIServer 並不能直接使用一般的強一致 KV 資料庫作為元資訊儲存系統,它與元資訊儲存系統的互動主要包括資料全量和增量同步的 List/Watch,以及單個 KV 讀寫。更近一步來說,它主要包含以下方面:

  在版本控制方面,儲存系統需要對 APIServer 暴露資料的版本資訊,APIServer 側依賴於資料的版本生成對應的 ResourceVersion;

  在寫操作方面,儲存系統需要支援 Create/Update/Delete 三種語義的操作,更為重要的是,儲存系統需要支援在寫入或者刪除資料時對資料的版本資訊進行 CAS;

  在讀操作方面,儲存系統需要支援指定版本進行快照 List 以此從儲存中獲取全量的資料,填充APIServer 中的 WatchCache 或供查詢使用,此外也需要支援讀取資料的同時獲取對應的資料版本資訊;

  在事件監聽方面,儲存系統需要支援獲取特定版本之後的有序變更,這樣 APIServer 透過 List 從元資訊儲存中獲取了全量的資料之後,可以監聽快照版本之後的所有變更事件,進而以增量的方式來更新 Watch Cache 以及向其他元件進行變更的分發,進而保證 K8s 各個元件中資料的最終一致性。

  1.2 etcd 的實現方式與瓶頸

  etcd 本質上是一種主從架構的強一致、高可用分散式 KV 儲存系統:

  節點之間,透過 Raft 協議進行選舉,將操作抽象為 log 基於 Raft 的日誌同步機制在多個狀態機上同步;

  單節點上,按順序將 log 應用到狀態機,基於 boltdb 進行狀態持久化 。

  對於 APIServer 元資訊儲存需求,etcd 大致透過以下方式來實現:

  在版本控制方面,etcd 使用 Revision 作為邏輯時鐘,對每一個修改操作,會分配遞增的版本號Revision,以此進行版本控制,並且在記憶體中透過 TreeIndex 管理 Key 到 Revision 的索引;

  在寫操作方面,etcd 以序列 Apply Raft Log 的方式實現,以 Revision 為鍵,Key/Value/Lease 等資料作為值存入 BoltDB 中,在此基礎上實現了支援對 Revision 進行 CAS 的寫事務;

  在讀操作方面,etcd 則是透過管理 Key 到 Revision 的 TreeIndex 來查詢 Revision 進而查詢 Value,並在此基礎上實現快照讀;

  在事件監聽方面,歷史事件可以從 BoltDB 中指定 Revision 獲取 KV 資料轉換得到,而新事件則由寫操作同步 Notify 得到。

  etcd 並不是一個專門為 K8s 設計的元資訊儲存系統,其提供的能力是 K8s 所需的能力的超集。在使用過程中,其暴露出來的主要問題有:

  etcd 的網路介面層限流能力較弱,雪崩時自愈能力差;

  etcd 所採用的是單 raft group,存在單點瓶頸,單個 raft group 增加節點數只能提高容錯能力,並不能提高寫效能;

  etcd 的 ExpensiveRead 容易導致 OOM,如果採用分頁讀取的話,延遲相對會提高;

  boltdb 的序列寫入,限制了寫效能,高負載下寫延遲會顯著提高;

  長期執行容易因為碎片問題導致寫效能發生一定劣化,線上叢集定期透過 defrag 整理碎片,一方面會比較複雜,另一方面也可能會影響可用性。

   2. 新的後設資料儲存

  過去面對生產環境中 etcd 的效能問題,只能透過按 Resource 拆分儲存、etcd 引數調優等手段來進行一定的緩解。但是面對 K8s 更大範圍的應用之後帶來的挑戰,我們迫切的需要一個更高效能的後設資料儲存系統作為 etcd 的替代方案,從而能對上層業務有更有力的支撐。

  在調研了 K8s 叢集的需求以及相關開源專案之後,我們借鑑了 k3s 的開源專案 kine 的思想,設計並實現了基於分散式 KV 儲存引擎的高效能 K8s 後設資料儲存專案—— KubeBrain 。  

  KubeBrain 系統實現了 APIServer 所使用的元資訊儲存 API ,整體採用主從架構,主節點負責處理寫操作和事件分發,從節點負責處理讀操作,主節點和從節點之間共享一個分散式強一致 KV 儲存,在此基礎上進行資料讀寫。下面介紹 KubeBrain 的核心模組。

  2.1 儲存引擎 

  KubeBrain 統一抽象了邏輯層所使用的 KeyValue 儲存引擎介面,以此為基礎,專案實現了核心邏輯與底層儲存引擎的解耦:

  邏輯層基於儲存引擎介面來操作底層資料,不關心底層實現;

  對接新的儲存引擎只需要實現對應的適配層,以實現儲存介面。

  目前專案已經實現了對 ByteKV 和 TiKV 的適配,此外還實現了用於測試的適配單機儲存 Badger 的版本。需要注意的是,並非所有 KV 儲存都能作為 KubeBrain 的儲存引擎。當前 KubeBrain 對於儲存引擎有著以下特性要求:

  支援快照讀

  支援雙向遍歷

  支援讀寫事務或者帶有CAS功能的寫事務

  對外暴露邏輯時鐘

  此外,由於 KubeBrain 對於上層提供的一致性保證依賴於儲存引擎的一致性保證, KubeBrain 要求儲存引擎的事務需要達到以下級別(定義參考 HATs :):

  Isolation Guarantee: Snapshot Isolation

  Session Guarantee: Linearizable  

  在內部生產環境中, KubeBrain 均以 ByteKV 為儲存引擎提供元資訊儲存服務。ByteKV 是一種強一致的分散式 KV 儲存。在 ByteKV 中,資料按照 key 的字典序有序儲存。當單個 Partition 資料大小超過閾值時, Partition 自動地分裂,然後可以透過 multi-raft group 進行水平擴充套件,還支援配置分裂的閾值以及分裂邊界選擇的規則的定製。此外, ByteKV 還對外暴露了全域性的時鐘,同時支援寫事務和快照讀,並且提供了極高的讀寫效能以及強一致的保證。

  2.2 選主機制  

  KubeBrain 基於底層強一致的分散式 KV 儲存引擎,封裝實現了一種 ResourceLock,在儲存引擎中指向一組特定的 KeyValue。ResourceLock 中包含主節點的地址以及租約的時長等資訊。

  KubeBrain 程式啟動後均以從節點的身份對自己進行初始化,並且會自動在後臺進行競選。競選時,首先嚐試讀取當前的 ResourceLock。如果發現當前 ResourceLock 為空,或者 ResourceLock 中的租約已經過期,節點會嘗試將自己的地址以及租約時長以 CAS 的方式寫入 ResourceLock,如果寫入成功,則晉升為主節點。

  從節點可以透過 ResourceLock 讀取主節點的地址,從而和主節點建立連線,並進行必要的通訊,但是主節點並不感知從節點的存在。即使沒有從節點,單個 KubeBrain 主節點也可以提供完成的 APIServer 所需的 API,但是主節點當機後可用性會受損。

  2.3 邏輯時鐘

  KubeBrain 與 etcd 類似,都引入了 Revision 的概念進行版本控制。KubeBrain 叢集的發號器僅在主節點上啟動。當從節點晉升為主節點時,會基於儲存引擎提供的邏輯時鐘介面來進行初始化,發號器的Revision 初始值會被賦值成儲存引擎中獲取到的邏輯時間戳。

  單個 Leader 的任期內,發號器發出的整數號碼是單調連續遞增的。主節點發生故障時,從節點搶到主,就會再次重複一個初始化的流程。由於主節點的發號是連續遞增的,而儲存引擎的邏輯時間戳可能是非連續的,其增長速度是遠快於連續發號的發號器,因此能夠保證切主之後, Revision 依然是遞增的一個趨勢,舊主節點上發號器所分配的最大的 Revision 會小於新主節點上發號器所分配的最小的Revision。

  KubeBrain 主節點上的發號是一個純記憶體操作,具備極高的效能。由於 KubeBrain 的寫操作在主節點上完成,為寫操作分配 Revision 時並不需要進行網路傳輸,因此這種高效能的發號器對於最佳化寫操作效能也有很大的幫助。

  2.4 資料模型

  KubeBrain 對於 API Server 讀寫請求引數中的 Raw Key,會進行編碼出兩類 Internal Key寫入儲存引擎索引和資料。對於每個 Raw Key,索引 Revision Key 記錄只有一條,記錄當前 Raw Key 的最新版本號, Revision Key 同時也是一把鎖,每次對 Raw Key 的更新操作需要對索引進行 CAS。資料記錄Object Key 有一到多條,每條資料記錄了 Raw Key 的歷史版本與版本對應的 Value。Object Key 的編碼方式為magic+raw_key+split_key+revision,其中:

  magic為\x57\xfb\x80\x8b;

  raw_key為實際 API Server 輸入到儲存系統中的 Key ;

  split_key為$;

  revision為邏輯時鐘對寫操作分配的邏輯操作序號透過 BigEndian 編碼成的 Bytes 。  

  根據 Kubernetes 的校驗規則,raw_key 只能包含小寫字母、數字,以及'-' 和 '.',所以目前選擇 split_key 為 $ 符號。

  特別的,Revision Key 的編碼方式和 Object Key 相同,revision取長度為 8 的空 Bytes 。這種編碼方案保證編碼前和編碼後的比較關係不變。

  在儲存引擎中,同一個 Raw Key 生成的所有 Internal Key 落在一個連續區間內 。  

  這種編碼方式有以下優點:

  編碼可逆,即可以透過Encode(RawKey,Revision)得到InternalKey,相對應的可以透過Decode(InternalKey)得到Rawkey與Revision;

  將 Kubernetes 的物件資料都轉換為儲存引擎內部的 Key-Value 資料,且每個物件資料都是有唯一的索引記錄最新的版本號,透過索引實現鎖操作;

  可以很容易地構造出某行、某條索引所對應的 Key,或者是某一塊相鄰的行、相鄰的索引值所對應的 Key 範圍;

  由於 Key 的格式非單調遞增,可以避免儲存引擎中的遞增 Key 帶來的熱點寫問題。

  2.5 資料寫入

  每一個寫操作都會由發號器分配一個唯一的寫入 revision ,然後併發地對儲存引擎進行寫入。在 建立、更新 和 刪除 Kubernetes 物件資料的時候,需要同時操作物件對應的索引和資料。由於索引和資料在底層儲存引擎中是不同的 Key-Value 對,需要使用 寫事務 保證更新過程的 原子性,並且要求至少達到 Snapshot Isolation 。  

  同時 KubeBrain 依賴索引實現了樂觀鎖進行併發控制。KubeBrain 寫入時,會先根據 APIServer 輸入的 RawKey 以及被髮號器分配的 Revision 構造出實際需要到儲存引擎中的 Revision Key 和 Object Key,以及希望寫入到 Revision Key 中的 Revision Bytes。在寫事務過程中,先進行索引 Revision Key 的檢查,檢查成功後更新索引 Revision Key,在操作成功後進行資料 Object Key 的插入操作。

  執行 Create 請求時,當 Revision Key 不存在時,才將 Revision Bytes 寫入 Revision Key 中,隨後將 API Server 寫入的 Value 寫到 Object Key 中;

  執行 Update 請求時,當 Revision Key 中存放的舊 Revision Bytes 符合預期時,才將新 Revision Bytes 寫入,隨後將 API Server 寫入的 Value 寫到 Object Key 中;

  執行 Delete 請求時,當 Revision Key 中存放的舊 Revision Bytes 符合預期時,才將新 Revision Bytes 附帶上刪除標記寫入,隨後將 tombstone 寫到 Object Key 中。

  由於寫入資料時基於遞增的 Revision 不斷寫入新的 KeyValue , KubeBrain 會進行後臺的垃圾回收操作,將 Revision 過舊的資料進行刪除,避免資料量無限增長。

  2.6 資料讀取

  資料讀取分成點讀和範圍查詢查詢操作,分別對應 API Server 的 Get 和 List 操作。  

  Get 需要指定讀操作的ReadRevision,需要讀最新值時則將 ReadRevision 置為最大值MaxUint64, 構造 Iterator ,起始點為Encode(RawKey, ReadRevision),向Encode( RawKey, 0)遍歷,取第一個。  

  範圍查詢需要指定讀操作的ReadRevision 。對於範圍查詢的 RawKey 邊界[RawKeyStart, RawKeyEnd)區間, KubeBrain 構造儲存引擎的 Iterator 快照讀,透過編碼將 RawKey 的區間對映到儲存引擎中 InternalKey 的資料區間

  InternalKey 上界InternalKeyStart為Encode(RawKeyStart, 0)

  InternalKey 的下界為InternalKeyEnd為Encode(RawKeyEnd, MaxRevision)

  對於儲存引擎中[InternalKeyStart, InternalKeyEnd)內的所有資料按序遍歷,透過Decode(InternalKey)得到RawKey與Revision,對於一個RawKey 相同的所有ObjectKey,在滿足條件Revision<=ReadRevision的子集中取Revision最大的,對外返回。

  2.7 事件機制

  對於所有變更操作,會由 TSO 分配一個連續且唯一的 revision ,然後併發地寫入儲存引擎中。變更操作寫入儲存引擎之後,不論寫入成功或失敗,都會按照 revision 從小到大的順序,將變更結果提交到滑動視窗中,變更結果包括變更的型別、版本、鍵、值、寫入成功與否 。在記錄變更結果的滑動視窗中,從起點到終點,所有變更資料中的 revision 嚴格遞增,相鄰 revision 差為 1。  

  記錄變更結果的滑動視窗由事件生成器統一從起點開始消費,取出的變更結果後會根據變更的 revision更新發號器的 commit index ,如果變更執行成功,則還會構造出對應的修改事件,將並行地寫入事件快取和分發到所有監聽所建立出的通知佇列。  

  在後設資料儲存系統中,需要監聽指定邏輯時鐘即指定 revision 之後發生的所有修改事件,用於下游的快取更新等操作,從而保證分散式系統的資料最終一致性。註冊監聽時,需要傳入起始 revision 和過濾引數,過濾引數包括 key 字首等等。

  當客戶端發起監聽時,服務端在建立事件流之後的處理,分成以下幾個主要步驟:

  處理監聽註冊請求時首先建立通知佇列,將通知佇列註冊到事件生成元件中,獲取下發的新增事件;

  從事件快取中拉取事件的 revision 大於等於給定要求 revision 所有事件到事件佇列中,並放到輸出佇列中,以此獲取歷史事件;

  將通知佇列中的事件取出,新增到輸出佇列中, revision 去重之後新增到輸出佇列;

  按照 revision 從小到大的順序,依次使用過濾器進行過濾;

  將過濾後符合客戶端要求的事件,透過事件流推送到後設資料儲存系統外部的客戶端。

   3. 落地效果  

  在 Benchmark 環境下,基於 ByteKV 的 KubeBrain 對比於 etcd 純寫場景吞吐提升 10 倍左右,延遲大幅度降低, PCT 50 降低至 1/6 ,PCT 90 降低至 1/20 ,PCT 99降低至 1/4 ;讀寫混合場景吞吐提升 4 倍左右;事件吞吐大約提升5倍;

  在模擬 K8s Workload 的壓測環境中,配合 APIServer 側的最佳化和調優,支援 K8s 叢集規模達到 5w Node 和 200w Pod;

  在生產環境中,穩定上量至 2.1w Node ,高峰期寫入超過 1.2w QPS,讀寫負載合計超過 1.8w QPS。

   4. 未來演進

  專案未來的演進計劃主要包括四個方面的工作:

  探索實現多點寫入的方案以支援水平擴充套件 現在 KubeBrain 本質上還是一個單主寫入的系統,KubeBrain 後續會在水平擴充套件方面做進一步的探索,後續也會在社群中討論;

  提升切主的恢復速度 當前切主會觸發 API Server 側的 Re-list ,資料同步的開銷較大,我們會在這方面進一步做最佳化;

  實現內建儲存引擎 實現兩層儲存融合,由於現在在儲存引擎、KubeBrain 中存在兩層 MVCC 設計,整體讀寫放大較多,實現融合有助於降低讀寫放大,更進一步提高效能;

  完善周邊元件 包括資料遷移工具、備份工具等等,幫助使用者更好地使用 KubeBrain 。

來自 “ 位元組跳動技術團隊 ”, 原文作者:薛英才;原文連結:http://server.it168.com/a2022/1125/6777/000006777213.shtml,如有侵權,請聯絡管理員刪除。

相關文章