三年之久的 etcd3 資料不一致 bug 分析

騰訊雲原生發表於2020-09-16

問題背景

詭異的 K8S 滾動更新異常

筆者某天收到同事反饋,測試環境中 K8S 叢集進行滾動更新發布時未生效。通過 kube-apiserver 檢視發現,對應的 Deployment 版本已經是最新版,但是這個最新版本的 Pod 並未建立出來。

針對該現象,我們最開始猜測可能是 kube-controller-manager 的 bug 導致,但是觀察 controller-manager 日誌並未發現明顯異常。第一次調高 controller-manager 的日誌等級並進行重啟操作之後,似乎由於 controller-manager 並沒有 watch 到這個更新事件,我們仍然沒有發現問題所在。此時,觀察 kube-apiserver 日誌,同樣也沒有出現明顯異常。

於是,再次調高日誌等級並重啟 kube-apiserver,詭異的事情發生了,之前的 Deployment 正常滾動更新了!

etcd 資料不一致 ?

由於從 kube-apiserver 的日誌中同樣無法提取出能夠幫助解決問題的有用資訊,起初我們只能猜測可能是 kube-apiserver 的快取更新異常導致的。正當我們要從這個切入點去解決問題時,該同事反饋了一個更詭異的問題——自己新建立的 Pod,通過 kubectl查詢 Pod 列表,突然消失了!納尼?這是什麼騷操作?經過我們多次測試查詢發現,通過 kubectl 來 list pod 列表,該 pod 有時候能查到,有時候查不到。那麼問題來了,K8s api 的 list 操作是沒有快取的,資料是 kube-apiserver 直接從 etcd 拉取返回給客戶端的,難道是 etcd 本身出了問題?

眾所周知,etcd 本身是一個強一致性的 KV 儲存,在寫操作成功的情況下,兩次讀請求不應該讀取到不一樣的資料。懷著不信邪的態度,我們通過 etcdctl 直接查詢了 etcd 叢集狀態和叢集資料,返回結果顯示 3 個節點狀態都正常,且 RaftIndex 一致,觀察 etcd 的日誌也並未發現報錯資訊,唯一可疑的地方是 3 個節點的 dbsize 差別較大。接著,我們又將 client 訪問的 endpoint 指定為不同節點地址來查詢每個節點的 key 的數量,結果發現 3 個節點返回的 key 的數量不一致,甚至兩個不同節點上 Key 的數量差最大可達到幾千!而直接通過 etcdctl 查詢剛才建立的 Pod,發現訪問某些 endpoint 能夠查詢到該 pod,而訪問其他 endpoint 則查不到。至此,基本可以確定 etcd 叢集的節點之間確實存在資料不一致現象。

問題分析和排查過程

遇事不決問Google

強一致性的儲存突然資料不一致了,這麼嚴重的問題,想必日誌裡肯定會有所體現。然而,可能是 etcd 開發者擔心日誌太多會影響效能的緣故,etcd 的日誌列印的比較少,以至於我們排查了 etcd 各個節點的日誌,也沒有發現有用的報錯日誌。甚至是在我們調高日誌級別之後,仍沒有發現異常資訊。

作為一個21世紀的程式設計師,遇到這種詭異且暫時沒頭緒的問題,第一反應當然是先 Google 一下啦,畢竟不會 StackOverFlow 的程式設計師不是好運維!Google 輸入“etcd data inconsistent” 搜尋發現,並不是只有我們遇到過該問題,之前也有其他人向 etcd 社群反饋過類似問題,只是苦於沒有提供穩定的復現方式,最後都不了了之。如 issue:

https://github.com/etcd-io/etcd/issues/9630

https://github.com/etcd-io/etcd/issues/10407

https://github.com/etcd-io/etcd/issues/10594

https://github.com/etcd-io/etcd/issues/11643

由於這個問題比較嚴重,會影響到資料的一致性,而我們生產環境中當前使用了數百套 etcd 叢集,為了避免出現類似問題,我們決定深入定位一番。

etcd 工作原理和術語簡介

在開始之前,為方便讀者理解,這裡先簡單介紹下 etcd 的常用術語和基本讀寫原理。

術語表:

etcd 是一個強一致性的分散式 KV 儲存,所謂強一致性,簡單來說就是一個寫操作成功後,從任何一個節點讀出來的資料都是最新值,而不會出現寫資料成功後讀不出來或者讀到舊資料的情況。etcd 通過 raft 協議來實現 leader 選舉、配置變更以及保證資料讀寫的一致性。下面簡單介紹下 etcd 的讀寫流程:

