背景與挑戰
隨著騰訊自研上云及公有云用戶的迅速增長,一方面,騰訊云容器服務TKE服務數量和核數大幅增長, 另一方面我們提供的容器服務型別(TKE托管及獨立集群、EKS彈性集群、edge邊緣計算集群、mesh服務網格、serverless knative)也越來越豐富,各類容器服務型別背后的核心都是K8s,K8s核心的存盤etcd又統一由我們基于K8s構建的etcd平臺進行管理,基于它我們目前管理了千級etcd集群,背后支撐了萬級K8s集群,
在萬級K8s集群規模下的我們如何高效保障etcd集群的穩定性?
etcd集群的穩定性風險又來自哪里?
我們通過基于業務場景、歷史遺留問題、現網運營經驗等進行穩定性風險模型分析,風險主要來自舊TKE etcd架構設計不合理、etcd穩定性、etcd性能部分場景無法滿足業務、測驗用例覆寫不足、變更管理不嚴謹、監控是否全面覆寫、隱患點是否能自動巡檢發現、極端災難故障資料安全是否能保障,
前面所描述的etcd平臺已經從架構設計上、變更管理上、監控及巡檢、資料遷移、備份幾個方面程度解決了我們管理的各類容器服務的etcd可擴展性、可運維性、可觀測性以及資料安全性,因此本文將重點描述我們在萬級K8s場景下面臨的etcd內核穩定性及性能挑戰,比如:
- 資料不一致
- 記憶體泄露
- 死鎖
- 行程Crash
- 大包請求導致etcd OOM及丟包
- 較大資料量場景下啟動慢
- 鑒權及查詢key數量、查詢指定數量記錄介面性能較差

本文將簡易描述我們是如何發現、分析、復現、解決以上問題及挑戰,以及從以上程序中我們獲得了哪些經驗及教訓,并將之應用到我們的各類容器服務存盤穩定性保障中,
同時,我們將解決方案全部貢獻、回饋給etcd開源社區, 截止目前我們貢獻的30+ pr已全部合并到社區,騰訊云TKE etcd團隊是etcd社區2020年上半年最活躍的貢獻團隊之一, 為etcd的發展貢獻我們的一點力量, 在這程序中特別感謝社區AWS、Google、Ali等maintainer的支持與幫助,
穩定性優化案例剖析
從GitLab誤刪主庫丟失部分資料到GitHub資料不一致導致中斷24小時,再到號稱"不沉航母"的AWS S3故障數小時等,無一例外都是存盤服務,穩定性對于一個存盤服務、乃至一個公司的口碑而言至關重要,它決定著一個產品生與死,穩定性優化案例我們將從資料不一致的嚴重性、兩個etcd資料不一致的bug、lease記憶體泄露、mvcc 死鎖、wal crash方面闡述,我們是如何發現、分析、復現、解決以上case,并分享我們從每個case中的獲得的識訓和反思,從中汲取經驗,防患于未然,
資料不一致(Data Inconsistency)
談到資料不一致導致的大故障,就不得不詳細提下GitHub在18年一次因網路設備的例行維護作業導致的美國東海岸網路中心與東海岸主要資料中心之間的連接斷開,雖然網路的連通性在43秒內得以恢復,但是短暫的中斷引發了一系列事件,最終導致GitHub 24小時11分鐘的服務降級,部分功能不可用,
GitHub使用了大量的MySQL集群存盤GitHub的meta data,如issue、pr、page等等,同時做了東西海岸跨城級別的容災,故障核心原因是網路例外時GitHub的MySQL仲裁服務Orchestrator進行了故障轉移,將寫入資料定向到美國西海岸的MySQL集群(故障前primary在東海岸),然而美國東海岸的MySQL包含一小段寫入,尚未復制到美國西海岸集群,同時故障轉移后由于兩個資料中心的集群現在都包含另一個資料中心中不存在的寫入,因此又無法安全地將主資料庫故障轉移回美國東海岸,
最終, 為了保證保證用戶資料不丟失,GitHub不得不以24小時的服務降級為代價來修復資料一致性,
資料不一致的故障嚴重性不言而喻,然而etcd是基于raft協議實作的分布式高可靠存盤系統,我們也并未做跨城容災,按理資料不一致這種看起來高大上bug我們是很難遇到的,然而夢想是美好的,現實是殘酷的,我們不僅遇到了不可思議的資料不一致bug, 還一踩就是兩個,一個是重啟etcd有較低的概率觸發,一個是升級etcd版本時如果開啟了鑒權,在K8s場景下較大概率觸發,在詳細討論這兩個bug前,我們先看看在K8s場景下etcd資料不一致會導致哪些問題呢?
- 資料不一致最恐怖之處在于client寫入是成功的,但可能在部分節點讀取到慷訓者是舊資料,client無法感知到寫入在部分節點是失敗的和可能讀到舊資料
- 讀到空可能會導致業務Node消失、Pod消失、Node上Service路由規則消失,一般場景下,只會影響用戶變更的服務
- 讀到老資料會導致業務變更不生效,如服務擴縮容、Service rs替換、變更鏡像例外等待,一般場景下,只會影響用戶變更的服務
- 在etcd平臺遷移場景下,client無法感知到寫入失敗,若校驗資料一致性也無例外時(校驗時連接到了正常節點),會導致遷移后整個集群全面故障(apiserver連接到了例外節點),用戶的Node、部署的服務、lb等會被全部洗掉,嚴重影響用戶業務
首先第一個不一致bug是重啟etcd程序中遇到的,人工嘗試復現多次皆失敗,分析、定位、復現、解決這個bug之路幾經波折,程序很有趣并充滿挑戰,最終通過我對關鍵點增加debug日志,撰寫chaos monkey模擬各種例外場景、邊界條件,實作復現成功,最后的真兇竟然是一個授權介面在重啟后重放導致鑒權版本號不一致,然后放大導致多版本資料庫不一致, 部分節點無法寫入新資料, 影響所有v3版本的3年之久bug,
隨后我們提交若干個相關pr到社區, 并全部合并了, 最新的etcd v3.4.9[1],v3.3.22[2]已修復此問題, 同時google的jingyih也已經提K8s issue和pr[3]將K8s 1.19的etcd client及server版本升級到最新的v3.4.9,此bug詳細可參考超凡同學寫的文章三年之久的 etcd3 資料不一致 bug 分析,

