螞蟻集團萬級規模 K8s 叢集 etcd 高可用建設之路

螞蟻金服分散式架構發表於2021-08-04

螞蟻集團運維著可能是全球最大的 K8s 叢集:K8s 官方以 5k node 作為 K8s 規模化的頂峰,而螞蟻集團事實上運維著規模達到 10k node 規模的 K8s 叢集。一個形象的比喻就是,如果官方以及跟著官方的 K8s 使用者能想象到的 K8s 的叢集規模是泰山,那麼螞蟻集團在官方的解決方案之上已經實現了一個珠穆朗瑪峰,引領了 K8s 規模化技術的提升。

這個量級的差異,不僅僅是量的差異,更是 K8s 管理維護的質的提升。能維護有如此巨大挑戰巨量規模的 K8s 叢集,其背後原因是螞蟻集團付出了遠大於 K8s 官方的優化努力。

所謂萬丈高樓平地起,本文著重討論下螞蟻集團的在 K8s 的基石 --- etcd 層面做出的高可用建設工作:只有 etcd 這個基石穩當了,K8s 這棟高樓大廈才保持穩定性,有 tidb 大佬黃東旭朋友圈佐證【圖片已獲黃總授權】。

面臨的挑戰

etcd 首先是 K8s 叢集的 KV 資料庫。 從資料庫的角度來看,K8s 整體叢集架構各個角色如下:

  1. etcd 叢集的資料庫

  2. kube-apiserver etcd 的 API 介面代理、資料快取層

  3. kubelet 資料的生產者和消費者

  4. kube-controller-manager 資料的消費者和生產者

  5. kube-scheduler 資料的消費者和生產者

etcd 本質是一個 KV 資料庫,儲存了 K8s 自身資源 、使用者自定義的 CRD 以及 K8s 系統的 event 等資料。每種資料的一致性和資料安全性要求不一致,如 event 資料的安全性小於 K8s 自身的資源資料以及 CRD 資料。

K8s 的早期擁護者在推廣 K8s 時,宣稱其比 OpenStack 的優勢之一是 K8s 沒有使用訊息佇列,其延遲比 OpenStack 低。這其實是一個誤解,無論是 etcd 提供的 watch 介面,還是 K8s client 包中的 informer 機制,無不表明 K8s 是把 etcd 當做了訊息佇列,K8s 訊息的載體很多,譬如 K8s event。

從訊息佇列的角度來看,K8s 整體叢集架構各個角色如下:

  1. etcd 訊息路由器

  2. kube-apiserver etcd 生產者訊息代理和訊息廣播【或者成為次級訊息路由器、消費者代理】

  3. kubelet 訊息的生產者和消費者

  4. kube-controller-manager 訊息的消費者和生產者

  5. kube-scheduler 訊息的消費者和生產者

etcd 是推模式的訊息佇列。etcd 是 K8s 叢集的 KV 資料庫和訊息路由器,充當了 OpenStack 叢集中的 MySQL 和 MQ 兩個角色,這樣的實現貌似簡化了叢集的結構,但其實不然。在 large scale 規模 K8s 叢集中,一般經驗,首先都會使用一個單獨 etcd 叢集儲存 event 資料:把 KV 資料和一部分 MQ 資料物理隔離開,實現了 KV 和 MQ 角色的部分分離。 如 參考文件 2 中提到美團 “針對 etcd 的運營,通過拆分出獨立的 event 叢集降低主庫的壓力”。

當 K8s 叢集規模擴大時,etcd 承載著 KV 資料劇增、event 訊息暴增以及訊息寫放大的三種壓力。 為了證明所言非虛,特引用部分資料為佐證:

  1. etcd KV 資料量級在 100 萬以上;

  2. etcd event 資料量在 10 萬以上;

  3. etcd 讀流量壓力峰值在 30 萬 pqm 以上,其中讀 event 在 10k qpm 以上;

  4. etcd 寫流量壓力峰值在 20 萬 pqm 以上,其中寫 event 在 15k qpm 以上;

  5. etcd CPU 經常性飆升到 900% 以上;

  6. etcd 記憶體 RSS 在 60 GiB 以上;

  7. etcd 磁碟使用量可達 100 GiB 以上;

  8. etcd 自身的 goroutine 數量 9k 以上;

  9. etcd 使用的使用者態執行緒達 1.6k 以上;

  10. etcd gc 單次耗時常態下可達 15ms。

