etcd 单节点和集群搭建

etcd 单节点和集群搭建

etcd是使用Go语言开发的一个开源、高可用的分布式key-value存储系统,可以用于:

  • 配置共享
  • 服务注册与发现
  • 分布式锁

类似的项目或者说中间件还有zookeeperconsul,其中zookeeper,在java的技术栈中利用的最多,而在go语言中更多的是使用etcd或者consul,这俩对比,etcd的文档又比consul更齐全。

etcd通过raft算法实现了强一致、高可用的服务存储目录,集群中每个节点都可以使用完整的存档,集群部署也实现了高可用。

应用场景

分布式配置中心

将分布式系统中的各种配置文件信息注册到 etcd 中,当服务启动的之后从 etcd 中的指定配置 key 下拉取配置信息,作为第一次配置文件同步。此后,在etcd节点上注册一个Watcher,实时监听 etcd 中对应的键值的变化,在监听到变更时,及时同步最新的配置信息到我们的业务系统中,以实现配置热发布和热加载

服务注册中心

服务发现要解决的也是分布式系统中最常见的问题,即在同一个分布式集群中的进程或服务,要如何才能找到对方并建立连接。本质上来说, 服务发现就是想要了解集群中是否有进程在监听 udptcp 端口,并且通过名字就可以查找和连接。 就像查字典一样。etcd就能充当一个服务字典的角色,服务上线去往etcd进行注册,etcd与服务之间维持一个心跳,保证服务是否可用。

分布式锁

etcd 使用 raft 算法保证了数据的一致性,因此可以作为分布式锁使用

  • 保持独占: 只有一个可以获得锁etcd提供了一套实现分布式锁原子操作CAS(CompareAndSwap)API.通过设置prevExist值,可以保证多个节点同时去创建某个目录时,只有一个成功。而创建成功的那一个就可以认为是获得了锁。
  • 控制时序: 对所有想要获得锁进行排序,这个顺序全局唯一etcd提供了一套API,用于自动创建有序键。

核心组件

http Server

用于处理用户发送的 API 请求以及与集群中其他 etcd 节点通信

Store

顾名思义,存储组件。用于支持 etcd 的各类功能性事务,主要包括数据索引、节点状态变更、监控和反馈、事件处理和执行

raft

raft 分布式一致性算法

WAL

Write Ahead Log 预写式日志,是 etcd 的数据存储方式,除了在内存中存放所有的数据之外,etcd 还会通过 WAL 进行持久化存储

搭建单节点 etcd

下面在博主的 mbp 上做演示:

去官方的github下载最新版本的etcd,找到最新的release

https://github.com/etcd-io/etcd/releases

直接解压后运行就好

1
2
wu@WuuuudeMacBook-Pro ~/Projects/etcd-v3.5.17-darwin-arm64
./etcd

不间断运行

1
2
wu@WuuuudeMacBook-Pro ~/Projects/etcd-v3.5.17-darwin-arm64
nohup ./etcd &

测试

etcdcli是 etcd 官方提供的 etcd 命令行客户端,可在解压后的 etcd 目录下找见,并可以直接运行

1
2
3
4
5
> ./etcdctl put name wuuu
OK
> ./etcdctl get name
name
wuuu

如果需要指定 etcd 端点(集群节点)的话可以加上–endpoints 参数

1
2
3
> /etcdctl --endpoints=http://127.0.0.1:2379 get name
name
wuuu

搭建三节点 etcd 集群

如果处于测试或者开发的目的,我们可以使用单节点的 etcd,但是在生产环境中,为了确保 etcd 的高可用,应该使用 etcd 集群。与此同时,raft 算法在决策时需要半数以上的节点决策投票,因此一般在生产环境中都会部署奇数个节点的 etcd 集群。下面在博主的本地开发环境基于 docker 搭建三节点 etcd 集群

假设我们使用三个容器,分别命名为 etcd-node1etcd-node2etcd-node3,以下是对应的 IP 地址(通过 Docker 网络实现):

容器名称 IP 地址 主机名
etcd-node1 172.20.0.2 node1
etcd-node2 172.20.0.3 node2
etcd-node3 172.20.0.4 node3

1.创建 docker 网络

为了保证容器之间可以彼此通信,先创建 etcd 集群的 docker 网络

1
docker network create --subnet=172.20.0.0/16 etcd-network

2.创建并启动三个 etcd 节点,让其加入 docker 网络

第一个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
docker run -d \
--name etcd-node1 \
--net etcd-network \
--ip 172.20.0.2 \
--hostname node1 \
-p 23791:2379 -p 23801:2380 \
gcr.io/etcd-development/etcd:v3.5.9 \
etcd \
--name node1 \
--data-dir /etcd-data \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://172.20.0.2:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--initial-advertise-peer-urls http://172.20.0.2:2380 \
--initial-cluster node1=http://172.20.0.2:2380,node2=http://172.20.0.3:2380,node3=http://172.20.0.4:2380 \
--initial-cluster-token etcd-cluster \
--initial-cluster-state new