第二個不一致bug是在升級etcd程序中遇到的,因etcd缺少關鍵的錯誤日志,故障現場有效資訊不多,定位較困難,只能通過分析代碼和復現解決,然而人工嘗試復現多次皆失敗,于是我們通過chaos monkey模擬client行為場景,將測驗環境所有K8s集群的etcd分配請求調度到我們復現集群,以及對比3.2與3.3版本差異,在可疑點如lease和txn模塊增加大量的關鍵日志,并對etcd apply request失敗場景列印錯誤日志,
通過以上措施,我們比較快就復現成功了, 最終通過代碼和日志發現是3.2版本與3.3版本在revoke lease權限上出現了差異,3.2無權限,3.3需要寫權限,當lease過期的時候,如果leader是3.2,那么請求在3.3節點就會因無權限導致失敗,進而導致key數量不一致,mvcc版本號不一致,導致txn事務部分場景執行失敗等,最新的3.2分支也已合并我們提交的修復方案,同時我們增加了etcd核心程序失敗的錯誤日志以提高資料不一致問題定位效率,完善了升級檔案,詳細說明了lease會在此場景下引起資料不一致性,避免大家再次采坑,

- 從這兩個資料不一致bug中我們獲得了以下識訓和最佳實踐:
- 演算法理論資料一致性,不代表整體服務實作能保證資料一致性,目前業界對于這種基于日志復制狀態機實作的分布式存盤系統,沒有一個核心的機制能保證raft、wal、mvcc、snapshot等模塊協作不出問題,raft只能保證日志狀態機的一致性,不能保證應用層去執行這些日志對應的command都會成功
- etcd版本升級存在一定的風險,需要仔細review代碼評估是否存在不兼容的特性,如若存在是否影響鑒權版本號及mvcc版本號,若影響則升級程序中可能會導致資料不一致性,同時一定要灰度變更現網集群
- 對所有etcd集群增加了一致性巡檢告警,如revision差異監控、key數量差異監控等
- 定時對etcd集群資料進行備份,再小概率的故障,根據墨菲定律都可能會發生,即便etcd本身雖具備完備的自動化測驗(單元測驗、集成測驗、e2e測驗、故障注入測驗等),但測驗用例仍有不少場景無法覆寫,我們需要為最壞的場景做準備(如3個節點wal、snap、db檔案同時損壞),降低極端情況下的損失, 做到可用備份資料快速恢復
- etcd v3.4.4后的集群灰度開啟data corruption檢測功能,當集群出現不一致時,拒絕集群寫入、讀取,及時止損,控制不一致的資料范圍
- 繼續完善我們的chaos monkey和使用etcd本身的故障注入測驗框架functional,以協助我們驗證、壓測新版本穩定性(長時間持續跑),復現隱藏極深的bug, 降低線上采坑的概率
記憶體泄露(OOM)
眾所周知etcd是golang寫的,而golang自帶垃圾回識訓制也會記憶體泄露嗎?首先我們得搞清楚golang垃圾回收的原理,它是通過后臺運行一個守護執行緒,監控各個物件的狀態,識別并且丟棄不再使用的物件來釋放和重用資源,若你遲遲未釋放物件,golang垃圾回收不是萬能的,不泄露才怪,比如以下場景會導致記憶體泄露:
- goroutine泄露
- deferring function calls(如for回圈里面未使用匿名函式及時呼叫defer釋放資源,而是整個for回圈結束才呼叫)
- 獲取string/slice中的一段導致長string/slice未釋放(會共享相同的底層記憶體塊)
- 應用記憶體資料結構管理不周導致記憶體泄露(如為及時清理過期、無效的資料)
接下來看看我們遇到的這個etcd記憶體泄露屬于哪種情況呢?事情起源于3月末的一個周末起床后收到現網3.4集群大量記憶體超過安全閾值告警,立刻排查了下發現以下現象:
- QPS及流量監控顯示都較低,因此排除高負載及慢查詢因素
- 一個集群3個節點只有兩個follower節點出現例外,leader 4g,follower節點高達23G
- goroutine、fd等資源未出現泄漏
- go runtime memstats指標顯示各個節點申請的記憶體是一致的,但是follower節點go_memstats_heap_release_bytes遠低于leader節點,說明某資料結構可能長期未釋放
- 生產集群默認關閉了pprof,開啟pprof,等待復現, 并在社區上搜索釋放有類似案例, 結果發現有多個用戶1月份就報了,沒引起社區重視,使用場景和現象跟我們一樣
- 通過社區的heap堆疊快速定位到了是由于etcd通過一個heap堆來管理lease的狀態,當lease過期時需要從堆中洗掉,但是follower節點卻無此操作,因此導致follower記憶體泄露, 影響所有3.4版本,
- 問題分析清楚后,我提交的修復方案是follower節點不需要維護lease heap,當leader發生選舉時確保新的follower節點能重建lease heap,老的leader節點則清空lease heap.
此記憶體泄露bug屬于記憶體資料結構管理不周導致的,問題修復后,etcd社區立即發布了新的版本(v3.4.6+)以及K8s都立即進行了etcd版本更新,