使用 Go 語言實現的 etcd 管理這些資料非常吃力,無論是 CPU、記憶體、gc、goroutine 數量還是執行緒使用量,基本上都接近 go runtime 管理能力極限:經常在 CPU profile 中觀測到 go runtime 和 gc 佔用資源超過 50% 以上。

螞蟻的 K8s 叢集在經歷高可用專案維護之前,當叢集規模突破 7 千節點規模時,曾出現如下效能瓶頸問題:

  1. etcd 出現大量的讀寫延遲,延遲甚至可達分鐘級;

  2. kube-apiserver 查詢 pods / nodes / configmap / crd 延時很高,導致 etcd oom;

  3. etcd list-all pods 時長可達 30 分鐘以上;

  4. 2020 年 etcd 叢集曾因 list-all 壓力被打垮導致的事故就達好幾起;

  5. 控制器無法及時感知資料變化,如出現 watch 資料延遲可達 30s 以上。

如果說這種狀況下的 etcd 叢集是在刀鋒上跳舞, 此時的整個 K8s 叢集就是一個活火山:稍不留神就有可能背個 P 級故障, 彼時的整個 K8s master 運維工作大概是整個螞蟻集團最危險的工種之一。

高可用策略

實現一個分散式系統高可用能力的提升,大概有如下手段:

  1. 提升自身穩定性與效能;

  2. 精細管理上游流量;

  3. 保證服務下游服務 SLO。

etcd 經過社群與各方使用者這麼多年的錘鍊,其自身的穩定性足夠。螞蟻人能做到的,無非是使出周扒皮的本事,提高叢集資源整體利用率,scale out 和 scale up 兩種技術手段雙管齊下,儘可能的提升其效能。

etcd 自身作為 K8s 的基石,其並無下游服務。如果說有,那也是其自身所使用的物理 node 環境了。下面分別從 etcd 叢集效能提升、請求流量管理等角度闡述我們在 etcd 層面所做出的高可用能力提升工作。

檔案系統升級

在山窩裡飛出金鳳凰,誠非易事。讓 etcd 跑的更快這件事,沒有什麼手段比提供一個高效能的機器更短平快地見效了。

1.使用 NVMe ssd

etcd 自身 = etcd 程式 + 其執行環境。早期 etcd 伺服器使用的磁碟是 SATA 盤,經過簡單地測試發現 etcd 讀磁碟速率非常慢,老闆豪橫地把機器鳥槍換炮 --- 升級到使用了 NVMe SSD 的 f53 規格的機器:etcd 使用 NVMe ssd 儲存 boltdb 資料後,隨機寫速率可提升到 70 MiB/s 以上。

參考文件 2 中提到美團 “基於高配的 SSD 物理機器部署可以達到日常 5 倍的高流量訪問”,可見提升硬體效能是大廠的首選,能折騰機器千萬別浪費人力。

2.使用 tmpfs

NVMe ssd 雖好,理論上其讀寫極限效能跟記憶體比還是差一個數量級。我們測試發現使用 tmpfs【未禁止 swap out】替換 NVMe ssd 後,etcd 在讀寫併發的情況下效能仍然能提升 20% 之多。考察 K8s 各種資料型別的特點後,考慮到 event 對資料的安全性要求不高但是對實時性要求較高的特點,我們毫不猶豫的把 event etcd 叢集執行在了 tmpfs 檔案系統之上,將 K8s 整體的效能提升了一個層次。

3.磁碟檔案系統

磁碟儲存介質升級後,儲存層面能夠進一步做的事情就是研究磁碟的檔案系統格式。目前 etcd 使用的底層檔案系統是 ext4 格式,其 block size 使用的是預設的 4 KiB。我們團隊曾對 etcd 進行單純的在單純寫並行壓測時發現,把檔案系統升級為 xfs,且 block size 為 16 KiB【在測試的 KV size 總和 10 KiB 條件下】時,etcd 的寫效能仍然有提升空間。

