万级K8s集群背后etcd稳定性及性能优化实践
作者:唐聰, 騰訊 CSIG 后臺開發(fā)工程師
本文旨在幫助大家了解 etcd集群場景下穩(wěn)定性與性能優(yōu)化經(jīng)驗引的容量,避免給后面留坑。
背景與挑戰(zhàn)
隨著騰訊自研上云及公有云用戶的迅速增長,一方面,騰訊云容器服務(wù)TKE服務(wù)數(shù)量和核數(shù)大幅增長, 另一方面我們提供的容器服務(wù)類型(TKE托管及獨(dú)立集群、EKS彈性集群、edge邊緣計算集群、mesh服務(wù)網(wǎng)格、serverless knative)也越來越豐富。各類容器服務(wù)類型背后的核心都是K8s,K8s核心的存儲etcd又統(tǒng)一由我們基于K8s構(gòu)建的etcd平臺進(jìn)行管理。基于它我們目前管理了千級etcd集群,背后支撐了萬級K8s集群。
在萬級K8s集群規(guī)模下的我們?nèi)绾胃咝ПU蟚tcd集群的穩(wěn)定性??
etcd集群的穩(wěn)定性風(fēng)險又來自哪里?
我們通過基于業(yè)務(wù)場景、歷史遺留問題、現(xiàn)網(wǎng)運(yùn)營經(jīng)驗等進(jìn)行穩(wěn)定性風(fēng)險模型分析,風(fēng)險主要來自舊TKE etcd架構(gòu)設(shè)計不合理、etcd穩(wěn)定性、etcd性能部分場景無法滿足業(yè)務(wù)、測試用例覆蓋不足、變更管理不嚴(yán)謹(jǐn)、監(jiān)控是否全面覆蓋、隱患點(diǎn)是否能自動巡檢發(fā)現(xiàn)、極端災(zāi)難故障數(shù)據(jù)安全是否能保障。
前面所描述的etcd平臺已經(jīng)從架構(gòu)設(shè)計上、變更管理上、監(jiān)控及巡檢、數(shù)據(jù)遷移、備份幾個方面程度解決了我們管理的各類容器服務(wù)的etcd可擴(kuò)展性、可運(yùn)維性、可觀測性以及數(shù)據(jù)安全性,因此本文將重點(diǎn)描述我們在萬級K8s場景下面臨的etcd內(nèi)核穩(wěn)定性及性能挑戰(zhàn),比如:
數(shù)據(jù)不一致
內(nèi)存泄露
死鎖
進(jìn)程Crash
大包請求導(dǎo)致etcd OOM及丟包
較大數(shù)據(jù)量場景下啟動慢
鑒權(quán)及查詢key數(shù)量、查詢指定數(shù)量記錄接口性能較差
本文將簡易描述我們是如何發(fā)現(xiàn)、分析、復(fù)現(xiàn)、解決以上問題及挑戰(zhàn),以及從以上過程中我們獲得了哪些經(jīng)驗及教訓(xùn),并將之應(yīng)用到我們的各類容器服務(wù)存儲穩(wěn)定性保障中。
同時,我們將解決方案全部貢獻(xiàn)、回饋給etcd開源社區(qū), 截止目前我們貢獻(xiàn)的30+ pr已全部合并到社區(qū)。騰訊云TKE etcd團(tuán)隊是etcd社區(qū)2020年上半年最活躍的貢獻(xiàn)團(tuán)隊之一, 為etcd的發(fā)展貢獻(xiàn)我們的一點(diǎn)力量, 在這過程中特別感謝社區(qū)AWS、Google、Ali等maintainer的支持與幫助。
穩(wěn)定性優(yōu)化案例剖析
從GitLab誤刪主庫丟失部分?jǐn)?shù)據(jù)到GitHub數(shù)據(jù)不一致導(dǎo)致中斷24小時,再到號稱"不沉航母"的AWS S3故障數(shù)小時等,無一例外都是存儲服務(wù)。穩(wěn)定性對于一個存儲服務(wù)、乃至一個公司的口碑而言至關(guān)重要,它決定著一個產(chǎn)品生與死。穩(wěn)定性優(yōu)化案例我們將從數(shù)據(jù)不一致的嚴(yán)重性、兩個etcd數(shù)據(jù)不一致的bug、lease內(nèi)存泄露、mvcc 死鎖、wal crash方面闡述,我們是如何發(fā)現(xiàn)、分析、復(fù)現(xiàn)、解決以上case,并分享我們從每個case中的獲得的收獲和反思,從中汲取經(jīng)驗,防患于未然。
數(shù)據(jù)不一致(Data Inconsistency)
談到數(shù)據(jù)不一致導(dǎo)致的大故障,就不得不詳細(xì)提下GitHub在18年一次因網(wǎng)絡(luò)設(shè)備的例行維護(hù)工作導(dǎo)致的美國東海岸網(wǎng)絡(luò)中心與東海岸主要數(shù)據(jù)中心之間的連接斷開。雖然網(wǎng)絡(luò)的連通性在43秒內(nèi)得以恢復(fù),但是短暫的中斷引發(fā)了一系列事件,最終導(dǎo)致GitHub 24小時11分鐘的服務(wù)降級,部分功能不可用。
GitHub使用了大量的MySQL集群存儲GitHub的meta data,如issue、pr、page等等,同時做了東西海岸跨城級別的容災(zāi)。故障核心原因是網(wǎng)絡(luò)異常時GitHub的MySQL仲裁服務(wù)Orchestrator進(jìn)行了故障轉(zhuǎn)移,將寫入數(shù)據(jù)定向到美國西海岸的MySQL集群(故障前primary在東海岸),然而美國東海岸的MySQL包含一小段寫入,尚未復(fù)制到美國西海岸集群,同時故障轉(zhuǎn)移后由于兩個數(shù)據(jù)中心的集群現(xiàn)在都包含另一個數(shù)據(jù)中心中不存在的寫入,因此又無法安全地將主數(shù)據(jù)庫故障轉(zhuǎn)移回美國東海岸。
最終, 為了保證保證用戶數(shù)據(jù)不丟失,GitHub不得不以24小時的服務(wù)降級為代價來修復(fù)數(shù)據(jù)一致性。
數(shù)據(jù)不一致的故障嚴(yán)重性不言而喻,然而etcd是基于raft協(xié)議實(shí)現(xiàn)的分布式高可靠存儲系統(tǒng),我們也并未做跨城容災(zāi),按理數(shù)據(jù)不一致這種看起來高大上bug我們是很難遇到的。然而夢想是美好的,現(xiàn)實(shí)是殘酷的,我們不僅遇到了不可思議的數(shù)據(jù)不一致bug, 還一踩就是兩個,一個是重啟etcd有較低的概率觸發(fā),一個是升級etcd版本時如果開啟了鑒權(quán),在K8s場景下較大概率觸發(fā)。在詳細(xì)討論這兩個bug前,我們先看看在K8s場景下etcd數(shù)據(jù)不一致會導(dǎo)致哪些問題呢?
數(shù)據(jù)不一致最恐怖之處在于client寫入是成功的,但可能在部分節(jié)點(diǎn)讀取到空或者是舊數(shù)據(jù),client無法感知到寫入在部分節(jié)點(diǎn)是失敗的和可能讀到舊數(shù)據(jù)
讀到空可能會導(dǎo)致業(yè)務(wù)Node消失、Pod消失、Node上Service路由規(guī)則消失,一般場景下,只會影響用戶變更的服務(wù)
讀到老數(shù)據(jù)會導(dǎo)致業(yè)務(wù)變更不生效,如服務(wù)擴(kuò)縮容、Service rs替換、變更鏡像異常等待,一般場景下,只會影響用戶變更的服務(wù)
在etcd平臺遷移場景下,client無法感知到寫入失敗,若校驗數(shù)據(jù)一致性也無異常時(校驗時連接到了正常節(jié)點(diǎn)),會導(dǎo)致遷移后整個集群全面故障(apiserver連接到了異常節(jié)點(diǎn)),用戶的Node、部署的服務(wù)、lb等會被全部刪除,嚴(yán)重影響用戶業(yè)務(wù)
首先第一個不一致bug是重啟etcd過程中遇到的,人工嘗試復(fù)現(xiàn)多次皆失敗,分析、定位、復(fù)現(xiàn)、解決這個bug之路幾經(jīng)波折,過程很有趣并充滿挑戰(zhàn),最終通過我對關(guān)鍵點(diǎn)增加debug日志,編寫chaos monkey模擬各種異常場景、邊界條件,實(shí)現(xiàn)復(fù)現(xiàn)成功。最后的真兇竟然是一個授權(quán)接口在重啟后重放導(dǎo)致鑒權(quán)版本號不一致,然后放大導(dǎo)致多版本數(shù)據(jù)庫不一致, 部分節(jié)點(diǎn)無法寫入新數(shù)據(jù), 影響所有v3版本的3年之久bug。
隨后我們提交若干個相關(guān)pr到社區(qū), 并全部合并了, 最新的etcd v3.4.9[1],v3.3.22[2]已修復(fù)此問題, 同時google的jingyih也已經(jīng)提K8s issue和pr[3]將K8s 1.19的etcd client及server版本升級到最新的v3.4.9。此bug詳細(xì)可參考超凡同學(xué)寫的文章三年之久的 etcd3 數(shù)據(jù)不一致 bug 分析。
第二個不一致bug是在升級etcd過程中遇到的,因etcd缺少關(guān)鍵的錯誤日志,故障現(xiàn)場有效信息不多,定位較困難,只能通過分析代碼和復(fù)現(xiàn)解決。然而人工嘗試復(fù)現(xiàn)多次皆失敗,于是我們通過chaos monkey模擬client行為場景,將測試環(huán)境所有K8s集群的etcd分配請求調(diào)度到我們復(fù)現(xiàn)集群,以及對比3.2與3.3版本差異,在可疑點(diǎn)如lease和txn模塊增加大量的關(guān)鍵日志,并對etcd apply request失敗場景打印錯誤日志。
通過以上措施,我們比較快就復(fù)現(xiàn)成功了, 最終通過代碼和日志發(fā)現(xiàn)是3.2版本與3.3版本在revoke lease權(quán)限上出現(xiàn)了差異,3.2無權(quán)限,3.3需要寫權(quán)限。當(dāng)lease過期的時候,如果leader是3.2,那么請求在3.3節(jié)點(diǎn)就會因無權(quán)限導(dǎo)致失敗,進(jìn)而導(dǎo)致key數(shù)量不一致,mvcc版本號不一致,導(dǎo)致txn事務(wù)部分場景執(zhí)行失敗等。最新的3.2分支也已合并我們提交的修復(fù)方案,同時我們增加了etcd核心過程失敗的錯誤日志以提高數(shù)據(jù)不一致問題定位效率,完善了升級文檔,詳細(xì)說明了lease會在此場景下引起數(shù)據(jù)不一致性,避免大家再次采坑。
從這兩個數(shù)據(jù)不一致bug中我們獲得了以下收獲和最佳實(shí)踐:
算法理論數(shù)據(jù)一致性,不代表整體服務(wù)實(shí)現(xiàn)能保證數(shù)據(jù)一致性,目前業(yè)界對于這種基于日志復(fù)制狀態(tài)機(jī)實(shí)現(xiàn)的分布式存儲系統(tǒng),沒有一個核心的機(jī)制能保證raft、wal、mvcc、snapshot等模塊協(xié)作不出問題,raft只能保證日志狀態(tài)機(jī)的一致性,不能保證應(yīng)用層去執(zhí)行這些日志對應(yīng)的command都會成功
etcd版本升級存在一定的風(fēng)險,需要仔細(xì)review代碼評估是否存在不兼容的特性,如若存在是否影響鑒權(quán)版本號及mvcc版本號,若影響則升級過程中可能會導(dǎo)致數(shù)據(jù)不一致性,同時一定要灰度變更現(xiàn)網(wǎng)集群
對所有etcd集群增加了一致性巡檢告警,如revision差異監(jiān)控、key數(shù)量差異監(jiān)控等
定時對etcd集群數(shù)據(jù)進(jìn)行備份,再小概率的故障,根據(jù)墨菲定律都可能會發(fā)生,即便etcd本身雖具備完備的自動化測試(單元測試、集成測試、e2e測試、故障注入測試等),但測試用例仍有不少場景無法覆蓋,我們需要為最壞的場景做準(zhǔn)備(如3個節(jié)點(diǎn)wal、snap、db文件同時損壞),降低極端情況下的損失, 做到可用備份數(shù)據(jù)快速恢復(fù)
etcd v3.4.4后的集群灰度開啟data corruption檢測功能,當(dāng)集群出現(xiàn)不一致時,拒絕集群寫入、讀取,及時止損,控制不一致的數(shù)據(jù)范圍
繼續(xù)完善我們的chaos monkey和使用etcd本身的故障注入測試框架functional,以協(xié)助我們驗證、壓測新版本穩(wěn)定性(長時間持續(xù)跑),復(fù)現(xiàn)隱藏極深的bug, 降低線上采坑的概率。
內(nèi)存泄露(OOM)
眾所周知etcd是golang寫的,而golang自帶垃圾回收機(jī)制也會內(nèi)存泄露嗎?首先我們得搞清楚golang垃圾回收的原理,它是通過后臺運(yùn)行一個守護(hù)線程,監(jiān)控各個對象的狀態(tài),識別并且丟棄不再使用的對象來釋放和重用資源,若你遲遲未釋放對象,golang垃圾回收不是萬能的,不泄露才怪。比如以下場景會導(dǎo)致內(nèi)存泄露:
goroutine泄露
deferring function calls(如for循環(huán)里面未使用匿名函數(shù)及時調(diào)用defer釋放資源,而是整個for循環(huán)結(jié)束才調(diào)用)
獲取string/slice中的一段導(dǎo)致長string/slice未釋放(會共享相同的底層內(nèi)存塊)
應(yīng)用內(nèi)存數(shù)據(jù)結(jié)構(gòu)管理不周導(dǎo)致內(nèi)存泄露(如為及時清理過期、無效的數(shù)據(jù))
接下來看看我們遇到的這個etcd內(nèi)存泄露屬于哪種情況呢?事情起源于3月末的一個周末起床后收到現(xiàn)網(wǎng)3.4集群大量內(nèi)存超過安全閾值告警,立刻排查了下發(fā)現(xiàn)以下現(xiàn)象:
QPS及流量監(jiān)控顯示都較低,因此排除高負(fù)載及慢查詢因素
一個集群3個節(jié)點(diǎn)只有兩個follower節(jié)點(diǎn)出現(xiàn)異常,leader 4g,follower節(jié)點(diǎn)高達(dá)23G
goroutine、fd等資源未出現(xiàn)泄漏
go runtime memstats指標(biāo)顯示各個節(jié)點(diǎn)申請的內(nèi)存是一致的,但是follower節(jié)點(diǎn)go_memstats_heap_release_bytes遠(yuǎn)低于leader節(jié)點(diǎn),說明某數(shù)據(jù)結(jié)構(gòu)可能長期未釋放
生產(chǎn)集群默認(rèn)關(guān)閉了pprof,開啟pprof,等待復(fù)現(xiàn), 并在社區(qū)上搜索釋放有類似案例, 結(jié)果發(fā)現(xiàn)有多個用戶1月份就報了,沒引起社區(qū)重視,使用場景和現(xiàn)象跟我們一樣
通過社區(qū)的heap堆棧快速定位到了是由于etcd通過一個heap堆來管理lease的狀態(tài),當(dāng)lease過期時需要從堆中刪除,但是follower節(jié)點(diǎn)卻無此操作,因此導(dǎo)致follower內(nèi)存泄露, 影響所有3.4版本。
問題分析清楚后,我提交的修復(fù)方案是follower節(jié)點(diǎn)不需要維護(hù)lease heap,當(dāng)leader發(fā)生選舉時確保新的follower節(jié)點(diǎn)能重建lease heap,老的leader節(jié)點(diǎn)則清空lease heap.
此內(nèi)存泄露bug屬于內(nèi)存數(shù)據(jù)結(jié)構(gòu)管理不周導(dǎo)致的,問題修復(fù)后,etcd社區(qū)立即發(fā)布了新的版本(v3.4.6+)以及K8s都立即進(jìn)行了etcd版本更新。
從這個內(nèi)存泄露bug中我們獲得了以下收獲和最佳實(shí)踐:
持續(xù)關(guān)注社區(qū)issue和pr, 別人今天的問題很可能我們明天就會遇到
etcd本身測試無法覆蓋此類需要一定時間運(yùn)行的才能觸發(fā)的資源泄露bug,我們內(nèi)部需要加強(qiáng)此類場景的測試與壓測
持續(xù)完善、豐富etcd平臺的各類監(jiān)控告警,機(jī)器留足足夠的內(nèi)存buffer以扛各種意外的因素。
存儲層死鎖(Mvcc Deadlock)
死鎖是指兩個或兩個以上的goroutine的執(zhí)行過程中,由于競爭資源相互等待(一般是鎖)或由于彼此通信(chan引起)而造成的一種程序卡死現(xiàn)象,無法對外提供服務(wù)。deadlock問題因為往往是在并發(fā)狀態(tài)下資源競爭導(dǎo)致的, 一般比較難定位和復(fù)現(xiàn), 死鎖的性質(zhì)決定著我們必須保留好分析現(xiàn)場,否則分析、復(fù)現(xiàn)及其困難。
那么我們是如何發(fā)現(xiàn)解決這個deadlock bug呢?問題起源于內(nèi)部團(tuán)隊在壓測etcd集群時,發(fā)現(xiàn)一個節(jié)點(diǎn)突然故障了,而且一直無法恢復(fù),無法正常獲取key數(shù)等信息。收到反饋后,我通過分析卡住的etcd進(jìn)程和查看監(jiān)控,得到以下結(jié)論:
不經(jīng)過raft及mvcc模塊的rpc請求如member list可以正常返回結(jié)果,而經(jīng)過的rpc請求全部context timeout
etcd health健康監(jiān)測返回503,503的報錯邏輯也是經(jīng)過了raft及mvcc
通過tcpdump和netstat排除raft網(wǎng)絡(luò)模塊異常,可疑目標(biāo)縮小到mvcc
分析日志發(fā)現(xiàn)卡住的時候因數(shù)據(jù)落后leader較多,接收了一個數(shù)據(jù)快照,然后執(zhí)行更新快照的時候卡住了,沒有輸出快照加載完畢的日志,同時確認(rèn)日志未丟失
排查快照加載的代碼,鎖定幾個可疑的鎖和相關(guān)goroutine,準(zhǔn)備獲取卡住的goroutine堆棧
通過kill或pprof獲取goroutine堆棧,根據(jù)goroutine卡住的時間和相關(guān)可疑點(diǎn)的代碼邏輯,成功找到兩個相互競爭資源的goroutine,其中一個正是執(zhí)行快照加載,重建db的主goroutine,它獲取了一把mvcc鎖等待所有異步任務(wù)結(jié)束,而另外一個goroutine則是執(zhí)行歷史key壓縮任務(wù),當(dāng)它收到stop的信號后,立刻退出,調(diào)用一個compactBarrier邏輯,而這個邏輯又恰恰需要獲取mvcc鎖,因此出現(xiàn)死鎖,堆棧如下。
這個bug也隱藏了很久,影響所有etcd3版本,在集群中寫入量較大,某落后的較多的節(jié)點(diǎn)執(zhí)行了快照重建,同時此時又恰恰在做歷史版本壓縮,那就會觸發(fā)。我提交的修復(fù)PR目前也已經(jīng)合并到3.3和3.4分支中,新的版本已經(jīng)發(fā)布(v3.3.21+/v3.4.8+)。
從這個死鎖bug中我們獲得了以下收獲和最佳實(shí)踐:
多并發(fā)場景的組合的etcd自動化測試用例覆蓋不到,也較難構(gòu)建,因此也容易出bug, 是否還有其他類似場景存在同樣的問題?需要參與社區(qū)一起繼續(xù)提高etcd測試覆蓋率(etcd之前官方博客介紹一大半代碼已經(jīng)是測試代碼),才能避免此類問題。
監(jiān)控雖然能及時發(fā)現(xiàn)異常節(jié)點(diǎn)宕機(jī),但是死鎖這種場景之前我們不會自動重啟etcd,因此需要完善我們的健康探測機(jī)制(比如curl /health來判斷服務(wù)是否正常),出現(xiàn)死鎖時能夠保留堆棧、自動重啟恢復(fù)服務(wù)。
對于讀請求較高的場景,需評估3節(jié)點(diǎn)集群在一節(jié)點(diǎn)宕機(jī)后,剩余兩節(jié)點(diǎn)提供的QPS容量是否能夠支持業(yè)務(wù),若不夠則考慮5節(jié)點(diǎn)集群。
Wal crash (Panic)
panic是指出現(xiàn)嚴(yán)重運(yùn)行時和業(yè)務(wù)邏輯錯誤,導(dǎo)致整個進(jìn)程退出。panic對于我們而言并不陌生,我們在現(xiàn)網(wǎng)遇到過幾次,最早遭遇的不穩(wěn)定性因素就是集群運(yùn)行過程中panic了。
雖說我們3節(jié)點(diǎn)的etcd集群是可以容忍一個節(jié)點(diǎn)故障,但是crash瞬間對用戶依然有影響,甚至出現(xiàn)集群撥測連接失敗。
我們遇到的第一個crash bug,是發(fā)現(xiàn)集群鏈接數(shù)較多的時候有一定的概率出現(xiàn)crash, 然后根據(jù)堆棧查看社區(qū)已有人報grpc?crash(issue)[4], 原因是etcd依賴的組件grpc-go出現(xiàn)了grpc?crash(pr)[5],而最近我們遇到的crash bug[6]是v3.4.8/v3.3.21新版本發(fā)布引起的,這個版本跟我們有很大關(guān)系,我們貢獻(xiàn)了3個PR到這個版本,占了一大半以上, 那么這個crash bug是如何產(chǎn)生以及復(fù)現(xiàn)呢?會不會是我們自己的鍋呢?
首先crash報錯是walpb: crc mismatch, 而我們并未提交代碼修改wal相關(guān)邏輯,排除自己的鍋。
其次通過review新版本pr, 目標(biāo)鎖定到google一位大佬在修復(fù)一個wal在寫入成功后,而snapshot寫入失敗導(dǎo)致的crash bug的時候引入的.
但是具體是怎么引入的?pr中包含多個測試用例驗證新加邏輯,本地創(chuàng)建空集群和使用存量集群(數(shù)據(jù)比較小)也無法復(fù)現(xiàn).
錯誤日志信息太少,導(dǎo)致無法確定是哪個函數(shù)報的錯,因此首先還是加日志,對各個可疑點(diǎn)增加錯誤日志后,在我們測試集群隨便找了個老節(jié)點(diǎn)替換版本,然后很容易就復(fù)現(xiàn)了,并確定是新加的驗證快照文件合法性的鍋,那么它為什么會出現(xiàn)crc mismatch呢? 首先我們來簡單了解下wal文件。
etcd任何經(jīng)過raft的模塊的請求在寫入etcd mvcc db前都會通過wal文件持久化,若進(jìn)程在apply command過程中出現(xiàn)被殺等異常,重啟時可通過wal文件重放將數(shù)據(jù)補(bǔ)齊,避免數(shù)據(jù)丟失。wal文件包含各種請求命令如成員變化信息、涉及key的各個操作等,為了保證數(shù)據(jù)完整性、未損壞,wal每條記錄都會計算其的crc32,寫入wal文件。重啟后解析wal文件過程中,會校驗記錄的完整性,如果數(shù)據(jù)出現(xiàn)損壞或crc32計算算法出現(xiàn)變化則會出現(xiàn)crc32 mismatch.
硬盤及文件系統(tǒng)并未出現(xiàn)異常,排除了數(shù)據(jù)損壞,經(jīng)過深入排查crc32算法的計算,發(fā)現(xiàn)是新增邏輯未處理crc32類型的數(shù)據(jù)記錄,它會影響crc32算法的值,導(dǎo)致出現(xiàn)差異,而且只有在當(dāng)etcd集群創(chuàng)建產(chǎn)生后的第一個wal文件被回收才會觸發(fā),因此對存量運(yùn)行一段時間的集群,100%復(fù)現(xiàn)。
解決方案就是通過增加crc32算法的處理邏輯以及增加單元測試覆蓋wal文件被回收的場景,社區(qū)已合并并發(fā)布了新的3.4和3.3版本(v3.4.9/v3.3.22).
雖然這個bug是社區(qū)用戶反饋的,但從這個crash bug中我們獲得了以下收獲和最佳實(shí)踐:
單元測試用例非常有價值,然而編寫完備的單元測試用例并不容易,需要考慮各類場景。
etcd社區(qū)對存量集群升級、各版本之間兼容性測試用例幾乎是0,需要大家一起來為其舔磚加瓦,讓測試用例覆蓋更多場景。
新版本上線內(nèi)部流程標(biāo)準(zhǔn)化、自動化, 如測試環(huán)境壓測、混沌測試、不同版本性能對比、優(yōu)先在非核心場景使用(如event)、灰度上線等流程必不可少。
數(shù)配額及限速(Quota&QoS)
etcd面對一些大數(shù)據(jù)量的查詢(expensive read)和寫入操作時(expensive write),如全key遍歷(full keyspace fetch)、大量event查詢, list all Pod, configmap寫入等會消耗大量的cpu、內(nèi)存、帶寬資源,極其容易導(dǎo)致過載,乃至雪崩。
然而,etcd目前只有一個極其簡單的限速保護(hù),當(dāng)etcd的commited index大于applied index的閾值大于5000時,會拒絕一切請求,返回Too Many Request,其缺陷很明顯,無法精確的對expensive read/write進(jìn)行限速,無法有效防止集群過載不可用。
為了解決以上挑戰(zhàn),避免集群過載目前我們通過以下方案來保障集群穩(wěn)定性:
基于K8s apiserver上層限速能力,如apiserver默認(rèn)寫100/s,讀200/s
基于K8s resource quota控制不合理的Pod/configmap/crd數(shù)
基于K8s controller-manager的-terminated-Pod-gc-threshold參數(shù)控制無效Pod數(shù)量(此參數(shù)默認(rèn)值高達(dá)12500,有很大優(yōu)化空間)
基于K8s的apiserver各類資源可獨(dú)立的存儲的特性, 將event/configmap以及其他核心數(shù)據(jù)分別使用不同的etcd集群,在提高存儲性能的同時,減少核心主etcd故障因素
基于event admission webhook對讀寫event的apiserver請求進(jìn)行速率控制
基于不同業(yè)務(wù)情況,靈活調(diào)整event-ttl時間,盡量減少event數(shù)
基于etcd開發(fā)QoS特性,目前也已經(jīng)向社區(qū)提交了初步設(shè)計方案,支持基于多種對象類型設(shè)置QoS規(guī)則(如按grpcMethod、grpcMethod+請求key前綴路徑、traffic、cpu-intensive、latency)
通過多維度的集群告警(etcd集群lb及節(jié)點(diǎn)本身出入流量告警、內(nèi)存告警、精細(xì)化到每個K8s集群的資源容量異常增長告警、集群資源讀寫QPS異常增長告警)來提前防范、規(guī)避可能出現(xiàn)的集群穩(wěn)定性問題
多維度的集群告警在我們的etcd穩(wěn)定性保障中發(fā)揮了重要作用,多次幫助我們發(fā)現(xiàn)用戶和我們自身集群組件問題。用戶問題如內(nèi)部某K8s平臺之前出現(xiàn)bug, 寫入大量的集群CRD資源和client讀寫CRD QPS明顯偏高。我們自身組件問題如某舊日志組件,當(dāng)集群規(guī)模增大后,因日志組件不合理的頻繁調(diào)用list Pod,導(dǎo)致etcd集群流量高達(dá)3Gbps, 同時apiserver本身也出現(xiàn)5XX錯誤。
雖然通過以上措施,我們能極大的減少因expensive read導(dǎo)致的穩(wěn)定性問題,然而從線上實(shí)踐效果看,目前我們?nèi)匀槐容^依賴集群告警幫助我們定位一些異常client調(diào)用行為,無法自動化的對異常client的進(jìn)行精準(zhǔn)智能限速,。etcd層因無法區(qū)分是哪個client調(diào)用,如果在etcd側(cè)限速會誤殺正常client的請求, 因此依賴apiserver精細(xì)化的限速功能實(shí)現(xiàn)。社區(qū)目前已在1.18中引入了一個API?Priority?and?Fairness[7],目前是alpha版本,期待此特性早日穩(wěn)定。
性能優(yōu)化案例剖析
etcd讀寫性能決定著我們能支撐多大規(guī)模的集群、多少client并發(fā)調(diào)用,啟動耗時決定著我們當(dāng)重啟一個節(jié)點(diǎn)或因落后leader太多,收到leader的快照重建時,它重新提供服務(wù)需要多久?性能優(yōu)化案例剖析我們將從啟動耗時減少一半、密碼鑒權(quán)性能提升12倍、查詢key數(shù)量性能提升3倍等來簡單介紹下如何對etcd進(jìn)行性能優(yōu)化。
啟動耗時及查詢key數(shù)量、查詢指定記錄數(shù)性能優(yōu)化
當(dāng)db size達(dá)到4g時,key數(shù)量百萬級別時,發(fā)現(xiàn)重啟一個集群耗時竟然高達(dá)5分鐘, key數(shù)量查詢也是超時,調(diào)整超時時間后,發(fā)現(xiàn)高達(dá)21秒,內(nèi)存暴漲6G。同時查詢只返回有限的記錄數(shù)的場景(如業(yè)務(wù)使用etcd grpc-proxy來減少watch數(shù),etcd grpc proxy在默認(rèn)創(chuàng)建watch的時候,會發(fā)起對watch路徑的一次limit讀查詢),依然耗時很高且有巨大的內(nèi)存開銷。于是周末空閑的時候我對這幾個問題進(jìn)行了深入調(diào)查分析,啟動耗時到底花在了哪里?是否有優(yōu)化空間?查詢key數(shù)量為何如何耗時,內(nèi)存開銷如此之大?
帶著這些問題對源碼進(jìn)行了深入分析和定位,首先來看查詢key數(shù)和查詢只返回指定記錄數(shù)的耗時和內(nèi)存開銷極大的問題,分析結(jié)論如下:
查詢key數(shù)量時etcd之前實(shí)現(xiàn)是遍歷整個內(nèi)存btree,把key對應(yīng)的revision存放在slice數(shù)組里面
問題就在于key數(shù)量較多時,slice擴(kuò)容涉及到數(shù)據(jù)拷貝,以及slice也需要大量的內(nèi)存開銷
因此優(yōu)化方案是新增一個CountRevision來統(tǒng)計key的數(shù)量即可,不需要使用slice,此方案優(yōu)化后性能從21s降低到了7s,同時無任何內(nèi)存開銷
對于查詢指定記錄數(shù)據(jù)耗時和內(nèi)存開銷非常大的問題,通過分析發(fā)現(xiàn)是limit記錄數(shù)并未下推到索引層,通過將查詢limit參數(shù)下推到索引層,大數(shù)據(jù)場景下limit查詢性能提升百倍,同時無額外的內(nèi)存開銷。
再看啟動耗時問題過高的問題,通過對啟動耗時各階段增加日志,得到以下結(jié)論:
啟動的時候機(jī)器上的cpu資源etcd進(jìn)程未能充分利用
9%耗時在打開后端db時,如將整個db文件mmap到內(nèi)存
91%耗時在重建內(nèi)存索引btree上。當(dāng)etcd收到一個請求Get Key時,請求被層層傳遞到了mvcc層后,它首先需要從內(nèi)存索引btree中查找key對應(yīng)的版本號,隨后從boltdb里面根據(jù)版本號查出對應(yīng)的value, 然后返回給client. 重建內(nèi)存索引btree數(shù)的時候,恰恰是相反的流程,遍歷boltdb,從版本號0到最大版本號不斷遍歷,從value里面解析出對應(yīng)的key、revision等信息,重建btree,因為這個是個串行操作,所以操作及其耗時
嘗試將串行構(gòu)建btree優(yōu)化成高并發(fā)構(gòu)建,盡量把所有核計算力利用起來,編譯新版本測試后發(fā)現(xiàn)效果甚微,于是編譯新版本打印重建內(nèi)存索引各階段的詳細(xì)耗時分析,結(jié)果發(fā)現(xiàn)瓶頸在內(nèi)存btree的插入上,而這個插入擁有一個全局鎖,因此幾乎無優(yōu)化空間
繼續(xù)分析91%耗時發(fā)現(xiàn)重建內(nèi)存索引竟然被調(diào)用了兩次,第一處是為了獲取一個mvcc的關(guān)鍵的consistent index變量,它是用來保證etcd命令不會被重復(fù)執(zhí)行的關(guān)鍵數(shù)據(jù)結(jié)構(gòu),而我們前面提到的一個數(shù)據(jù)不一致bug恰好也是跟consistent index有密切關(guān)系。
consistent index實(shí)現(xiàn)不合理,封裝在mvcc層,因此我前面提了一個pr將此特性重構(gòu),做為了一個獨(dú)立的包,提供各類方法給etcdserver、mvcc、auth、lease等模塊調(diào)用。
特性重構(gòu)后的consistent index在啟動的時候就不再需要通過重建內(nèi)存索引數(shù)等邏輯來獲取了,優(yōu)化成調(diào)用cindex包的方法快速獲取到consistent index,就將整個耗時從5min從縮短到2分30秒左右。因此優(yōu)化同時依賴的consistent index特性重構(gòu),改動較大暫未backport到3.4/3.3分支,在未來3.5版本中、數(shù)據(jù)量較大時可以享受到啟動耗時的顯著提升。
密碼鑒權(quán)性能提升12倍
某內(nèi)部業(yè)務(wù)服務(wù)一直跑的好好的,某天client略微增多后,突然現(xiàn)網(wǎng)etcd集群出現(xiàn)大量超時,各種折騰,切換云盤類型、切換部署環(huán)境、調(diào)整參數(shù)都不發(fā)揮作用,收到求助后,索要metrics和日志后,經(jīng)過一番排查后,得到以下結(jié)論:
現(xiàn)象的確很詭異,db延時相關(guān)指標(biāo)顯示沒任何異常,日志無任何有效信息
業(yè)務(wù)反饋大量讀請求超時,甚至可以通過etcdctl客戶端工具簡單復(fù)現(xiàn),可是metric對應(yīng)的讀請求相關(guān)指標(biāo)數(shù)竟然是0
引導(dǎo)用戶開啟trace日志和metrics開啟extensive模式,開啟后發(fā)現(xiàn)無任何trace日志,然而開啟extensive后,我發(fā)現(xiàn)耗時竟然全部花在了Authenticate接口,業(yè)務(wù)反饋是通過密碼鑒權(quán),而不是基于證書的鑒權(quán)
嘗試讓業(yè)務(wù)同學(xué)短暫關(guān)閉鑒權(quán)測試業(yè)務(wù)是否恢復(fù),業(yè)務(wù)同學(xué)找了一個節(jié)點(diǎn)關(guān)閉鑒權(quán)后,此節(jié)點(diǎn)立刻恢復(fù)了正常,于是選擇臨時通過關(guān)閉鑒權(quán)來恢復(fù)現(xiàn)網(wǎng)業(yè)務(wù)
那鑒權(quán)為什么耗時這么慢?我們對可疑之處增加了日志,打印了鑒權(quán)各個步驟的耗時,結(jié)果發(fā)現(xiàn)是在等待鎖的過程中出現(xiàn)了超時,而這個鎖為什么耗時這么久呢?排查發(fā)現(xiàn)是因為加鎖過程中會調(diào)用bcrpt加密函數(shù)計算密碼hash值,每次耗費(fèi)60ms左右,數(shù)百并發(fā)下等待此鎖的最高耗時高達(dá)5s+。
于是我們編寫新版本將鎖的范圍減少,降低持鎖阻塞時間,用戶使用新版本后,開啟鑒權(quán)后,業(yè)務(wù)不再超時,恢復(fù)正常。
隨后我們將修復(fù)方案提交給了社區(qū),并編寫了壓測工具,測試提升后的性能高達(dá)近12倍(8核32G機(jī)器,從18/s提升到202/s),但是依然是比較慢,主要是鑒權(quán)過程中涉及密碼校驗計算, 社區(qū)上也有用戶反饋密碼鑒權(quán)慢問題, 目前最新的v3.4.9版本已經(jīng)包含此優(yōu)化, 同時可以通過調(diào)整bcrpt-cost參數(shù)來進(jìn)一步提升性能。
總結(jié)
本文簡單描述了我們在管理萬級K8s集群和其他業(yè)務(wù)過程中遇到的etcd穩(wěn)定性和性能挑戰(zhàn),以及我們是如何定位、分析、復(fù)現(xiàn)、解決這些挑戰(zhàn),并將解決方案貢獻(xiàn)給社區(qū)。
同時,詳細(xì)描述了我們從這些挑戰(zhàn)中收獲了哪些寶貴的經(jīng)驗和教訓(xùn),并將之應(yīng)用到后續(xù)的etcd穩(wěn)定性保障中,以支持更大規(guī)模的單集群和總集群數(shù)。
最后我們面對萬級K8s集群數(shù), 千級的etcd集群數(shù), 10幾個版本分布,其中不少低版本包含重要的潛在可能觸發(fā)的嚴(yán)重bug, 我們還需要投入大量工作不斷優(yōu)化我們的etcd平臺,使其更智能、變更更加高效、安全、可控(如支持自動化、可控的集群升級等), 同時數(shù)據(jù)安全也至關(guān)重要,目前騰訊云TKE托管集群我們已經(jīng)全面?zhèn)浞?#xff0c;獨(dú)立集群的用戶后續(xù)將引導(dǎo)通過應(yīng)用市場的etcd備份插件開啟定時備份到騰訊云對象存儲COS上。
未來我們將繼續(xù)緊密融入etcd的社區(qū),為etcd社區(qū)的發(fā)展貢獻(xiàn)我們的力量,與社區(qū)一塊提升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
本文來自公眾號:騰訊云原生,可閱讀原文了解。
騰訊大數(shù)據(jù)技術(shù)系列沙龍活動即將舉辦,歡迎掃碼報名參加:
總結(jié)
以上是生活随笔為你收集整理的万级K8s集群背后etcd稳定性及性能优化实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Elasticsearch 索引容量管理
- 下一篇: AI助力新药研发 腾讯发布「云深智药」平