從這個記憶體泄露bug中我們獲得了以下識訓和最佳實踐:
- 持續關注社區issue和pr, 別人今天的問題很可能我們明天就會遇到
- etcd本身測驗無法覆寫此類需要一定時間運行的才能觸發的資源泄露bug,我們內部需要加強此類場景的測驗與壓測
- 持續完善、豐富etcd平臺的各類監控告警,機器留足足夠的記憶體buffer以扛各種意外的因素,
存盤層死鎖(Mvcc Deadlock)
-
死鎖是指兩個或兩個以上的goroutine的執行程序中,由于競爭資源相互等待(一般是鎖)或由于彼此通信(chan引起)而造成的一種程式卡死現象,無法對外提供服務,deadlock問題因為往往是在并發狀態下資源競爭導致的, 一般比較難定位和復現, 死鎖的性質決定著我們必須保留好分析現場,否則分析、復現及其困難,
那么我們是如何發現解決這個deadlock bug呢?問題起源于內部團隊在壓測etcd集群時,發現一個節點突然故障了,而且一直無法恢復,無法正常獲取key數等資訊,收到反饋后,我通過分析卡住的etcd行程和查看監控,得到以下結論:
- 不經過raft及mvcc模塊的rpc請求如member list可以正常回傳結果,而經過的rpc請求全部context timeout
- etcd health健康監測回傳503,503的報錯邏輯也是經過了raft及mvcc
- 通過tcpdump和netstat排除raft網路模塊例外,可疑目標縮小到mvcc
- 分析日志發現卡住的時候因資料落后leader較多,接收了一個資料快照,然后執行更新快照的時候卡住了,沒有輸出快照加載完畢的日志,同時確認日志未丟失
- 排查快照加載的代碼,鎖定幾個可疑的鎖和相關goroutine,準備獲取卡住的goroutine堆疊
- 通過kill或pprof獲取goroutine堆疊,根據goroutine卡住的時間和相關可疑點的代碼邏輯,成功找到兩個相互競爭資源的goroutine,其中一個正是執行快照加載,重建db的主goroutine,它獲取了一把mvcc鎖等待所有異步任務結束,而另外一個goroutine則是執行歷史key壓縮任務,當它收到stop的信號后,立刻退出,呼叫一個compactBarrier邏輯,而這個邏輯又恰恰需要獲取mvcc鎖,因此出現死鎖,堆疊如下,