但在讀寫併發的情況下,磁碟本身的寫佇列幾乎毫無壓力,又由於 etcd 3.4 版本實現了並行快取讀,磁碟的讀壓力幾乎為零,這就意味著:繼續優化檔案系統對 etcd 的效能提升空間幾乎毫無幫助。自此以後單節點 etcd scale up 的關鍵就從磁碟轉移到了記憶體:優化其記憶體索引讀寫速度。

4.磁碟透明大頁

在現代作業系統的記憶體管理中,有 huge page 和 transparent huge page 兩種技術,不過一般使用者採用 transparent huge page 實現記憶體 page 的動態管理。在 etcd 執行環境,關閉 transparent huge page 功能,否則 RT 以及 QPS 等經常性的監控指標會經常性的出現很多毛刺,導致效能不平穩。

etcd 調參

MySQL 運維工程師常被人稱為 “調參工程師”,另一個有名的 KV 資料庫 RocksDB 也不遑多讓,二者可調整的引數之多到了令人髮指的地方:其關鍵就在於針對不同儲存和執行環境需要使用不同的引數,才能充分利用硬體的效能。etcd 隨不及之,但也不拉人後,預計以後其可調整引數也會越來越多。

etcd 自身也對外暴露了很多引數調整介面。除了阿里集團 K8s 團隊曾經做出的把 freelist 由 list 改進為 map 組織形式優化外,目前常規的 etcd 可調整引數如下:

  1. write batch

  2. compaction

1.write batch

像其他常規的 DB 一樣,etcd 磁碟提交資料時也採用了定時批量提交、非同步寫盤的方式提升吞吐,並通過記憶體快取的方式平衡其延時。具體的調整引數介面如下:

  1. batch write number 批量寫 KV 數目,預設值是 10k;

  2. batch write interval 批量寫事件間隔,預設值是 100 ms。

etcd batch 這兩個預設值在大規模 K8s 叢集下是不合適的,需要針對具體的執行環境調整之,避免導致記憶體使用 OOM。一般地規律是,叢集 node 數目越多,這兩個值就應該成比例減小。

2.compaction

etcd 自身由於支援事務和訊息通知,所以採用了 MVCC 機制儲存了一個 key 的多版本資料,etcd 使用定時的 compaction 機制回收這些過時資料。etcd 對外提供的壓縮任務引數如下:

  1. compaction interval 壓縮任務週期時長;

  2. compaction sleep interval 單次壓縮批次間隔時長,預設 10 ms;

  3. compaction batch limit 單次壓縮批次 KV 數目,預設 1000。

(1)壓縮任務週期

K8s 叢集的 etcd compaction 可以有兩種途徑進行 compaction:

  1. etcd 另外提供了 comapct 命令和 API 介面,K8s kube-apiserver 基於這個 API 介面也對外提供了 compact 週期引數;

  2. etcd 自身會週期性地執行 compaction;

  3. etcd 對外提供了自身週期性 compaction 引數調整介面,這個引數的取值範圍是 (0, 1 hour];

  4. 其意義是:etcd compaction 即只能開啟不能關閉,如果設定的週期時長大於 1 hour,則 etcd 會截斷為 1 hour。

螞蟻 K8s 團隊在經過測試和線下環境驗證後,目前的壓縮週期取值經驗是:

  1. 在 etcd 層面把 compaction 週期儘可能地拉長,如取值 1 hour,形同在 etcd 自身層面關閉 compaction,把 compaction interval 的精細調整權交給 K8s kube-apiserver;

  2. 在 K8s kube-apiserver 層面,根據線上叢集規模取值不同的 compaction interval。

之所以把 etcd compaction interval 精細調整權調整到 kube-apiserver 層面,是因為 etcd 是 KV 資料庫,不方便經常性地啟停進行測試,而 kube-apiserver 是 etcd 的快取,其資料是弱狀態資料,相對來說啟停比較方便,方便調參。至於 compaction interval 的取值,一條經驗是:叢集 node 越多 compaction interval 取值可以適當調大。

compaction 本質是一次寫動作,在大規模叢集中頻繁地執行 compaction 任務會影響叢集讀寫任務的延時,叢集規模越大,其延時影響越明顯,在 kube-apiserver 請求耗時監控上表現就是有頻繁出現地週期性的大毛刺。

