etcd 服務入門指南

**Etcd 是一個使用一致性哈希算法 (Raft) 在分佈式環境下的 key/value 存儲服務。**利用 Etcd 的特性,應用程序可以在集羣中共享信息、配置或作服務發現,Etcd 會在集羣的各個節點中複製這些數據並保證這些數據始終正確。

System Requirements >= 8v CPU + 16GB RAM + 50GB SSD

安裝使用

靜態就是在配置服務之前已經知道了節點的地址和集羣的大小

############################
# Build the latest version
############################

# 1.下載項目並編譯
$ git clone https://github.com/etcd-io/etcd.git && cd etcd
$ ./build
To build a vendored etcd from the master branch via go get:

# 2.設置GOPATH環境變量export GOPATH='/Users/example/go'
$ go get -v go.etcd.io/etcd
$ go get -v go.etcd.io/etcd/etcdctl

# 3.啓動服務
$ ./bin/etcd
$ $GOPATH/bin/etcd

# 4.簡單使用
$ ./bin/etcdctl put foo bar
OK

部署單機單服務 (靜態)

##################################
# Running etcd in standalone mode
##################################

# 1.設置啓動的Node地址export NODE1='172.16.176.52'

# 2.創建一個邏輯存儲
$ docker volume create --name etcd-data

# 3.啓動etcd服務
# 正式的ectd端口是2379用於客戶端連接,而2380用於夥伴通訊
# --data-dir: 到數據目錄的路徑
# --initial-advertise-peer-urls: 集羣中節點間通訊的URL地址
# --listen-peer-urls: 集羣中節點間通訊的URL地址
# --advertise-client-urls: 客戶端監聽的URL地址
# --listen-client-urls: 客戶端監聽的URL地址
# --initial-cluster: 啓動初始化集羣配置
$ docker run -p 2379:2379 -p 2380:2380 --name etcd \
    --volume=etcd-data:/etcd-data \
    quay.io/coreos/etcd:latest \
    /usr/local/bin/etcd \
    --data-dir=/etcd-data --name node1 \
    --initial-advertise-peer-urls http://${NODE1}:2380 \
    --listen-peer-urls http://0.0.0.0:2380 \
    --advertise-client-urls http://${NODE1}:2379 \
    --listen-client-urls http://0.0.0.0:2379 \
    --initial-cluster node1=http://${NODE1}:2380

# 4.列出現在集羣中的服務狀態
$ etcdctl --endpoints=http://${NODE1}:2379 member list

部署分佈式集羣服務 (靜態)

################################
# Running a 3 node etcd cluster
################################

# node1
docker run -p 2379:2379 -p 2380:2380 --name etcd-node-1 \
  --volume=/var/lib/etcd:/etcd-data \
  quay.io/coreos/etcd:latest \
  /usr/local/bin/etcd \
  --data-dir=/etcd-data \
  --initial-advertise-peer-urls "http://10.20.30.1:2380" \
  --listen-peer-urls "http://0.0.0.0:2380" \
  --advertise-client-urls "http://10.20.30.1:2379" \
  --listen-client-urls "http://0.0.0.0:2379" \
  --initial-cluster "etcd-node-1=http://10.20.30.1:2380, etcd-node-2=http://10.20.30.2:2380, etcd-node-3=http://10.20.30.3:2380" \
  --initial-cluster-state "new" \
  --initial-cluster-token "my-etcd-token"

# node2
docker run -p 2379:2379 -p 2380:2380 --name etcd-node-2 \
  --volume=/var/lib/etcd:/etcd-data \
  quay.io/coreos/etcd:latest \
  /usr/local/bin/etcd \
  --data-dir=/etcd-data \
  --initial-advertise-peer-urls "http://10.20.30.2:2380" \
  --listen-peer-urls "http://0.0.0.0:2380" \
  --advertise-client-urls "http://10.20.30.2:2379" \
  --listen-client-urls "http://0.0.0.0:2379" \
  --initial-cluster "etcd-node-1=http://10.20.30.1:2380, etcd-node-2=http://10.20.30.2:2380, etcd-node-3=http://10.20.30.3:2380" \
  --initial-cluster-state "new" \
  --initial-cluster-token "my-etcd-token"