這個bug也隱藏了很久,影響所有etcd3版本,在集群中寫入量較大,某落后的較多的節點執行了快照重建,同時此時又恰恰在做歷史版本壓縮,那就會觸發,我提交的修復PR目前也已經合并到3.3和3.4分支中,新的版本已經發布(v3.3.21+/v3.4.8+),

從這個死鎖bug中我們獲得了以下識訓和最佳實踐:
- 多并發場景的組合的etcd自動化測驗用例覆寫不到,也較難構建,因此也容易出bug, 是否還有其他類似場景存在同樣的問題?需要參與社區一起繼續提高etcd測驗覆寫率(etcd之前官方博客介紹一大半代碼已經是測驗代碼),才能避免此類問題,
- 監控雖然能及時發現例外節點宕機,但是死鎖這種場景之前我們不會自動重啟etcd,因此需要完善我們的健康探測機制(比如curl /health來判斷服務是否正常),出現死鎖時能夠保留堆疊、自動重啟恢復服務,
- 對于讀請求較高的場景,需評估3節點集群在一節點宕機后,剩余兩節點提供的QPS容量是否能夠支持業務,若不夠則考慮5節點集群,
Wal crash(Panic)
panic是指出現嚴重運行時和業務邏輯錯誤,導致整個行程退出,panic對于我們而言并不陌生,我們在現網遇到過幾次,最早遭遇的不穩定性因素就是集群運行程序中panic了,
雖說我們3節點的etcd集群是可以容忍一個節點故障,但是crash瞬間對用戶依然有影響,甚至出現集群撥測連接失敗,
我們遇到的第一個crash bug,是發現集群鏈接數較多的時候有一定的概率出現crash, 然后根據堆疊查看社區已有人報grpc crash(issue)[4], 原因是etcd依賴的組件grpc-go出現了grpc crash(pr)[5],而最近我們遇到的crash bug[6]是v3.4.8/v3.3.21新版本發布引起的,這個版本跟我們有很大關系,我們貢獻了3個PR到這個版本,占了一大半以上, 那么這個crash bug是如何產生以及復現呢?會不會是我們自己的鍋呢?
- 首先crash報錯是walpb: crc mismatch, 而我們并未提交代碼修改wal相關邏輯,排除自己的鍋,
- 其次通過review新版本pr, 目標鎖定到google一位大佬在修復一個wal在寫入成功后,而snapshot寫入失敗導致的crash bug的時候引入的.
- 但是具體是怎么引入的?pr中包含多個測驗用例驗證新加邏輯,本地創建空集群和使用存量集群(資料比較小)也無法復現.
- 錯誤日志資訊太少,導致無法確定是哪個函式報的錯,因此首先還是加日志,對各個可疑點增加錯誤日志后,在我們測驗集群隨便找了個老節點替換版本,然后很容易就復現了,并確定是新加的驗證快照檔案合法性的鍋,那么它為什么會出現crc mismatch呢? 首先我們來簡單了解下wal檔案,
- etcd任何經過raft的模塊的請求在寫入etcd mvcc db前都會通過wal檔案持久化,若行程在apply command程序中出現被殺等例外,重啟時可通過wal檔案重放將資料補齊,避免資料丟失,wal檔案包含各種請求命令如成員變化資訊、涉及key的各個操作等,為了保證資料完整性、未損壞,wal每條記錄都會計算其的crc32,寫入wal檔案,重啟后決議wal檔案程序中,會校驗記錄的完整性,如果資料出現損壞或crc32計算演算法出現變化則會出現crc32 mismatch.
- 硬碟及檔案系統并未出現例外,排除了資料損壞,經過深入排查crc32演算法的計算,發現是新增邏輯未處理crc32型別的資料記錄,它會影響crc32演算法的值,導致出現差異,而且只有在當etcd集群創建產生后的第一個wal檔案被回收才會觸發,因此對存量運行一段時間的集群,100%復現,
- 解決方案就是通過增加crc32演算法的處理邏輯以及增加單元測驗覆寫wal檔案被回收的場景,社區已合并并發布了新的3.4和3.3版本(v3.4.9/v3.3.22).

