磁盤 IO
除了網絡延遲,磁盤 IO 也嚴重影響 etcd 的穩定性, etcd需要持久化數據,對磁盤速度很敏感,強烈建議對 ETCD 的數據掛 SSD。
另外,要確認機器上沒有其他高 IO 操作,否則會影響 etcd 的 fsync,導致 etcd 丟失心跳,leader更換等。一般磁盤有問題時,報錯的關鍵字類似於:
took too long (1.483848046s) to execute etcdserver: failed to send out heartbeat on time
磁盤 IO 可以通過監控手段提前發現,並預防這類問題的出現
快照
etcd的存儲分為內存存儲和持久化(硬盤)存儲兩部分,內存中的存儲除了順序化的記錄下所有用戶對節點數據變更的記錄外,還會對用戶數據進行索引、建堆等方便查詢的操作。而持久化則使用預寫式日志(WAL:Write Ahead Log)進行記錄存儲。
在WAL的體系中,所有的數據在提交之前都會進行日志記錄。在etcd的持久化存儲目錄中,有兩個子目錄。一個是WAL,存儲着所有事務的變化記錄;另一個則是snapshot,用於存儲某一個時刻etcd所有目錄的數據。通過WAL和snapshot相結合的方式,etcd可以有效的進行數據存儲和節點故障恢復等操作。
既然有了WAL實時存儲了所有的變更,為什么還需要snapshot呢?隨着使用量的增加,WAL存儲的數據會暴增,為了防止磁盤很快就爆滿,etcd默認每10000條記錄做一次snapshot,經過snapshot以后的WAL文件就可以刪除。而通過API可以查詢的歷史etcd操作默認為1000條。
客戶端優化
etcd 的客戶端應該避免一些頻繁操作或者大對象操作,如:
- put 時避免大 value,精簡再精簡(例如 k8s 中 crd 使用)
- 避免創建頻繁變化的 kv(例如 k8s 中 node 信息匯報),如 node-lease
- 避免創建大量 lease,盡量選擇復用(例如 k8s 中 event 數據管理)
- 合理利用 apiserver 中的緩存,避免大量請求打到 etcd上,如集群異常恢復后大量 pod同步
其他
你可能還看到過lease revoke 、boltdb、內存優化等方式,這些已經合入了最新的 etcd3.4版本,因此選擇最新的 release 版本也是提高穩定性的一種方式。
壓縮機制
Etcd作為 KV 存儲,會為每個 key 都保留歷史版本,比如用於發布回滾、配置歷史等。
對 demo 寫入值為 101,然后更為為 102,103。-w json 可以輸出這次寫入的 revision
etcdctl put demo 101 -w json etcdctl put demo 102 -w json etcdctl put demo 103 -w json 返回類似: {"header":{"cluster_id":4871617780647557296,"member_id":3135801277950388570,"revision":434841,"raft_term":2}}
取值:
etcdctl get demo 默認 --rev=0即最新值=103 如果要拿到歷史值,需要制定 rev 版本 etcdctl get demo --rev=434841,得到 102
觀察key的變化:
etcdctl watch foo --rev=0
歷史版本越多,存儲空間越大,性能越差,直到etcd到達空間配額限制的時候,etcd的寫入將會被禁止變為只讀,影響線上服務,因此這些歷史版本需要進行壓縮。
數據壓縮並不是清理現有數據,只是對給定版本之前的歷史版本進行清理,清理后數據的歷史版本將不能訪問,但不會影響現有最新數據的訪問。
手動壓縮
etcdctl compact 5。 在 5 之前的所有版本都會被壓縮,不可訪問 如果 etcdctl get --rev=4 demo,會報錯 Error: rpc error: code = 11 desc = etcdserver: mvcc: required revision has been compacted
手動操作畢竟繁瑣,Etcd提供了啟動參數 “–auto-compaction-retention” 支持自動壓縮 key 的歷史版本,以小時為單位
etcd --auto-compaction-retention=1 代表 1 小時壓縮一次
v3.3之上的版本有這樣一個規則:
如果配置的值小於1小時,那么就嚴格按照這個時間來執行壓縮;如果配置的值大於1小時,會每小時執行壓縮,但是采樣還是按照保留的版本窗口依然按照用戶指定的時間周期來定。
k8s api-server支持定期執行壓縮操作,其參數里面有這樣的配置:
– etcd-compaction-interval 即默認 5 分鍾一次
你可以在 etcd 中看到這樣的壓縮日志,5 分鍾一次:
Apr 25 11:05:20 etcd[2195]: store.index: compact 433912 Apr 25 11:05:20 etcd[2195]: finished scheduled compaction at 433912 (took 1.068846ms) Apr 25 11:10:20 etcd[2195]: store.index: compact 434487 Apr 25 11:10:20 etcd[2195]: finished scheduled compaction at 434487 (took 1.019571ms) Apr 25 11:15:20 etcd[2195]: store.index: compact 435063 Apr 25 11:15:20 etcd[2195]: finished scheduled compaction at 435063 (took 1.659541ms) Apr 25 11:20:20 etcd[2195]: store.index: compact 435637 Apr 25 11:20:20 etcd[2195]: finished scheduled compaction at 435637 (took 1.676035ms) Apr 25 11:25:20 etcd[2195]: store.index: compact 436211 Apr 25 11:25:20 etcd[2195]: finished scheduled compaction at 436211 (took 1.17725ms)
碎片整理
進行壓縮操作之后,舊的revision被清理,會產生內部的碎片,內部碎片是指空閑狀態的,能被etcd使用但是仍然消耗存儲空間的磁盤空間,去碎片化實際上是將存儲空間還給文件系統。
# defrag命令默認只對本機有效 etcdctl defrag # 如果帶參數--endpoints,可以指定集群中的其他節點也做整理 etcdctl defrag --endpoints
如果etcd沒有運行,可以直接整理目錄中db的碎片
etcdctl defrag --data-dir <path-to-etcd-data-dir>
碎片整理會阻塞對etcd的讀寫操作,因此偶爾一次大量數據的defrag最好逐台進行,以免影響集群穩定性。
etcdctl執行后的返回 Finished defragmenting etcd member[https://127.0.0.1:2379]
存儲空間
Etcd 的存儲配額可保證集群操作的可靠性。如果沒有存儲配額,那么 Etcd 的性能就會因為存儲空間的持續增長而嚴重下降,甚至有耗完集群磁盤空間導致不可預測集群行為的風險。一旦其中一個節點的后台數據庫的存儲空間超出了存儲配額,Etcd 就會觸發集群范圍的告警,並將集群置於接受讀 key 和刪除 key 的維護模式。只有在釋放足夠的空間和消除后端數據庫的碎片之后,清除存儲配額告警,集群才能恢復正常操作。
啟動 etcd 時。–quota-backend-bytes 默認為 2G,2G 一般情況下是不夠用的,
你可以通過 etcdctl endpoint status 命令來查看當前的存儲使用量
在 3.4 版本中,etcd 的存儲容量得到了提高,你可以設置 100G 的存儲空間,當然並不是越大越好,key 存儲過多性能也會變差,根據集群規模適當調整。
另外,–max-request-bytes 限制了請求的大小,默認值是1572864,即1.5M。在某些場景可能會出現請求過大導致無法寫入的情況,可以調大到10485760即10M。
如果遇到空間不足,可以這樣操作:
# 獲取當前版本號 $ rev=$(ETCDCTL_API=3 etcdctl endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9]*') # 壓縮所有舊版本 $ ETCDCTL_API=3 etcdctl compact $rev # 去碎片化 $ ETCDCTL_API=3 etcdctl defrag # 取消警報 $ ETCDCTL_API=3 etcdctl alarm disarm # 測試通過 $ ETCDCTL_API=3 etcdctl put key0 1234
快照備份
etcd可以定期做備份、以保證數據更好的持久化。通過加載備份數據,etcd可以將集群恢復到具有已知良好狀態的時間點。
使用命令etcdctl:
etcdctl snapshot save backup.db etcdctl --write-out=table snapshot status backup.db +----------+----------+------------+------------+ | HASH | REVISION | TOTAL KEYS | TOTAL SIZE | +----------+----------+------------+------------+ | fe01cf57 | 10 | 7 | 2.1 MB | +----------+----------+------------+------------+
learner 角色
learner 是 etcd 3.4 版本中增加的新角色,類似於 zookeeper 的 observer, 不參與 raft 投票選舉。通過這個新角色的引入,降低了加入新節點時給老集群的額外壓力,增強了集群的穩定性。除此之外還可以使用它作為集群的熱備或服務一些讀請求。
舉例,如果 etcd集群需要加入一個新節點,新加入的 etcd 成員因為沒有任何數據,因此需要從 leader 那里同步數據,直到趕上領導者的日志為止。這樣就會導致 leader 的網絡過載,導致 leader 和 member 之間的心跳可能阻塞。然后就開始了新的leader選舉,也就是說,具有新成員的集群更容易受到領導人選舉的影響。領導者的選舉以及隨后向新成員的更新都容易導致一段時間的群集不可用,這種是不符合預期,風險也是很大的。
因此為了解決這個問題,raft 4.2.1 論文中介紹了一種新的節點角色:Learner。加入集群的節點不參與投票選舉,只接收 leader 的 replication message,直到與 leader 保持同步為止。
learner 在網絡分區等場景下的處理,可以詳細參考:https://etcd.io/docs/v3.3.12/learning/learner/
具體操作:
# 增加一個節點作為learner member add --learner # 當learner的日志趕上了leader的進度時,將learner提升為有投票權的成員,然后該成員將計入法定人數 member promote etcd server 會驗證 promote 請求以確保真實
在提升之前,learner僅充當備用節點,leader無法轉移給learner。learner拒絕客戶端讀寫(客戶端平衡器不應將請求路由到learner)
另外,etcd限制了集群可以擁有的learner總數,並避免了日志復制導致領導者過載。learner永遠不會自我提升。
etcd client v3
Etcd client v3是基於grpc實現的,而grpc又是基於http2.0實現的,借用了很多 http2的優勢如二進制通訊、多路復用等,因此整體上借用grpc的框架做地址管理、連接管理、負載均衡等,而底層對每個Etcd的server只需維持一個http2.0連接。
Etcd client v3實現了grpc中的Resolver接口,用於Etcd server地址管理。當client初始化或者server集群地址發生變更(可以配置定時刷新地址)時,Resolver解析出新的連接地址,通知grpc ClientConn來響應變更。
client v3的原理解析可以看這篇文章:https://www.jianshu.com/p/281b80ae619b
我們是用etcd client做應用的選主操作,可以看下這篇
這里提一下,最早的時候以為 kubernetes 中的 scheduler、controller-manager是基於 etcd 做選主的,client拿來直接用很方便。后來發現不是,kubernetes 是用搶占 endpoint 資源的方式實現選主邏輯,不依賴外部 etcd,這么想來也合理,嚴格來講,etcd 不是kubernetes的東西,不應該有太多依賴。
k8s 中 scheduler 的選主邏輯可以看這篇文章
問題排查
列幾個常遇到的 etcd 問題,后面監控部分會提到如何監測、預防這類問題
一個節點宕機
一個節點宕機,並不會影響整個集群的正常工作,慢慢修復。
- 移出該節點:etcdctl member remove xx
- 修復機器問題,刪除舊的數據目錄,重新啟動 etcd 服務
- 因為 etcd 的證書需要簽入所有節點 ip,因此這里的節點不能更改 ip,否則要全部重簽證書,重啟服務
- 重啟啟動 etcd 時,需要將配置中的 cluster_state改為:existing,因為是加入已有集群,不能用 new
- 加入 memeber: etcdctl member add xxx –peer-urls=https://x.x.x.x:2380
- 驗證:etcdctl endpoint status
遷移數據
如果你的集群需要更換所有的機器,包括更換 IP,那就得通過快照恢復的方式了
使用 etcdctl snapshot save 來保存現有數據,新集群更換后,使用 restore 命令恢復數據,在執行快照時會產生一個 hash 值,來標記快照內容后面恢復時用於校驗,如果你是直接復制的數據文件,可以–skip-hash-check 跳過這個檢查。
遷移集群會更換證書和端點,因此一定會影響上層服務,在遷移之前一定要做好新舊切換,如 apiserver 分批升級(會有部分數據不一致)、避免服務宕機時間過長等
failed to send out heartbeat on time
這個前面已經提過,大概率是因為磁盤性能不足,導致心跳失敗,更換 SSD 或者排查機器上高 IO 的進程
request ... took too long to execute 這類報錯也是同理
mvcc: database space exceeded
存儲空間不足,參考上面提到的清理和恢復步驟,或者提高存儲空間
endpoints問題
盡量不要使用lb 作為 etcd endpoints 配置,etcd client 是 grpc 訪問,請使用默認的 全量list ,客戶端做負載均衡的方式。詳細內容可以參考 grpc 負載均衡場景解析
監控
etcd 默認以/metrics的 path 暴露了監控數據,數據為 prometheus 標准格式。
通過 metric 數據可以配置出如下面板,一般我們關心的數據,或者說需要配置報警的內容:
- 是否有 leader:集群就不可用了
- leader 更換次數:一定時間內頻率過高一般是有問題,且leader 更換會影響到上層服務
- rpc 請求速率:即 qps,可以評估當前負載
- db 總大小:用於評估數據量、壓縮策略等
- 磁盤讀寫延遲:這個很關鍵,延遲過高會導致集群出現問題