更進一步,如果平臺上執行的任務有很明顯的波谷波峰特性,如每天的 8:30 am ~ 21:45 pm 是業務高峰期,其他時段是業務波峰期,那麼可以這樣執行 compaction 任務:

  1. 在 etcd 層面設定 compaction 週期是 1 hour;

  2. 在 kube-apiserver 層面設定 comapction 週期是 30 minutes;

  3. 在 etcd 運維平臺上啟動一個週期性任務:當前時間段在業務波谷期,則啟動一個 10 minutes 周

期的 compaction 任務。

其本質就是把 etcd compaction 任務交給 etcd 運維平臺,當發生電商大促銷等全天無波谷的特殊長週期時間段時,就可以在平臺上緊急關閉 compaction 任務,把 compaction 任務對正常的讀寫請求影響降低到最低。

(2)單次壓縮

即使是單次壓縮任務,etcd 也是分批執行的。因為 etcd 使用的儲存引擎 boltdb 的讀寫形式是多讀一寫:可以同時並行執行多個讀任務,但是同時刻只能執行一個寫任務。

為了防止單次 compaction 任務一直佔用 boltdb 的讀寫鎖,每次執行一批固定量【compaction batch limit】的磁碟 KV 壓縮任務後,etcd 會釋放讀寫鎖 sleep 一段時間【compaction sleep interval】。

在 v3.5 之前,compaction sleep interval 固定為 10 ms,在 v3.5 之後 etcd 已經把這個引數開放出來方便大規模 K8s 叢集進行調參。類似於 batch write 的 interval 和 number,單次 compaction 的 sleep interval 和 batch limit 也需要不同的叢集規模設定不同的引數,以保證 etcd 平穩執行和 kube-apiserver 的讀寫 RT 指標平穩無毛刺。

運維平臺

無論是 etcd 調參,還是升級其執行的檔案系統,都是通過 scale up 的手段提升 etcd 的能力。還有兩種 scale up 手段尚未使用:

  1. 通過壓測或者線上獲取 etcd 執行 profile,分析 etcd 流程的瓶頸,然後優化程式碼流程提升效能;

  2. 通過其他手段降低單節點 etcd 資料量。

通過程式碼流程優化 etcd 效能,可以根據 etcd 使用方的人力情況進行之,更長期的工作應該是緊跟社群,及時獲取其版本升級帶來的技術紅利。通過降低 etcd 資料規模來獲取 etcd 效能的提升則必須依賴 etcd 使用方自身的能力建設了。

我們曾對 etcd 的單節點 RT 與 QPS 效能與 KV 資料量的關係進行過 benchmark 測試,得到的結論是:當 KV 資料量增加時,其 RT 會隨之線性增加,其 QPS 吞吐則會指數級下降。這一步測試結果帶來的啟示之一即是:通過分析 etcd 中的資料組成、外部流量特徵以及資料訪問特點,儘可能地降低單 etcd 節點的資料規模。

目前螞蟻的 etcd 運維平臺具有如下資料分析功能:

  1. longest N KV --- 長度最長的 N 個 KV

  2. top N KV --- 段時間內訪問次數最多的 N 個 KV

  3. top N namespace --- KV 數目最多的 N 個 namespace

  4. verb + resoure --- 外部訪問的動作和資源統計

  5. 連線數 --- 每個 etcd 節點的長連線數目

  6. client 來源統計 --- 每個 etcd 節點的外部請求來源統計

  7. 冗餘資料分析 --- etcd 叢集中近期無外部訪問的 KV 分佈

根據資料分析結果,可以進行如下工作:

  1. 客戶限流

  2. 負載均衡

  3. 叢集拆分

  4. 冗餘資料刪除

  5. 業務流量精細分析

1.叢集拆分

前文提到,etcd 叢集效能提升的一個經典手段就是把 event 資料獨立拆分到一個獨立的 etcd 叢集,因為 event 資料是 K8s 叢集一中量級比較大、流動性很強、訪問量非常高的資料,拆分之後可以降低 etcd 的資料規模並減輕 etcd 單節點的外部客戶端流量。

一些經驗性的、常規性的 etcd 拆分手段有:

  1. pod/cm

  2. node/svc

  3. event, lease