寫資料流程(以 leader 節點為例,見上圖):

  1. etcd 任一節點的 etcd server 模組收到 Client 寫請求(如果是 follower 節點,會先通過 Raft 模組將請求轉發至 leader 節點處理)。
  2. etcd server 將請求封裝為 Raft 請求,然後提交給 Raft 模組處理。
  3. leader 通過 Raft 協議與叢集中 follower 節點進行互動,將訊息複製到follower 節點,於此同時,並行將日誌持久化到 WAL。
  4. follower 節點對該請求進行響應,回覆自己是否同意該請求。
  5. 當叢集中超過半數節點((n/2)+1 members )同意接收這條日誌資料時,表示該請求可以被Commit,Raft 模組通知 etcd server 該日誌資料已經 Commit,可以進行 Apply。
  6. 各個節點的 etcd server 的 applierV3 模組非同步進行 Apply 操作,並通過 MVCC 模組寫入後端儲存 BoltDB。
  7. 當 client 所連線的節點資料 apply 成功後,會返回給客戶端 apply 的結果。

讀資料流程

  • etcd 任一節點的 etcd server 模組收到客戶端讀請求(Range 請求)
  • 判斷讀請求型別,如果是序列化讀(serializable)則直接進入 Apply 流程
  • 如果是線性一致性讀(linearizable),則進入 Raft 模組
  • Raft 模組向 leader 發出 ReadIndex 請求,獲取當前叢集已經提交的最新資料 Index
  • 等待本地 AppliedIndex 大於或等於 ReadIndex 獲取的 CommittedIndex 時,進入 Apply 流程
  • Apply 流程:通過 Key 名從 KV Index 模組獲取 Key 最新的 Revision,再通過 Revision 從 BoltDB 獲取對應的 Key 和 Value。

初步驗證

通常叢集正常執行情況下,如果沒有外部變更的話,一般不會出現這麼嚴重的問題。我們查詢故障 etcd 叢集近幾天的釋出記錄時發現,故障前一天對該叢集進行的一次釋出中,由於之前 dbsize 配置不合理,導致 db 被寫滿,叢集無法寫入新的資料,為此運維人員更新了叢集 dbsize 和 compaction 相關配置,並重啟了 etcd。重啟後,運維同學繼續對 etcd 手動執行了 compact 和 defrag 操作,來壓縮 db 空間。

通過上述場景,我們可以初步判斷出以下幾個可疑的觸發條件:

  1. dbsize 滿
  2. dbsize 和 compaction 配置更新
  3. compaction 操作和 defrag 操作
  4. 重啟 etcd

出了問題肯定要能夠復現才更有利於解決問題,正所謂能夠復現的 bug 就不叫 bug。復現問題之前,我們通過分析 etcd 社群之前的相關 issue 發現,觸發該 bug 的共同條件都包含執行過 compaction 和 defrag 操作,同時重啟過 etcd 節點。因此,我們計劃首先嚐試同時模擬這幾個操作,觀察是否能夠在新的環境中復現。為此我們新建了一個叢集,然後通過編寫指令碼向叢集中不停的寫入和刪除資料,直到 dbsize 達到一定程度後,對節點依次進行配置更新和重啟,並觸發 compaction 和 defrag 操作。然而經過多次嘗試,我們並沒有復現出類似於上述資料不一致的場景。

抽絲剝繭,初現端倪

緊接著,在之後的測試中無意發現,client 指定不同的 endpoint 寫資料,能夠查到資料的節點也不同。比如,endpoint 指定為 node1 進行寫資料,3個節點都可以查到資料;endpoint 指定為 node2 進行寫資料,node2 和 node3 可以查到;endpoint 指定為 node3 寫資料,只有 node3 自己能夠查到。具體情況如下表:

於是我們做出了初步的猜測,有以下幾種可能:

  1. 叢集可能分裂了,leader 未將訊息傳送給 follower 節點。
  2. leader 給 follower 節點傳送了訊息,但是 log 異常,沒有對應的 command 。
  3. leader 給 follower 節點傳送了訊息,有對應的 command,但是 apply 異常,操作還未到 KV Index 和 boltdb 就異常了。
  4. leader 給 follower 節點傳送了訊息, 有對應的 command,但是 apply 異常,KV Index 出現了問題。
  5. leader 給 follower 節點傳送了訊息, 有對應的 command,但是 apply 異常,boltdb 出現了問題。