第二个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
docker run -d \
--name etcd-node2 \
--net etcd-network \
--ip 172.20.0.3 \
--hostname node2 \
-p 23792:2379 -p 23802:2380 \
gcr.io/etcd-development/etcd:v3.5.9 \
etcd \
--name node2 \
--data-dir /etcd-data \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://172.20.0.3:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--initial-advertise-peer-urls http://172.20.0.3:2380 \
--initial-cluster node1=http://172.20.0.2:2380,node2=http://172.20.0.3:2380,node3=http://172.20.0.4:2380 \
--initial-cluster-token etcd-cluster \
--initial-cluster-state new

第三个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
docker run -d \
--name etcd-node3 \
--net etcd-network \
--ip 172.20.0.4 \
--hostname node3 \
-p 23793:2379 -p 23803:2380 \
gcr.io/etcd-development/etcd:v3.5.9 \
etcd \
--name node3 \
--data-dir /etcd-data \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://172.20.0.4:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--initial-advertise-peer-urls http://172.20.0.4:2380 \
--initial-cluster node1=http://172.20.0.2:2380,node2=http://172.20.0.3:2380,node3=http://172.20.0.4:2380 \
--initial-cluster-token etcd-cluster \
--initial-cluster-state new

这里,三个节点的主机名分别为 node1、node2、node3,映射到宿主机的端口分别为 23791、23792、23793

3. 验证集群状态

检查 etcd 容器是否运行

1
docker ps

使用 etcdctl 验证集群

通过 etcdctl 检查集群成员和状态: member list用于列出同集群下的所有节点

1
docker exec etcd-node1 etcdctl --endpoints=http://172.20.0.2:2379 member list

你将看到类似输出:

1
2
3
b217c5c8b9eaba91, started, node1, http://172.20.0.2:2380, http://172.20.0.2:2379, false
e6996497aa96d1fc, started, node3, http://172.20.0.4:2380, http://172.20.0.4:2379, false
f01c391eb2944291, started, node2, http://172.20.0.3:2380, http://172.20.0.3:2379, false

测试键值存储

  1. 通过执行节点写入键值:

    1
    docker exec etcd-node1 etcdctl --endpoints=http://172.20.0.2:2379 put foo bar
  2. 从其他节点读取键值:

    1
    docker exec etcd-node2 etcdctl --endpoints=http://172.20.0.3:2379 get foo

或者我们可以直接使用etcd 安装包下的 etcdcli 可执行文件来发送命令,不过此时需要使用容器映射在宿主机上的端口

1
> ./etcdctl --endpoints=http://172.20.0.2:2379 put foo bar

查看Leader节点

在 etcd 集群中,没有固定的“主节点”(如传统的主从数据库架构)。etcd 使用的是 Raft 共识算法,它通过选举机制动态选出一个 Leader(领导者),其余节点为 Follower(追随者)。Leader 负责处理所有写请求并将变更同步到 Follower。所有节点都可以处理读请求。

可以使用如下命令查看 etcd 集群中的 leader 节点:

1
2
3
4
5
6
7
8
> ./etcdctl --endpoints=http://127.0.0.1:23791,127.0.0.1:23792,http://127.0.0.1:23793 endpoint status --write-out=table
+------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| http://127.0.0.1:23791 | ade526d28b1f92f7 | 3.5.9 | 20 kB | false | false | 2 | 9 | 9 | |
| http://127.0.0.1:23792 | d282ac2ce600c1ce | 3.5.9 | 20 kB | false | false | 2 | 9 | 9 | |
| http://127.0.0.1:23793 | bd388e7810915853 | 3.5.9 | 20 kB | true | false | 2 | 9 | 9 | |
+------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

IS LEADER 为 true 的节点即为集群中的 leader 节点

使用 docker-compose 方式

当然,也可以使用 docker-compose来编排这个 etcd 集群,在管理上更为便捷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
version: '3.8'
services:
etcd1:
image: gcr.io/etcd-development/etcd:v3.5.9
container_name: etcd1
networks:
- etcd_network
ports:
- "23791:2379" # 映射宿主机的 2379 端口,方便访问 etcd 客户端接口
- "23801:2380"
environment:
- ETCD_NAME=etcd1
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd1:2380 # 当前节点对外广播的节点间通信地址
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd1:2379 # 当前节点对外广播的客户端访问地址
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 # 监听节点间通信的地址
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 # 监听客户端访问的地址
- ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380 # 初始化集群时的节点列表
- ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster # 指定 etcd 集群的唯一标识符。它在集群初始化时使用,确保多个 etcd 实例能够识别并加入同一个集群
- ETCD_INITIAL_CLUSTER_STATE=new # 初始化一个新集群

etcd2:
image: gcr.io/etcd-development/etcd:v3.5.9
container_name: etcd2
networks:
- etcd_network
ports:
- "23792:2379"
- "23802:2380"
environment:
- ETCD_NAME=etcd2
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd2:2380
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd2:2379
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
- ETCD_INITIAL_CLUSTER_STATE=new

etcd3:
image: gcr.io/etcd-development/etcd:v3.5.9
container_name: etcd3
networks:
- etcd_network
ports:
- "23793:2379"
- "23803:2380"
environment:
- ETCD_NAME=etcd3
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd3:2380
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd3:2379
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
- ETCD_INITIAL_CLUSTER_STATE=new

networks:
etcd_network:
driver: bridge

测试方法同上即可


etcd 单节点和集群搭建
https://wuwanhao.github.io/2024/12/08/etcd/
作者
Wuuu
发布于
2024年12月8日
许可协议