這些資料拆分後,大概率能顯著提升 K8s 叢集的 RT 與 QPS,但是更進一步的資料拆分工作還是有必要的。依據資料分析平臺提供的熱資料【top N KV】量級以及外部客戶訪問【verb + resource】情況,進行精細分析後可以作為 etcd 叢集拆分工作的依據。

2.客戶資料分析

針對客戶資料的分析分為 longest N KV 分析、top N namespace。

一個顯然成立的事實是:單次讀寫訪問的 KV 資料越長,則 etcd 響應時間越長。通過獲取客戶寫入的 longest N KV 資料後,可以與平臺使用方研究其對平臺的使用方法是否合理,降低業務對 K8s 平臺的訪問流量壓力和 etcd 自身的儲存壓力。

一般地,K8s 平臺每個 namespace 都是分配給一個業務單獨使用。前面提到 K8s 可能因為 list-all 壓力導致被壓垮,這些資料訪問大部分情況下都是 namespace 級別的 list-all。從平臺獲取 top N namespace 後,重點監控這些資料量級比較大的業務的 list-all 長連線請求,在 kube-apiserver 層面對其採取限流措施,就可以基本上保證 K8s 叢集不會被這些長連線請求打垮,保證叢集的高可用。

3.冗餘資料分析

etcd 中不僅有熱資料,還有冷資料。這些冷資料雖然不會帶來外部流量訪問壓力,但是會導致 etcd 記憶體索引鎖粒度的增大,進而導致每次 etcd 訪問 RT 時延增加和整體 QPS 的下降。

近期通過分析某大規模【7k node 以上】 K8s 叢集 etcd 中的冗餘資料,發現某業務資料在 etcd 中儲存了大量資料,其資料量大卻一週內沒有訪問過一次,與業務方詢問後獲悉:業務方把 K8s 叢集的 etcd 當做其 crd 資料的冷備使用。與業務方溝通後把資料從 etcd 中遷移掉後,記憶體 key 數目立即下降 20% 左右,大部分 etcd KV RT P99 延時立即下降 50% ~ 60% 之多。

4.負載均衡

K8s 平臺運維人員一般都有這樣一條經驗:etcd 叢集如果發生了啟停,需要儘快對所有 K8s kube-apiserver 進行一次重啟,以保證 kube-apiserver 與 etcd 之間連線數的均衡。其原因有二:

  1. kube-apiserver 在啟動時可以通過隨機方式保證其與 etcd 叢集中某個節點建立連線,但 etcd 發生啟停後,kube-apiserver 與 etcd 之間的連線數並無規律,導致每個 etcd 節點承擔的客戶端壓力不均衡;

  2. kube-apiserver 與 etcd 連線數均衡時,其所有讀寫請求有 2/3 概率是經過 follower 轉發到 leader,保證整體 etcd 叢集負載的均衡,如果連線不均衡則叢集效能無法評估。

通過 etcd 運維平臺提供的每個 etcd 的連線負載壓力,可以實時獲取叢集連線的均衡性,進而決定運維介入的時機,保證 etcd 叢集整體的健康度。

其實最新的 etcd v3.5 版本已經提供了 etcd 客戶端和 etcd 節點之間的自動負載均衡功能,但這個版本才釋出沒多久,目前最新版本的 K8s 尚未支援這個版本,可以及時跟進 K8s 社群對這個版本的支援進度以及時獲取這一技術紅利,減輕平臺運維壓力。

未來之路

通過一年多的包括 kube-apiserver 和 etcd 在內的 K8s 高可用建設,目前 K8s 叢集已經穩定下來,一個顯著的特徵是半年內 K8s 叢集沒有發生過一次 P 級故障,但其高可用建設工作不可能停歇 --- 作為全球 K8s 規模化建設領導力象限的螞蟻集團正在挑戰 node 量級更大規模的 K8s 叢集,這一工作將推動 etcd 叢集建設能力的進一步提升。

前面提到的很多 etcd 能力提升工作都是圍繞其 scale up 能力提升展開的,這方面的能力還需要更深層次的加強:

  1. etcd 最新 feature 地及時跟進,及時把社群技術進步帶來的開源價值轉換為螞蟻 K8s 平臺上的客戶價值