# node3
docker run -p 2379:2379 -p 2380:2380 --name etcd-node-3 \
  --volume=/var/lib/etcd:/etcd-data \
  quay.io/coreos/etcd:latest \
  /usr/local/bin/etcd \
  --data-dir=/etcd-data \
  --initial-advertise-peer-urls "http://10.20.30.3:2380" \
  --listen-peer-urls "http://0.0.0.0:2380" \
  --advertise-client-urls "http://10.20.30.3:2379" \
  --listen-client-urls "http://0.0.0.0:2379" \
  --initial-cluster "etcd-node-1=http://10.20.30.1:2380, etcd-node-2=http://10.20.30.2:2380, etcd-node-3=http://10.20.30.3:2380" \
  --initial-cluster-state "new" \
  --initial-cluster-token "my-etcd-token"

# run etcdctl using API version 3
docker exec etcd /bin/sh -c "export ETCDCTL_API=3 && /usr/local/bin/etcdctl put foo bar"

部署分佈式集羣服務

# 編輯docker-compose.yml文件
version: "3.6"

services:
  node1:
    image: quay.io/coreos/etcd
    volumes:
      - node1-data:/etcd-data
    expose:
      - 2379
      - 2380
    networks:
      cluster_net:
        ipv4_address: 172.16.238.100
    environment:
      - ETCDCTL_API=3
    command:
      - /usr/local/bin/etcd
      - --data-dir=/etcd-data
      - --name
      - node1
      - --initial-advertise-peer-urls
      - http://172.16.238.100:2380
      - --listen-peer-urls
      - http://0.0.0.0:2380
      - --advertise-client-urls
      - http://172.16.238.100:2379
      - --listen-client-urls
      - http://0.0.0.0:2379
      - --initial-cluster
      - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380
      - --initial-cluster-state
      - new
      - --initial-cluster-token
      - docker-etcd

  node2:
    image: quay.io/coreos/etcd
    volumes:
      - node2-data:/etcd-data
    networks:
      cluster_net:
        ipv4_address: 172.16.238.101
    environment:
      - ETCDCTL_API=3
    expose:
      - 2379
      - 2380
    command:
      - /usr/local/bin/etcd
      - --data-dir=/etcd-data
      - --name
      - node2
      - --initial-advertise-peer-urls
      - http://172.16.238.101:2380
      - --listen-peer-urls
      - http://0.0.0.0:2380
      - --advertise-client-urls
      - http://172.16.238.101:2379
      - --listen-client-urls
      - http://0.0.0.0:2379
      - --initial-cluster
      - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380
      - --initial-cluster-state
      - new
      - --initial-cluster-token
      - docker-etcd

  node3:
    image: quay.io/coreos/etcd
    volumes:
      - node3-data:/etcd-data
    networks:
      cluster_net:
        ipv4_address: 172.16.238.102
    environment:
      - ETCDCTL_API=3
    expose:
      - 2379
      - 2380
    command:
      - /usr/local/bin/etcd
      - --data-dir=/etcd-data
      - --name
      - node3
      - --initial-advertise-peer-urls
      - http://172.16.238.102:2380
      - --listen-peer-urls
      - http://0.0.0.0:2380
      - --advertise-client-urls
      - http://172.16.238.102:2379
      - --listen-client-urls
      - http://0.0.0.0:2379
      - --initial-cluster
      - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380
      - --initial-cluster-state
      - new
      - --initial-cluster-token
      - docker-etcd

volumes:
  node1-data:
  node2-data:
  node3-data:

networks:
  cluster_net:
    driver: bridge
    ipam:
      driver: default
      config:
      -
        subnet: 172.16.238.0/24
# 使用啓動集羣
docker-compose up -d


# 之後使用如下命令登錄到任一節點測試etcd集羣
docker exec -it node1 bash

