大家好,我是此林。
etcd 是一個高可用的鍵值對存儲系統,常用于分布式系統中保存配置、服務發現和協調信息。它是 CNCF 旗下的項目之一,也是 Kubernetes 的核心組件之一,用來存儲集群狀態。
可以說,云原生場景下經常使用到 etcd,一般我們會把 etcd 作為注冊中心來使用。
1. etcd 是什么?
etcd 單詞可以拆分成 etc + d,etc 就是 Linux 里的 /etc 目錄,d 就是 distribution,代表分布式。
-
使用 Raft 一致性算法保證分布式系統數據一致性(強一致性、保障了CP)
-
提供 鍵值對存儲接口(KV)
-
支持 Watch 機制(監聽鍵變化)
-
提供 Lease / TTL(用于臨時鍵,比如服務注冊)
2. etcd 常見使用場景
場景 | 說明 |
---|---|
配置中心 | 分布式系統中的配置動態變更通知 |
服務發現 | 服務注冊自己的信息,客戶端從 etcd 查詢 |
分布式鎖 | 利用 etcd 的原子操作實現分布式鎖 |
領導選舉 | 通過 Lease 和 Watch 實現 leader 的自動選舉和變更通知 |
Kubernetes 存儲 | K8s 所有 API 對象最終存儲在 etcd 中 |
3. etcd 安裝
這里使用版本 3.5.21。
Release v3.5.21 · etcd-io/etcd
下載完成后,上傳到 Linux 服務器,使用下面的命令解壓。
tar -zxvf etcd-v3.5.21-linux-amd64.tar.gz
進入目錄后,其實最核心的也就是三個二進制可執行文件,etcd(主服務Server端),etcdctl(etcd客戶端連接工具),etcdutl(etcd工具集)。
其他都是些文檔。
把這三個二進制文件復制到 /usr/local/bin 里。
# 創建目標目錄(如果不存在)
sudo mkdir -p /usr/local/bin# 賦予可執行權限
chmod +x etcd etcdctl etcdutl# 復制到系統路徑
sudo cp etcd etcdctl etcdutl /usr/local/bin/
?配置環境變量,/usr/local/bin 加入 /etc/profile 文件末尾。
vim /etc/profile
文件末尾有這一行就行。
export PATH=$PATH:/usr/local/bin
使環境變量生效。
source /etc/profile
4. 快速開始
輸入 etcd 啟動 etcd 服務端。
打開另一個終端,輸入命令:
etcdctl put greeting "hello, etcd"etcdctl get greeting
當然,也可以格式化輸出為 json。
etcdctl --endpoints=$ENDPOINTS --write-out="json" get foo
?輸出:
{"header":{"cluster_id":289318470931837780,"member_id":14947050114012957595,"revision":3,"raft_term":4, "kvs":[{"key":"Zm9v","create_revision":2,"mod_revision":3,"version":2,"value":"SGVsbG8gV29ybGQh"}]}}
看到這里,恭喜你,已經 etcd 入門了!?
5. 角色用戶配置
下面是官方文檔給出的配置示例,不急,我們慢慢來看。
1.?設置環境變量
export ETCDCTL_API=3
# 設置使用 etcdctl 的 v3 API(新版 etcd 默認使用 v3)
# 這個只在當前shell生效,重啟會失效,永久設置要在/etc/profile里ENDPOINTS=localhost:2379
# 設置 etcd 訪問地址(如果是集群也可寫多個:127.0.0.1:2379,127.0.0.2:2379)
2.?創建 root 角色并查看
etcdctl --endpoints=${ENDPOINTS} role add root
# 添加一個名為 root 的角色etcdctl --endpoints=${ENDPOINTS} role get root
# 查看 root 角色的權限信息(剛創建時是空的)
可以發現,root 角色有鍵值對讀寫權限。?
3.?創建 root 用戶并綁定 root 角色
etcdctl --endpoints=${ENDPOINTS} user add root
# 添加一個用戶 root(執行后會提示你輸入密碼)etcdctl --endpoints=${ENDPOINTS} user grant-role root root
# 給用戶 root 分配 root 角色etcdctl --endpoints=${ENDPOINTS} user get root
# 查看用戶 root 的角色信息
4.?創建 role0 角色并設置權限,創建 user0 用戶并賦予角色
etcdctl --endpoints=${ENDPOINTS} role add role0
# 添加一個角色 role0etcdctl --endpoints=${ENDPOINTS} role grant-permission role0 readwrite foo
# 給角色 role0 授予對 key "foo" 的讀寫權限(readwrite)
# 你也可以設定范圍權限,例如:--prefixetcdctl --endpoints=${ENDPOINTS} user add user0
# 添加用戶 user0,會提示輸入密碼etcdctl --endpoints=${ENDPOINTS} user grant-role user0 role0
# 將角色 role0 分配給用戶 user0
?這里,我們使用?grant-permission 對 role0 的權限進行了修改,role0 只有對 key "foo" 才有讀寫權限
5.?啟用認證
etcdctl --endpoints=${ENDPOINTS} auth enable
# 啟用 etcd 的認證功能
# 啟用后,所有的讀寫請求必須提供用戶名和密碼,否則會被拒絕
6.?權限驗證測試
etcdctl --endpoints=${ENDPOINTS} --user=user0:123 put foo bar
# 使用 user0 用戶(密碼是 123)寫入 key "foo",寫入成功(因為具有讀寫權限)etcdctl --endpoints=${ENDPOINTS} get foo
# 不帶用戶名直接訪問 key "foo",失敗(因為認證開啟了,默認匿名用戶無權限)etcdctl --endpoints=${ENDPOINTS} --user=user0:123 get foo
# 使用 user0 讀取 key "foo",成功(因為擁有讀權限)etcdctl --endpoints=${ENDPOINTS} --user=user0:123 get foo1
# 使用 user0 讀取 key "foo1",失敗(權限只對 foo 有效,foo1 不在授權范圍)
可以看到,user0 對 key foo1,沒有讀寫權限。
上面這個示例建議親手去敲一遍,有助于快速理解角色-用戶模型。
6. 集群搭建
6.1. 方法一:靜態集群配置(推薦用于生產)
在 /etc/etcd 目錄下創建三個配置文件(這里就用一臺機器上不同端口來模擬集群)
etcd-node1.yaml
name: node1
data-dir: /tmp/etcd1listen-peer-urls: http://127.0.0.1:2380
listen-client-urls: http://127.0.0.1:2379initial-advertise-peer-urls: http://127.0.0.1:2380
advertise-client-urls: http://127.0.0.1:2379initial-cluster: node1=http://127.0.0.1:2380,node2=http://127.0.0.1:3380,node3=http://127.0.0.1:4380
initial-cluster-token: local-etcd-cluster
initial-cluster-state: new
etcd-node2.yaml
name: node2
data-dir: /tmp/etcd2listen-peer-urls: http://127.0.0.1:3380
listen-client-urls: http://127.0.0.1:3379initial-advertise-peer-urls: http://127.0.0.1:3380
advertise-client-urls: http://127.0.0.1:3379initial-cluster: node1=http://127.0.0.1:2380,node2=http://127.0.0.1:3380,node3=http://127.0.0.1:4380
initial-cluster-token: local-etcd-cluster
initial-cluster-state: new
etcd-node3.yaml
name: node3
data-dir: /tmp/etcd3listen-peer-urls: http://127.0.0.1:4380
listen-client-urls: http://127.0.0.1:4379initial-advertise-peer-urls: http://127.0.0.1:4380
advertise-client-urls: http://127.0.0.1:4379initial-cluster: node1=http://127.0.0.1:2380,node2=http://127.0.0.1:3380,node3=http://127.0.0.1:4380
initial-cluster-token: local-etcd-cluster
initial-cluster-state: new
集群啟動?
etcd --config-file=/etc/etcd/etcd-node1.yaml
在每個終端分別執行命令。
參數說明:
1. initial-cluster-token: local-etcd-cluster
etcd 集群的初始化 token,用于標識一個新的集群。所有節點必須使用相同的 token 才能加入該集群。
2. initial-cluster-state: new
指定 etcd 是啟動一個新的集群(new),還是加入一個已有的集群(existing)。
new:表示初始化新的 etcd 集群(初次部署)
existing:表示已有集群的成員(用于節點重啟或擴容)
3.?name: node3
分別為三個 etcd 節點指定唯一的名稱,后面用于集群識別。
4.?data-dir: /tmp/etcd3
數據存儲目錄,etcd會在此目錄保存所有數據
# 監聽peer通信的URL(集群節點間內部通信)
listen-peer-urls: http://127.0.0.1:4380# 監聽客戶端請求的URL(應用程序連接etcd的地址)
listen-client-urls: http://127.0.0.1:4379# 向集群其他節點宣告的peer通信地址
# 必須可從其他節點訪問,通常與listen-peer-urls一致
initial-advertise-peer-urls: http://127.0.0.1:4380# 向客戶端宣告的服務地址
# 必須可從客戶端訪問,通常與listen-client-urls一致
advertise-client-urls: http://127.0.0.1:4379# 初始集群配置(集群所有節點的通信地址)
initial-cluster: node1=http://127.0.0.1:2380,node2=http://127.0.0.1:3380,node3=http://127.0.0.1:4380
6.2?方法二:基于 discovery token 的動態發現(適合臨時集群)
簡單來說,方法二適合部署時不提前知道所有成員 IP 的情況。
這里的 discovery
URL 每次使用都要新生成(不可復用)!
具體可以參考官方文檔,這里不多演示。
How to Set Up a Demo etcd Cluster | etcd
curl https://discovery.etcd.io/new?size=3
https://discovery.etcd.io/a81b5818e67a6ea83e9d4daea5ecbc92# grab this token
TOKEN=token-01
CLUSTER_STATE=new
NAME_1=machine-1
NAME_2=machine-2
NAME_3=machine-3
HOST_1=10.240.0.17
HOST_2=10.240.0.18
HOST_3=10.240.0.19
DISCOVERY=https://discovery.etcd.io/a81b5818e67a6ea83e9d4daea5ecbc92THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
etcd --data-dir=data.etcd --name ${THIS_NAME} \--initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \--advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \--discovery ${DISCOVERY} \--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}THIS_NAME=${NAME_2}
THIS_IP=${HOST_2}
etcd --data-dir=data.etcd --name ${THIS_NAME} \--initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \--advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \--discovery ${DISCOVERY} \--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}THIS_NAME=${NAME_3}
THIS_IP=${HOST_3}
etcd --data-dir=data.etcd --name ${THIS_NAME} \--initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \--advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \--discovery ${DISCOVERY} \--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}
現在 etcd 啟動了,開始測試吧!
export ETCDCTL_API=3
ENDPOINTS=127.0.0.1:2379,127.0.0.1:3379,127.0.0.1:4379etcdctl --endpoints=$ENDPOINTS endpoint status --write-out=table
可以看到,測試成功了,列出了所有節點。node1 是 leader,node2和node3是 follower。
6.3. 集群模式:Raft 共識算法
etcd 是一個 強一致性(strongly consistent) 的分布式鍵值存儲系統,但它不是傳統意義上的主從結構,而是基于 Raft 共識算法 構建的。
etcd 集群基于 Raft 算法,每個節點(member)在某一時刻的角色可能是以下之一:
角色 | 含義 |
---|---|
Leader | 當前集群的主節點,負責接收客戶端寫請求,并將數據復制給 Follower。整個集群 只能有一個 Leader。 |
Follower | 普通成員節點,響應來自 Leader 的日志復制和心跳,不能單獨處理寫入。 |
Candidate | 候選者,用于發起 Leader 選舉的過渡角色。 |
這個就解決了傳統主從集群模式下主節點宕機了,整個集群就不可用的問題。etcd 集群可以自動進行故障恢復,自動投票選出新的 master 節點。
etcd 的一致性模型是:
線性一致性(Linearizability):所有客戶端看到的順序與 Raft 日志順序一致。
也就是說:
-
所有寫操作必須通過 Leader;
-
Leader 會將寫請求復制到多數派(超過半數)節點,才會被“提交”(解決了集群腦裂)
etcd(Raft) | 傳統主從 | |
---|---|---|
節點角色 | Leader + Follower + Candidate | Master + Slave |
容錯方式 | 多數節點存活即可服務(如 3 節點需 ≥2 存活) | Master 掛了要人工或腳本切換 |
數據復制 | 同步復制,多數派一致后才提交 | 主寫從讀,通常異步復制 |
一致性 | 強一致性(線性一致) | 最終一致性(可能存在延遲) |
再看下之前的圖:
字段名稱 | 說明 | 示例值 | 重要性 |
---|---|---|---|
??ID?? | 節點的唯一標識符(16進制格式) | 84724583e7fe06d8 | 用于識別集群中的特定節點 |
??VERSION?? | etcd 服務器版本號 | 3.5.21 | 集群所有節點版本應一致 |
??DB SIZE?? | 后端數據庫的物理大小 | 20 kB | 監控存儲增長的關鍵指標 |
??IS LEADER?? | 當前是否為領導者節點 | true /false | 集群健康關鍵指標(必須有且只有一個leader) |
??IS LEARNER?? | 是否為學習者節點(非投票成員) | false | 學習者節點不參與選舉,用于災備或讀擴展 |
??RAFT TERM?? | 當前任期號(每次選舉遞增) | 2 | 數值越大表示集群經歷的領導選舉次數越多 |
??RAFT INDEX?? | 當前最高日志條目索引號 | 9 | 表示寫入操作的序列號 |
??RAFT APPLIED INDEX?? | 已應用到狀態機的最高日志索引 | 9 | 應與RAFT INDEX接近,差值大表示有未應用的操作 |
??ERRORS?? | 節點錯誤信息(為空表示正常) | `` | 出現內容時表示節點異常 |
7. 其他功能特性
7.1 prefix 前綴
1. 通過 prefix 前綴查找 key
2. 通過 --preifx 刪除 key
etcdctl --endpoints=$ENDPOINTS put k1 value1
etcdctl --endpoints=$ENDPOINTS put k2 value2
etcdctl --endpoints=$ENDPOINTS del k --prefix
7.2 etcd 事務
因為 etcd 本身就是 raft 強一致性的中間件,保證了 CAP 理論里的 CP。etcd 無論單機還是集群都支持事務,它內部使用了 MVCC 多版本并發控制機制。
Redis 保證了 CAP 理論里的 AP,屬于最終一致性。
Redis 通過 multi + watch 命令并?不能保障事務,Redis 官方文檔里說 Redis 設計就是為了高性能,一致性保障并不是最主要的。
??
MULTI
+WATCH
?的缺陷??
- 僅提供樂觀鎖(CAS),若?
WATCH
?的鍵被其他客戶端修改,事務會失敗。- 無回滾機制,失敗后需手動重試。
所以 Redis 里實現事務只能通過 Lua 腳本?來實現。
??Lua 腳本的原子性??
- 雖然單線程執行 Lua 能保證原子性,但:不支持跨鍵事務(所有操作必須在同一個腳本中)
- 也就是說 cluster 分片集群模式下,lua 腳本可能無法保障事務。
場景舉例:
你要同時更新一個用戶的郵箱和手機號
# 插入數據
etcdctl put /users/12345/email "old.address@johndoe.com"
etcdctl put /users/12345/phone "123-456-7890"
# 開啟事務
etcdctl txn --interactivecompares:
# 這里除了用value(),也可以使用version()版本號機制
value("/users/12345/email") = "old.address@johndoe.com"success requests (get, put, delete):
put /users/12345/email "new.address@johndoe.com"
put /users/12345/phone "098-765-4321"failure requests (get, put, delete):
get /users/12345/email
測試:?
1. Atomicity(原子性)
事務是原子的,要么所有操作都成功,要么一個都不執行。
只有 value("/users/12345/email") 等于?"old.address@johndoe.com"時,郵箱和手機號才會一起更新。 如果不是,則不會更新任何內容。
這保證了不會出現只更新了一半字段的尷尬情況。
2.?Consistency(一致性)
3. 注意點:避免在同一事務中對同一個 key 多次 put
? 錯誤示例:
# compares:
value(counter) = "1"# success requests:
put counter 2
put counter 3 # 同一個事務里又對 counter 執行一次 put,會導致沖突或不可預期結果
這種情況雖然可能不會報錯,但有時你不知道最終會落哪個值。etcd 沒法保證順序處理多次對同一個 key 的寫入。
? 正確方式:
每個事務里對同一個 key 最多只能 put 一次。
7.3. watch 實時監聽 key
etcdctl watch stock1
7.4. 設置 lease 租約
類似 redis 的 TTL 機制,但更強大(支持一個 leaseID 多鍵綁定、續租等)。
# 1. 創建一個 TTL=300秒 的租約(返回 Lease ID)
etcdctl lease grant 300
→ lease 2be7547fbc6a5afa granted with TTL(300s)# 2. 將鍵值對 "sample=value" 綁定到這個租約
etcdctl put sample value --lease=2be7547fbc6a5afa# 3. 獲取鍵值對(此時可以正常讀取)
etcdctl get sample
→ sample
→ value# 4. 手動續租(保持 Lease 存活,即重置為300秒)
etcdctl lease keep-alive 2be7547fbc6a5afa# 5. 主動撤銷租約(立即刪除所有關聯的鍵)
etcdctl lease revoke 2be7547fbc6a5afa# 6. 等待 300秒后(或撤銷后),鍵值對自動消失
etcdctl get sample
→ (無輸出,鍵已被刪除)
7.5. 分布式鎖
# 客戶端1
etcdctl lock lock1# 客戶端2,此時會阻塞,直到客戶端1釋放鎖
etcdctl lock lock1
7.6. 健康檢測
etcdctl endpoint status (--endpoints=$ENDPOINTS|--cluster)etcdctl endpoint health (--endpoints=$ENDPOINTS|--cluster)
關于etcd的分享就到這里了。
我是此林,關注我吧,帶你看不一樣的世界!?