2.及時跟進阿里集團在 etcd compact 演算法優化、etcd 單節點多 multiboltdb 的架構優化以及 kube-apiserver 的服務端資料壓縮等 etcd 優化工作【見參考文件 1】,對兄弟團隊的工作進行借鑑和反饋,協同作戰共同提升

  1. 跟進螞蟻自身 K8s 平臺上 etcd 的效能瓶頸,提出我們自己的解決方案,在提升我們平臺的技術價值的同時反哺開源

除了關注 etcd 單節點效能的提升,我們下一步的工作將圍繞分散式 etcd 叢集這一 scale out 方向展開。前面提到的 etcd 叢集拆分工作,其本質就是通過分散式 etcd 叢集的方式提升 etcd 叢集整體的效能:該叢集的資料劃分方式是依據 K8s 業務層面的資料型別進行的。

該工作可以進一步擴充為:不區分 KV 的業務意義,從單純的 KV 層面對把資料根據某種路由方式把資料寫入後端多 etcd 子叢集,實現 etcd 叢集整體的冷熱負載均衡。

分散式 etcd 叢集的實現有兩種方式:proxyless 和 proxy based:proxy based etcd 分散式叢集的請求鏈路是 client[kube-apiserver] -> proxy -> etcd server,而謂的 proxyless 分散式 etcd 叢集的請求鏈路是 client[kube-apiserver] -> etcd server。

proxy based etcd 分散式叢集的好處是可以直接基於 etcd 社群提供的 etcd proxy 進行開發,後期亦可回饋社群,實現其開源價值、技術價值和客戶價值的統一。但經過測試:按照測試發現,kube-apiserver 經過 proxy 向 etcd 發起讀寫請求後 RT 和 QPS 降低 20% ~ 25%。所以下一步的工作重點是開發 proxyless etcd 叢集。

目前的拆分後的 etcd 分散式叢集本質或者 67% 的概率是 proxy based 分散式叢集:kube-apiserver 的請求大概有三分之二的概率是經過 follower 轉發到 leader,此處的 follower 本質就是一個 proxy。如果 kube-apiserver 所有請求都是與 leader 直連後被處理,理論上當前的 K8s 叢集的 RT 和 QPS 就有 67% * 20% ≈ 13.4% 的效能收益。

proxyless etcd 分散式叢集的缺點是如果把 proxy 的路由邏輯放入 kube-apiserver 中,會造成 kube-apiserver 版本升級成本增加,但相比於至少 20% 【將來通過 etcd 叢集規模擴充這個收益肯定會更大】的收益,這個僅僅影響了 kube-apiserver 單個元件的版本升級的成本是值得的。

除了 multiple etcd clusters 的思路外,資料中介軟體團隊基於 OBKV 之上實現了 etcd V3 API ,算是另一種比較好的技術路線,頗類似於本文開頭黃東旭提到的在 tikv 之上 etcd V3 API 介面層,可以稱之為類 etcd 系統,目前相關工作也在推進中。

總之,隨著我們 K8s 規模越來越大,螞蟻集團 etcd 整體工作的重要性就日益凸顯。 如果說前期 etcd 的高可用建設之路是在泥濘小道上蹣跚前行,那麼以後的 etcd 高可用建設之路必是康莊大道 --- 道路越走越寬廣!

參看文件

參考文件 1:

www.kubernetes.org.cn/9284.html

參考文件 2:

tech.meituan.com/2020/08/13/…

作者簡介

於雨(github @AlexStocks),dubbogo 社群負責人,一個有十一年服務端基礎架構和中介軟體研發一線工作經驗的程式設計師。

陸續參與和改進過 Redis/Pika/Pika-Port/etcd/Muduo/Dubbo/dubbo-go/Sentinel-go 等知名專案,目前在螞蟻金服可信原生部螞蟻大規模 K8s 叢集排程團隊從事容器編排工作,參與維護全球規模最大的 Kubernetes 生產叢集之一,致力於打造規模化、金融級、可信的雲原生基礎設施。

歡迎對 Serverless 自動伸縮技術、自適應混合部署技術以及 Kata/Nanovisor 等安全容器技術感興趣的同行或者 2022 屆應屆畢業生加入我們。

聯絡郵箱 xiaoyun.maoxy@antgroup.com 或者 yuyu.zx@antgroup.com

本週推薦閱讀

更多文章請掃碼關注“金融級分散式架構”公眾號

相關文章