背景與挑戰
隨著騰訊自研上雲及公有云使用者的迅速增長,一方面,騰訊雲容器服務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
【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多幹貨!!