為了驗證我們的猜測,我們進行了一系列測試來縮小問題範圍:

首先,我們通過 endpoint status 檢視叢集資訊,發現 3 個節點的 clusterId,leader,raftTerm,raftIndex 資訊均一致,而 dbSize 大小和 revision 資訊不一致。clusterId 和 leader 一致,基本排除了叢集分裂的猜測,而 raftTerm 和 raftIndex 一致,說明 leader 是有向 follower 同步訊息的,也進一步排除了第一個猜測,但是 WAL落盤有沒有異常還不確定。dbSize 和 revision 不一致則進一步說明了 3 個節點資料已經發生不一致了。

其次,由於 etcd 本身提供了一些 dump 工具,例如 etcd-dump-log 和 etcd-dump-db。我們可以像下圖一樣,使用 etcd-dump-log dump 出指定 WAL 檔案的內容,使用 etcd-dump-db dump 出 db 檔案的資料,方便對 WAL 和 db 資料進行分析。

於是,我們向 node3 寫了一條便於區分的資料,然後通過 etcd-dump-log 來分析 3 個節點的 WAL,按照剛才的測試,endpoint 指定為 node3 寫入的資料,通過其他兩個節點應該查不到的。但是我們發現 3 個節點都收到了 WAL log,也就是說 WAL 並沒有丟,因此排除了第二個猜測

接下來我們 dump 了 db 的資料進行分析,發現 endpoint 指定為 node3 寫入的資料,在其他兩個節點的 db 檔案裡找不到,也就是說資料確實沒有落到 db,而不是寫進去了查不出來。

既然 WAL 裡有而 db 裡沒有,因此很大可能是 apply 流程異常了,資料可能在 apply 時被丟棄了。

由於現有日誌無法提供更有效的資訊,我們打算在 etcd 裡新加一些日誌來更好地幫助我們定位問題。etcd 在做 apply 操作時,trace 日誌會列印超過每個超過 100ms 的請求,我們首先把 100ms 這個閾值調低,調整到 1ns,這樣每個 apply 的請求都能夠記錄下來,可以更好的幫助我們定位問題。

編譯好新版本之後,我們替換了其中一個 etcd 節點,然後向不同 node 發起寫請求測試。果然,我們發現了一條不同尋常的錯誤日誌:"error":"auth:revision in header is old",因此我們斷定問題很可能是因為——發出這條錯誤日誌的節點,對應的 key 剛好沒有寫進去。

搜尋程式碼後,我們發現 etcd 在進行 apply 操作時,如果開啟了鑑權,在鑑權時會判斷 raft 請求 header 中的 AuthRevision,如果請求中的 AuthRevision 小於當前 node 的AuthRevision,則會認為 AuthRevision 太老而導致 Apply 失敗。