# etcdctl member list
422a74f03b622fef, started, node1, http://172.16.238.100:2380, http://172.16.238.100:2379
ed635d2a2dbef43d, started, node2, http://172.16.238.101:2380, http://172.16.238.101:2379
daf3fd52e3583ffe, started, node3, http://172.16.238.102:2380, http://172.16.238.102:2379

etcd 常用配置參數

--name       #指定節點名稱
--data-dir   #指定節點的數據存儲目錄,用於保存日誌和快照
--addr       #公佈的 IP 地址和端口;默認爲 127.0.0.1:2379
--bind-addr   #用於客戶端連接的監聽地址;默認爲–addr 配置
--peers       #集羣成員逗號分隔的列表;例如 127.0.0.1:2380,127.0.0.1:2381
--peer-addr   #集羣服務通訊的公佈的 IP 地址;默認爲 127.0.0.1:2380
-peer-bind-addr  #集羣服務通訊的監聽地址;默認爲-peer-addr 配置
--wal-dir         #指定節點的 wal 文件的存儲目錄,若指定了該參數 wal 文件會和其他數據文件分開存儲
--listen-client-urls #監聽 URL;用於與客戶端通訊
--listen-peer-urls   #監聽 URL;用於與其他節點通訊
--initial-advertise-peer-urls  #告知集羣其他節點 URL
--advertise-client-urls  #告知客戶端 URL
--initial-cluster-token  #集羣的 ID
--initial-cluster        #集羣中所有節點
--initial-cluster-state new  #表示從無到有搭建 etcd 集羣
--discovery-srv  #用於 DNS 動態服務發現,指定 DNS SRV 域名
--discovery      #用於 etcd 動態發現,指定 etcd 發現服務的 URL

數據存儲

etcd 的數據存儲有點像 PG 數據庫的存儲方式

etcd 目前支持 V2 和 V3 兩個大版本,這兩個版本在實現上有比較大的不同,一方面是對外提供接口的方式,另一方面就是底層的存儲引擎,V2 版本的實例是一個純內存的實現,所有的數據都沒有存儲在磁盤上,而 V3 版本的實例就支持了數據的持久化。

我們都知道 etcd 爲我們提供了 key/value 的服務目錄存儲。

# 設置鍵值對
$ etcdctl set name escape

# 獲取方式
$ etcdctl get name
escape

使用 etcd 之後,我們會疑問數據都存儲到的那裏呢?數據默認會存放在 /var/lib/etcd/default/ 目錄。我們會發現數據所在的目錄,會被分爲兩個文件夾中,分別是 snap 和 wal 目錄。

# 目錄結構
$ tree /var/lib/etcd/default/
default
└── member
    ├── snap
    │   ├── 0000000000000006-0000000000046ced.snap
    │   ├── 0000000000000006-00000000000493fe.snap
    │   ├── 0000000000000006-000000000004bb0f.snap
    │   ├── 0000000000000006-000000000004e220.snap
    │   └── 0000000000000006-0000000000050931.snap
    └── wal
        └── 0000000000000000-0000000000000000.wal

使用 WAL 進行數據的存儲使得 etcd 擁有兩個重要功能,那就是故障快速恢復和數據回滾 / 重做。

既然有了 WAL 實時存儲了所有的變更,爲什麼還需要 snapshot 呢?隨着使用量的增加,WAL 存儲的數據會暴增。爲了防止磁盤很快就爆滿,etcd 默認每 10000 條記錄做一次 snapshot 操作,經過 snapshot 以後的 WAL 文件就可以刪除。而通過 API 可以查詢的歷史 etcd 操作默認爲 1000 條。

首次啓動時,etcd 會把啓動的配置信息存儲到 data-dir 參數指定的數據目錄中。配置信息包括本地節點的 ID、集羣 ID 和初始時集羣信息。用戶需要避免 etcd 從一個過期的數據目錄中重新啓動,因爲使用過期的數據目錄啓動的節點會與集羣中的其他節點產生不一致。所以,爲了最大化集羣的安全性,一旦有任何數據損壞或丟失的可能性,你就應該把這個節點從集羣中移除,然後加入一個不帶數據目錄的新節點。