雖然這個bug是社區用戶反饋的,但從這個crash bug中我們獲得了以下識訓和最佳實踐:
- 單元測驗用例非常有價值,然而撰寫完備的單元測驗用例并不容易,需要考慮各類場景,
- etcd社區對存量集群升級、各版本之間兼容性測驗用例幾乎是0,需要大家一起來為其舔磚加瓦,讓測驗用例覆寫更多場景,
- 新版本上線內部流程標準化、自動化, 如測驗環境壓測、混沌測驗、不同版本性能對比、優先在非核心場景使用(如event)、灰度上線等流程必不可少,
配額及限速(Quota&QoS)
etcd面對一些大資料量的查詢(expensive read)和寫入操作時(expensive write),如全key遍歷(full keyspace fetch)、大量event查詢, list all Pod, configmap寫入等會消耗大量的cpu、記憶體、帶寬資源,極其容易導致過載,乃至雪崩,
然而,etcd目前只有一個極其簡單的限速保護,當etcd的commited index大于applied index的閾值大于5000時,會拒絕一切請求,回傳Too Many Request,其缺陷很明顯,無法精確的對expensive read/write進行限速,無法有效防止集群過載不可用,
為了解決以上挑戰,避免集群過載目前我們通過以下方案來保障集群穩定性:
- 基于K8s apiserver上層限速能力,如apiserver默認寫100/s,讀200/s
- 基于K8s resource quota控制不合理的Pod/configmap/crd數
- 基于K8s controller-manager的-terminated-Pod-gc-threshold引數控制無效Pod數量(此引數默認值高達12500,有很大優化空間)
- 基于K8s的apiserver各類資源可獨立的存盤的特性, 將event/configmap以及其他核心資料分別使用不同的etcd集群,在提高存盤性能的同時,減少核心主etcd故障因素
- 基于event admission webhook對讀寫event的apiserver請求進行速率控制
- 基于不同業務情況,靈活調整event-ttl時間,盡量減少event數
- 基于etcd開發QoS特性,目前也已經向社區提交了初步設計方案,支持基于多種物件型別設定QoS規則(如按grpcMethod、grpcMethod+請求key前綴路徑、traffic、cpu-intensive、latency)
- 通過多維度的集群告警(etcd集群lb及節點本身出入流量告警、記憶體告警、精細化到每個K8s集群的資源容量例外增長告警、集群資源讀寫QPS例外增長告警)來提前防范、規避可能出現的集群穩定性問題
多維度的集群告警在我們的etcd穩定性保障中發揮了重要作用,多次幫助我們發現用戶和我們自身集群組件問題,用戶問題如內部某K8s平臺之前出現bug, 寫入大量的集群CRD資源和client讀寫CRD QPS明顯偏高,我們自身組件問題如某舊日志組件,當集群規模增大后,因日志組件不合理的頻繁呼叫list Pod,導致etcd集群流量高達3Gbps, 同時apiserver本身也出現5XX錯誤,
雖然通過以上措施,我們能極大的減少因expensive read導致的穩定性問題,然而從線上實踐效果看,目前我們仍然比較依賴集群告警幫助我們定位一些例外client呼叫行為,無法自動化的對例外client的進行精準智能限速,,etcd層因無法區分是哪個client呼叫,如果在etcd側限速會誤殺正常client的請求, 因此依賴apiserver精細化的限速功能實作,社區目前已在1.18中引入了一個API Priority and Fairness[7],目前是alpha版本,期待此特性早日穩定,
性能優化案例剖析
etcd讀寫性能決定著我們能支撐多大規模的集群、多少client并發呼叫,啟動耗時決定著我們當重啟一個節點或因落后leader太多,收到leader的快照重建時,它重新提供服務需要多久?性能優化案例剖析我們將從啟動耗時減少一半、密碼鑒權性能提升12倍、查詢key數量性能提升3倍等來簡單介紹下如何對etcd進行性能優化,
啟動耗時及查詢key數量、查詢指定記錄數性能優化
當db size達到4g時,key數量百萬級別時,發現重啟一個集群耗時竟然高達5分鐘, key數量查詢也是超時,調整超時時間后,發現高達21秒,記憶體暴漲6G,同時查詢只回傳有限的記錄數的場景(如業務使用etcd grpc-proxy來減少watch數,etcd grpc proxy在默認創建watch的時候,會發起對watch路徑的一次limit讀查詢),依然耗時很高且有巨大的記憶體開銷,于是周末空閑的時候我對這幾個問題進行了深入調查分析,啟動耗時到底花在了哪里?是否有優化空間?查詢key數量為何如何耗時,記憶體開銷如此之大?
帶著這些問題對原始碼進行了深入分析和定位,首先來看查詢key數和查詢只回傳指定記錄數的耗時和記憶體開銷極大的問題,分析結論如下:
- 查詢key數量時etcd之前實作是遍歷整個記憶體btree,把key對應的revision存放在slice陣列里面
- 問題就在于key數量較多時,slice擴容涉及到資料拷貝,以及slice也需要大量的記憶體開銷
- 因此優化方案是新增一個CountRevision來統計key的數量即可,不需要使用slice,此方案優化后性能從21s降低到了7s,同時無任何記憶體開銷
- 對于查詢指定記錄資料耗時和記憶體開銷非常大的問題,通過分析發現是limit記錄數并未下推到索引層,通過將查詢limit引數下推到索引層,大資料場景下limit查詢性能提升百倍,同時無額外的記憶體開銷,