func (as *authStore) isOpPermitted(userName string, revision uint64, key, rangeEnd []byte, permTyp authpb.Permission_Type) error {    // ...  if revision < as.Revision() {    return ErrAuthOldRevision  }  // ...}

這樣看來,很可能是不同節點之間的 AuthRevision 不一致了,AuthRevision 是 etcd 啟動時直接從 db 讀取的,每次變更後也會及時的寫入 db,於是我們簡單修改了下 etcd-dump-db工具,將每個節點 db 記憶體儲的 AuthRevision 解碼出來對比了下,發現 3 個節點的 AuthRevision 確實不一致,node1 的 AuthRevision 最高,node3 的 AuthRevision 最低,這正好能夠解釋之前的現象,endpoint 指定為 node1 寫入的資料,3 個節點都能查到,指定為 node3 寫入的資料,只有 node3 能夠查到,因為 AuthRevision 較低的節點發起的 Raft 請求,會被 AuthRevision 較高的節點在 Apply 的過程中丟棄掉(如下表)。

原始碼之前,了無祕密?

目前為止我們已經可以明確,新寫入的資料通過訪問某些 endpoint 查不出來的原因是由於 AuthRevision 不一致。但是,資料最開始發生不一致問題是否是由 AuthRevision 造成,還暫時不能斷定。為什麼這麼說呢?因為 AuthRevision 很可能也是受害者,比如 AuthRevision 和資料的不一致都是由同一個原因導致的,只不過是 AuthRevision 的不一致放大了資料不一致的問題。但是,為更進一步接近真相,我們先假設 AuthRevision 就是導致資料不一致的罪魁禍首,進而找出導致 AuthRevision 不一致的真實原因。

原因到底如何去找呢?正所謂,原始碼之前了無祕密,我們首先想到了分析程式碼。於是,我們走讀了一遍 Auth 操作相關的程式碼(如下),發現只有在進行許可權相關的寫操作(如增刪使用者/角色,為角色授權等操作)時,AuthRevision 才會增加。AuthRevision 增加後,會和寫許可權操作一起,寫入 backend 快取,當寫操作超過一定閾值(預設 10000 條記錄)或者每隔100ms(預設值),會執行刷盤操作寫入 db。由於 AuthRevision 的持久化和建立使用者等操作的持久化放在一個事務內,因此基本不會存在建立使用者成功了,而 AuthRevision 沒有正常增加的情況。

func (as *authStore) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {    // ...    tx := as.be.BatchTx()    tx.Lock()    defer tx.Unlock()  // Unlock時滿足條件會觸發commit操作    // ...    putUser(tx, newUser)    as.commitRevision(tx)    return &pb.AuthUserAddResponse{}, nil}func (t *batchTxBuffered) Unlock() {    if t.pending != 0 {        t.backend.readTx.Lock() // blocks txReadBuffer for writing.        t.buf.writeback(&t.backend.readTx.buf)        t.backend.readTx.Unlock()        if t.pending >= t.backend.batchLimit {            t.commit(false)        }    }    t.batchTx.Unlock()}

那麼,既然 3 個節點的 AuthRevision 不一致,會不會是因為某些節點寫許可權相關的操作丟失了,從而沒有寫入 db ?如果該猜測成立,3 個節點的 db 裡 authUser 和 authRole 的 bucket 內容應該有所不同才對。於是為進一步驗證,我們繼續修改了下 etcd-dump-db 這個工具,加入了對比不同 db 檔案 bucket 內容的功能。遺憾的是,通過對比發現,3 個節點之間的 authUser 和 authRole bucket 的內容並沒有什麼不同。

既然節點寫許可權相關的操作沒有丟,那會不會是命令重複執行了呢?檢視異常時間段內日誌時發現,其中包含了較多的 auth 操作;進一步分別比對 3 個節點的 auth 操作相關的日誌又發現,部分節點日誌較多,而部分節點日誌較少,看起來像是存在命令重複執行現象。由於日誌壓縮,雖然暫時還不能確定是重複執行還是操作丟失,但是這些資訊能夠為我們後續的排查帶來很大啟發。

我們繼續觀察發現,不同節點之間的 AuthRevison雖有差異,但是差異較小,而且差異值在我們壓測期間沒有變過。既然不同節點之間的 AuthRevision 差異值沒有進一步放大,那麼通過新增的日誌基本上也看不出什麼問題,因為不一致現象的出現很可能是在過去的某個時間點瞬時造成的。這就造成我們如果想要發現問題根因,還是要能夠復現 AuthRevison 不一致或者資料不一致問題才行,並且要能夠抓到復現瞬間的現場。

問題似乎又回到了原點,但好在我們目前已經排除了很多干擾資訊,將目標聚焦在了 auth 操作上

混沌工程,成功復現

鑑於之前多次手動模擬各種場景都沒能成功復現,我們打算搞一套自動化的壓測方案來複現這個問題,方案制定時主要考慮的點有以下幾個:

  • 如何增大復現的概率?

    根據之前的排查結果,很有可能是 auth 操作導致的資料不一致,因此我們實現了一個 monkey 指令碼,每隔一段時間,會向叢集寫入隨機的使用者、角色,並向角色授權,同時進行寫資料操作,以及隨機的重啟叢集中的節點,詳細記錄每次一操作的時間點和執行日誌。

  • 怎樣保證在復現的情況下,能夠儘可能的定位到問題的根因?

    根據之前的分析得出,問題根因大概率是 apply 過程中出了問題,於是我們在 apply 的流程里加入了詳細的日誌,並列印了每次 apply 操作committedIndex、appliedIndex、consistentIndex 等資訊。

  • 如果復現成功,如何能夠在第一時間發現?

    由於日誌量太大,只有第一時間發現問題,才能夠更精確的縮小問題範圍,才能更利於我們定位問題。於是我們實現了一個簡單的 metric-server,每隔一分鐘拉取各個節點的 key 數量,並進行對比,將差異值暴露為 metric,通過 prometheus 進行拉取,並用 grafana 進行展示,一旦差異值超過一定閾值(寫入資料量大的情況下,就算併發統計各個節點的 key 數量,也可能會有少量的差異,所以這裡有一個容忍誤差),則立刻通過統一告警平臺向我們推送告警,以便於及時發現。

方案搞好後,我們新建了一套 etcd 叢集,部署了我們的壓測方案,打算進行長期觀察。結果第二天中午,我們就收到了微信告警——叢集中存在資料不一致現象。

於是,我們立刻登入壓測機器進行分析,首先停掉了壓測指令碼,然後檢視了叢集中各個節點的 AuthRevision,發現 3 個節點的 AuthRevision 果然不一樣!根據 grafana 監控皮膚上的監控資料,我們將資料不一致出現的時間範圍縮小到了 10 分鐘內,然後重點分析了下這 10 分鐘的日誌,發現在某個節點重啟後,consistentIndex 的值比重啟前要小。然而 etcd 的所有 apply 操作,冪等性都依賴 consistentIndex 來保證,當進行 apply 操作時,會判斷當前要 apply 的 Entry 的 Index 是否大於 consistentIndex,如果 Index 大於 consistentIndex,則會將 consistentIndex 設為 Index,並允許該條記錄被 apply。否則,則認為該請求被重複執行了,不會進行實際的 apply 操作。

// applyEntryNormal apples an EntryNormal type raftpb request to the EtcdServerfunc (s *EtcdServer) applyEntryNormal(e *raftpb.Entry) {    shouldApplyV3 := false    if e.Index > s.consistIndex.ConsistentIndex() {        // set the consistent index of current executing entry        s.consistIndex.setConsistentIndex(e.Index)        shouldApplyV3 = true    }        // ...        // do not re-apply applied entries.    if !shouldApplyV3 {        return    }        // ...}

也就是說,由於 consistentIndex 的減小,etcd 本身依賴它的冪等操作將變得不再冪等,導致許可權相關的操作在 etcd 重啟後被重複 apply 了,即一共apply 了兩次!

問題原理分析

為何 consistentIndex 會減小?走讀了 consistentIndex 相關程式碼後,我們終於發現了問題的根因:consistentIndex 本身的持久化,依賴於 mvcc 的寫資料操作;通過 mvcc 寫入資料時,會呼叫 saveIndex 來持久化 consistentIndex 到 backend,而 auth 相關的操作,是直接讀寫的 backend,並沒有經過 mvcc。這就導致,如果做了許可權相關的寫操作,並且之後沒有通過 mvcc 寫入資料,那麼這期間 consistentIndex 將不會被持久化,如果這時候重啟了 etcd,就會造成許可權相關的寫操作被 apply 兩次,帶來的副作用可能會導致 AuthRevision 重複增加,從而直接造成不同節點的 AuthRevision 不一致,而 AuthRevision 不一致又會造成資料不一致。

func putUser(lg *zap.Logger, tx backend.BatchTx, user *authpb.User) {    b, err := user.Marshal()    tx.UnsafePut(authUsersBucketName, user.Name, b) // 直接寫入Backend,未經過MVCC,此時不會持久化consistentIndex}func (tw *storeTxnWrite) End() {    // only update index if the txn modifies the mvcc state.    if len(tw.changes) != 0 {        tw.s.saveIndex(tw.tx)    // 當通過MVCC寫資料時,會觸發consistentIndex持久化        tw.s.revMu.Lock()        tw.s.currentRev++    }    tw.tx.Unlock()    if len(tw.changes) != 0 {        tw.s.revMu.Unlock()    }    tw.s.mu.RUnlock()}

再回過頭來,為什麼資料不一致了還可以讀出來,而且讀出來的資料還可能不一樣?etcd 不是使用了 raft 演算法嗎,難道不能夠保證強一致性嗎?其實這和 etcd 本身的讀操作實現有關。

而影響 ReadIndex 操作的,一個是 leader 節點的 CommittedIndex,一個是當前節點的 AppliedIndex,etcd 在 apply 過程中,無論 apply 是否成功,都會更新 AppliedIndex,這就造成,雖然當前節點 apply 失敗了,但讀操作在判斷的時候,並不會感知到這個失敗,從而導致某些節點可能讀不出來資料;而且 etcd 支援多版本併發控制,同一個 key 可以存在多個版本的資料,apply 失敗可能只是更新某個版本的資料失敗,這就造成不同節點之間最新的資料版本不一致,導致讀出不一樣的資料。

影響範圍

該問題從 2016 年引入,所有開啟鑑權的 etcd3 叢集都會受到影響,在特定場景下,會導致 etcd 叢集多個節點之間的資料不一致,並且 etcd 對外表現還可以正常讀寫,日誌無明顯報錯。

觸發條件

  1. 使用的為 etcd3 叢集,並且開啟了鑑權。

  2. etcd 叢集中節點發生重啟。

  3. 節點重啟之前,有 grant-permission 操作(或短時間內對同一個許可權操作連續多次增刪),且操作之後重啟之前無其他資料寫入。

  4. 通過非重啟節點向叢集發起寫資料請求。

修復方案

瞭解了問題的根因,修復方案就比較明確了,我們只需要在 auth 操作呼叫 commitRevision 後,觸發 consistentIndex 的持久化操作,就能夠保證 etcd 在重啟的時候 consistentIndex 的本身的正確性,從而保證 auth 操作的冪等性。具體的修復方式我們已經向 etcd 社群提交了 PR #11652,目前這個特性已經 backport 到 3.4,3.3 等版本,將會在最近一個 release 更新。

那麼如果資料已經不一致了怎麼辦,有辦法恢復嗎?在 etcd 程式沒有頻繁重啟的情況下,可以先找到 authRevision 最小的節點,它的資料應該是最全的,然後利用 etcd 的 move-leader 命令,將 leader 轉移到這個節點,再依次將其他節點移出叢集,備份並刪除資料目錄,然後將節點重新加進來,此時它會從 leader 同步一份最新的資料,這樣就可以使叢集其他節點的資料都和 leader 保持一致,即最大可能地不丟資料。

升級建議

需要注意的是,升級有風險,新版本雖然解決了這個問題,但由於升級過程中需要重啟 etcd,這個重啟過程仍可能觸發這個 bug。因此升級修復版本前建議停止寫許可權相關操作,並且手動觸發一次寫資料操作之後再重啟節點,避免因為升級造成問題。

另外,不建議直接跨大版本升級,例如從 etcd3.2 → etcd3.3。大版本升級有一定的風險,需謹慎測試評估,我們之前發現過由 lease 和 auth 引起的另一個不一致問題,具體可見 issue #11689,以及相關 PR #11691

問題總結

導致該問題的直接原因,是由於 consistentIndex 在進行許可權相關操作時未持久化,從而導致 auth 相關的操作不冪等,造成了資料的不一致。

而造成 auth 模組未持久化 consistentIndex 的一個因素,是因為 consistentIndex 目前是在 mvcc 模組實現的,並未對外暴露持久化介面,只能通過間接的方式來呼叫,很容易漏掉。為了優化這個問題,我們重構了 consistentIndex 相關操作,將它獨立為一個單獨的模組,其他依賴它的模組可以直接呼叫,一定程度上可以避免將來再出現類似問題,具體見 PR #11699

另一方面,etcd 的 apply 操作本身是個非同步流程,而且失敗之後沒有列印任何錯誤日誌,很大程度上增加了排障的難度,因此我們後邊也向社群提交了相關 PR #11670,來優化 apply 失敗時的日誌列印,便於定位問題。

造成問題定位困難的另一個原因,是 auth revision 目前沒有對外暴露 metric 或者 api,每次查詢只能通過 [etcd-dump-db 工具來直接從 db 獲取,為方便 debug,我們也增加了相關的

metric](https://github.com/etcd-io/etcd/pull/11652/commits/f14d2a087f7b0fd6f7980b95b5e0b945109c95f3) 和 status api,增強了 auth revision 的可觀測性和可測試性。

參考資料

  1. etcd 原始碼:https://github.com/etcd-io/etcdetcd
  2. 儲存實現:https://www.codedump.info/post/20181125-etcd-server/高可用分佈儲存
  3. etcd 的實現原理:https://draveness.me/etcd-introduction/etcd
  4. raft 設計與實現:https://zhuanlan.zhihu.com/p/51063866,https://zhuanlan.zhihu.com/p/51065416

致謝

感謝在 PR 提交過程中,etcd 社群 jingyih,mitake 的熱心幫助和建議!

【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多幹貨!!

相關文章