Raft 算法

保證一致性的共識算法

在每一個分佈式系統中,etcd 往往都扮演了非常重要的地位,由於很多服務配置發現以及配置的信息都存儲在 etcd 中,所以整個集羣可用性的上限往往就是 etcd 的可用性,而使用 3 ~ 5 個 etcd 節點構成高可用的集羣往往都是常規操作。

正是因爲 etcd 在使用的過程中會啓動多個節點,如何處理幾個節點之間的分佈式一致性就是一個比較有挑戰的問題了。解決多個節點數據一致性的方案其實就是共識算法,etcd 使用的就是 Raft 共識算法。

Raft 從一開始就被設計成一個易於理解和實現的共識算法,它在容錯和性能上與 Paxos 協議比較類似,區別在於它將分佈式一致性的問題分解成了幾個子問題,然後一一進行解決。

每一個 Raft 集羣中都包含多個服務器,在任意時刻,每一臺服務器只可能處於 Leader、Follower 以及 Candidate 三種狀態;在處於正常的狀態時,集羣中只會存在一個 Leader 狀態,其餘的服務器都是 Follower 狀態。

所有的 Follower 節點都是被動的,它們不會主動發出任何的請求,只會響應 Leader 和 Candidate 發出的請求。對於每一個用戶的可變操作,都會被路由給 Leader 節點進行處理,除了 Leader 和 Follower 節點之外,Candidate 節點其實只是集羣運行過程中的一個臨時狀態。

Raft 集羣中的時間也被切分成了不同的幾個任期 (Term),每一個任期都會由 Leader 的選舉開始,選舉結束後就會進入正常操作的階段,直到 Leader 節點出現問題纔會開始新一輪的選擇。

每一個服務器都會存儲當前集羣的最新任期,它就像是一個單調遞增的邏輯時鐘,能夠同步各個節點之間的狀態,當前節點持有的任期會隨着每一個請求被傳遞到其他的節點上。Raft 協議在每一個任期的開始時都會從一個集羣中選出一個節點作爲集羣的 Leader 節點,這個節點會負責集羣中的日誌的複製以及管理工作。

我們將 Raft 協議分成三個子問題:節點選舉、日誌複製以及安全性

服務發現

服務發現是 etcd 服務的主要的用途之一

服務發現要解決的也是分佈式系統中最常見的問題之一,即在同一個分佈式集羣中的進程或服務,要如何才能找到對方並建立連接。本質上來說,服務發現就是想要了解集羣中是否有進程在監聽 UDP 或 TCP 端口,並且通過名字就可以查找和連接。要解決服務發現的問題,需要有下面三大支柱,缺一不可。

日常開發集羣管理功能中,如果要設計可以動態調整集羣大小。那麼首先就要支持服務發現,就是說當一個新的節點啓動時,可以將自己的信息註冊給 master,然後讓 master 把它加入到集羣裏,關閉之後也可以把自己從集羣中刪除。etcd 提供了很好的服務註冊與發現的基礎功,我們採用 etcd 來做服務發現時,可以把精力用於服務本身的業務處理上。

使用方法


etcd 在鍵的組織上採用了層次化的空間結構,類似於文件系統中目錄的概念,數據庫操作圍繞對鍵值和目錄的 CRUD 完整生命週期的管理。etcdctl 是一個命令行的客戶端,它提供了一下簡潔的命令,可理解爲命令工具集,可以方便我們在對服務進行測試或者手動修改數據庫內容。etcdctl 與其他 xxxctl 的命令原理及操作類似,如 systemctl 等等。

# 列出etcd集羣中的實例
$ etcdctl member list

# 添加etcd集羣中的實例
$ etcdctl member add <實例>

# 刪除etcd集羣中的實例
$ etcdctl member remove <實例>

# 更新etcd集羣中的實例
$ etcdctl member update <實例>
--name #指定節點名稱

災備恢復

Etcd 集羣備份和數據恢復以及優化運維