再看啟動耗時問題過高的問題,通過對啟動耗時各階段增加日志,得到以下結論:
- 啟動的時候機器上的cpu資源etcd行程未能充分利用
- 9%耗時在打開后端db時,如將整個db檔案mmap到記憶體
- 91%耗時在重建記憶體索引btree上,當etcd收到一個請求Get Key時,請求被層層傳遞到了mvcc層后,它首先需要從記憶體索引btree中查找key對應的版本號,隨后從boltdb里面根據版本號查出對應的value, 然后回傳給client. 重建記憶體索引btree數的時候,恰恰是相反的流程,遍歷boltdb,從版本號0到最大版本號不斷遍歷,從value里面決議出對應的key、revision等資訊,重建btree,因為這個是個串行操作,所以操作及其耗時
- 嘗試將串行構建btree優化成高并發構建,盡量把所有核計算力利用起來,編譯新版本測驗后發現效果甚微,于是編譯新版本列印重建記憶體索引各階段的詳細耗時分析,結果發現瓶頸在記憶體btree的插入上,而這個插入擁有一個全域鎖,因此幾乎無優化空間
- 繼續分析91%耗時發現重建記憶體索引竟然被呼叫了兩次,第一處是為了獲取一個mvcc的關鍵的consistent index變數,它是用來保證etcd命令不會被重復執行的關鍵資料結構,而我們前面提到的一個資料不一致bug恰好也是跟consistent index有密切關系,
- consistent index實作不合理,封裝在mvcc層,因此我前面提了一個pr將此特性重構,做為了一個獨立的包,提供各類方法給etcdserver、mvcc、auth、lease等模塊呼叫,
- 特性重構后的consistent index在啟動的時候就不再需要通過重建記憶體索引數等邏輯來獲取了,優化成呼叫cindex包的方法快速獲取到consistent index,就將整個耗時從5min從縮短到2分30秒左右,因此優化同時依賴的consistent index特性重構,改動較大暫未backport到3.4/3.3分支,在未來3.5版本中、資料量較大時可以享受到啟動耗時的顯著提升,