etcd 被設計爲能承受集羣自動從臨時失敗 (例如機器重啓) 中恢復,而且對於一個有 N 個成員的集羣能容許 (N-1)/2 的持續失敗。當一個成員持續失敗時,不管是因爲硬件失敗或者磁盤損壞,它丟失到集羣的訪問。如果集羣持續丟失超過 (N-1)/2 的成員,則它只能悲慘的失敗,無可救藥的失去法定人數(quorum)。一旦法定人數丟失,集羣無法達到一致性而導致無法繼續接收更新。爲了從災難失敗中恢復數據,etcd v3 提供快照和修復工具來重建集羣而不丟失 v3 鍵數據。

etcd 證書製作

由於 v3 版本的 etcd 證書是基於 IP 的,所以每次新增 etcd 節點都需要重新制作證書。爲了我們更方便的使用,可以查看這個鏈接來 etcd 證書製作。詳情: https://github.com/cloudflare/cfssl。

快照鍵空間

恢復集羣,首先需要來自 etcd 成員的鍵空間的快照。快速可以是用 etcdctl snapshot save 命令從活動成員中獲取。或者是從 etcd 數據目錄複製 member/snap/db 文件。例如,下列命令快照在 $ENDPOINT 上服務的鍵空間到文件 snapshot.db。

# 集羣備份etcd數據快照
# $ENDPOINT => http://10.20.30.1:2379ETCDCTL_API=3 etcdctl --endpoints $ENDPOINT snapshot save snapshot.db

# 在單節點etcd上執行下面的命令就可以對etcd進行數據備份
# 每兩個小時備份一次數據並上傳到S3上,並保留最近兩天的數據ETCDCTL_API=3 etcdctl snapshot  save /var/lib/etcd_backup/etcd_$(date "+%Y%m%d%H%M%S").db

恢復集羣

爲了恢復集羣,使用之前任意節點上備份的快照 "db" 文件。恢復的手,可以使用 etcdctl snapshot restore 命令來恢復 etcd 數據目錄,此時所有成員應該使用相同的快照恢復。因爲恢復數據死後,會覆蓋某些快照元數據 (特別是成員 ID 和集羣 ID) 信息,集羣內的成員可能會丟失它之前的標識。因此爲了從快照啓動集羣,恢復必須啓動一個新的邏輯集羣。

在恢復時,快照完整性的檢驗是可選的。如果快照是通過 etcdctl snapshot save 得到的話,使用 etcdctl snapshot restore 命令恢復的時候,會檢查 hash 值的完整性。如果快照是從數據目錄複製而來,則沒有完整性校驗,因此它只能通過使用 --skip-hash-check 來恢復。

下面爲一個 3 成員的集羣創建新的 etcd 數據目錄:

$ etcdctl snapshot restore snapshot.db \
  --name m1 \
  --initial-cluster m1=http:/host1:2380,m2=http://host2:2380,m3=http://host3:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls http://host1:2380

$ etcdctl snapshot restore snapshot.db \
  --name m2 \
  --initial-cluster m1=http:/host1:2380,m2=http://host2:2380,m3=http://host3:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls http://host2:2380

$ etcdctl snapshot restore snapshot.db \
  --name m3 \
  --initial-cluster m1=http:/host1:2380,m2=http://host2:2380,m3=http://host3:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls http://host3:2380

下一步, 用新的數據目錄啓動 etcd 服務,現在恢復的集羣可以使用並提供來自快照的鍵空間服務。

$ etcd \
  --name m1 \
  --listen-client-urls http://host1:2379 \
  --advertise-client-urls http://host1:2379 \
  --listen-peer-urls http://host1:2380 &

$ etcd \
  --name m2 \
  --listen-client-urls http://host2:2379 \
  --advertise-client-urls http://host2:2379 \
  --listen-peer-urls http://host2:2380 &

$ etcd \
  --name m3 \
  --listen-client-urls http://host3:2379 \
  --advertise-client-urls http://host3:2379 \
  --listen-peer-urls http://host3:2380 &

_作者: Escape _

https://escapelife.github.io/posts/90d30a1e.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/s8ROS_e9YiaITr6cOMkGKw