密碼鑒權性能提升12倍
某內部業務服務一直跑的好好的,某天client略微增多后,突然現網etcd集群出現大量超時,各種折騰,切換云盤型別、切換部署環境、調整引數都不發揮作用,收到求助后,索要metrics和日志后,經過一番排查后,得到以下結論:
- 現象的確很詭異,db延時相關指標顯示沒任何例外,日志無任何有效資訊
- 業務反饋大量讀請求超時,甚至可以通過etcdctl客戶端工具簡單復現,可是metric對應的讀請求相關指標數竟然是0
- 引導用戶開啟trace日志和metrics開啟extensive模式,開啟后發現無任何trace日志,然而開啟extensive后,我發現耗時竟然全部花在了Authenticate介面,業務反饋是通過密碼鑒權,而不是基于證書的鑒權
- 嘗試讓業務同學短暫關閉鑒權測驗業務是否恢復,業務同學找了一個節點關閉鑒權后,此節點立刻恢復了正常,于是選擇臨時通過關閉鑒權來恢復現網業務
- 那鑒權為什么耗時這么慢?我們對可疑之處增加了日志,列印了鑒權各個步驟的耗時,結果發現是在等待鎖的程序中出現了超時,而這個鎖為什么耗時這么久呢?排查發現是因為加鎖程序中會呼叫bcrpt加密函式計算密碼hash值,每次耗費60ms左右,數百并發下等待此鎖的最高耗時高達5s+,
- 于是我們撰寫新版本將鎖的范圍減少,降低持鎖阻塞時間,用戶使用新版本后,開啟鑒權后,業務不再超時,恢復正常,
- 隨后我們將修復方案提交給了社區,并撰寫了壓測工具,測驗提升后的性能高達近12倍(8核32G機器,從18/s提升到202/s),但是依然是比較慢,主要是鑒權程序中涉及密碼校驗計算, 社區上也有用戶反饋密碼鑒權慢問題, 目前最新的v3.4.9版本已經包含此優化, 同時可以通過調整bcrpt-cost引數來進一步提升性能,

本文簡單描述了我們在管理萬級K8s集群和其他業務程序中遇到的etcd穩定性和性能挑戰,以及我們是如何定位、分析、復現、解決這些挑戰,并將解決方案貢獻給社區,
同時,詳細描述了我們從這些挑戰中識訓了哪些寶貴的經驗和教訓,并將之應用到后續的etcd穩定性保障中,以支持更大規模的單集群和總集群數,
最后我們面對萬級K8s集群數, 千級的etcd集群數, 10幾個版本分布,其中不少低版本包含重要的潛在可能觸發的嚴重bug, 我們還需要投入大量作業不斷優化我們的etcd平臺,使其更智能、變更更加高效、安全、可控(如支持自動化、可控的集群升級等), 同時資料安全也至關重要,目前騰訊云TKE托管集群我們已經全面備份,獨立集群的用戶后續將引導通過應用市場的etcd備份插件開啟定時備份到騰訊云物件存盤COS上,
未來我們將繼續緊密融入etcd的社區,為etcd社區的發展貢獻我們的力量,與社區一塊提升etcd的各個功能,
參考資料
[1]v3.4.9: https://github.com/etcd-io/etcd/releases/tag/v3.4.9
[2]v3.3.22: https://github.com/etcd-io/etcd/releases/tag/v3.3.22
[3]K8s issue和pr: https://github.com/kubernetes/kubernetes/issues/91266
[4]grpc crash(issue): https://github.com/etcd-io/etcd/issues/9956
[5]grpc crash(pr): https://github.com/grpc/grpc-go/pull/2695
[6]crash bug : https://github.com/etcd-io/etcd/issues/11918
[7]API Priority and Fairness: https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/20190228-priority-and-fairness.md
【騰訊云原生】云說新品、云研新術、云游新活、云賞資訊,掃碼關注同名公眾號,及時獲取更多干貨!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/698.html
標籤:其他

