准备工作
这里简单在本地同一台机器上搭建一个伪 etcd 集群,每个节点监听不同的端口,数据都存储在 /var/lib/etcd/cluster/ 目录下,如下:
| 节点 | IP | 客户端端口 | 内部端口 | 数据路径 |
|---|---|---|---|---|
| etcd01 | 127.0.0.1 | 12379 | 12380 | /var/lib/etcd/cluster/etcd01 |
| etcd02 | 127.0.0.1 | 22379 | 22380 | /var/lib/etcd/cluster/etcd02 |
| etcd03 | 127.0.0.1 | 32379 | 32380 | /var/lib/etcd/cluster/etcd03 |
搭建 etcd 伪集群
etcd 可以通过给命令行参数提供所有必要的配置信息来启动并形成集群,例如节点名称、客户端和对等通信的 URL、初始集群配置和数据目录等。这种方式在进行快速部署或测试时非常方便。在实际生产环境中,尤其是当配置变得更加复杂时,使用配置文件可能更为方便和可靠。无论是使用命令行参数还是配置文件来启动 etcd,其背后的集群逻辑和通信机制都是相同的。
通过命令启动
启动脚本内容如下:
#!/bin/bash# 定义变量PID_FILE="/var/run/etcd-cluster.pid"NODES=("etcd01" "etcd02" "etcd03")DATA_DIRS=("/var/lib/etcd/cluster/etcd01" "/var/lib/etcd/cluster/etcd02" "/var/lib/etcd/cluster/etcd03")CLIENT_PORTS=(12379 22379 32379)PEER_PORTS=(12380 22380 32380)INITIAL_CLUSTER="etcd01=http://localhost:12380,etcd02=http://localhost:22380,etcd03=http://localhost:32380"CLUSTER_TOKEN="etcd-cluster"# 清空 PID 文件> "${PID_FILE}"# 启动每个 etcd 节点for i in "${!NODES[@]}"; do etcd --name ${NODES[$i]} \ --data-dir ${DATA_DIRS[$i]} \ --listen-peer-urls http://localhost:${PEER_PORTS[$i]} \ --listen-client-urls http://localhost:${CLIENT_PORTS[$i]} \ --advertise-client-urls http://localhost:${CLIENT_PORTS[$i]} \ --initial-cluster ${INITIAL_CLUSTER} \ --initial-advertise-peer-urls http://localhost:${PEER_PORTS[$i]} \ --initial-cluster-token ${CLUSTER_TOKEN} \ --initial-cluster-state new \ --log-level info >"${DATA_DIRS[$i]}/etcd.log" 2>&1 & echo $! >> "${PID_FILE}"doneecho "etcd 伪集群启动完毕,PID 已写入 ${PID_FILE}"etcdctl 是 etcd 的命令行客户端,用于与 etcd 服务器交互。使用上面的脚本启动 etcd 集群后,使用etcdctl member list命令列出所有节点会发现报错,这是因为 etcdctl 找不到 etcd 集群。可以使用 ETCDCTL_ENDPOINTS 环境变量来让 etcdctl 找到 etcd 集群,所以该环境变量的作用就是告诉 etcdctl 客户端应该与哪些端口上的 etcd 服务器实例进行通信。下面是在当前会话中设置一个临时的环境变量:
export ETCDCTL_ENDPOINTS=http://localhost:12379,http://localhost:22379,http://localhost:32379这样使用etcdctl member list命令就可以列出集群的所有节点的信息。除此之外,还可以使用etcdctl endpoint status -w table命令查看集群的健康状态。

停止脚本内容如下:
#!/bin/bashPID_FILE="/var/run/etcd-cluster.pid"if [ -f "${PID_FILE}" ]; then while read -r pid; do if [ -n "${pid}" ]; then kill "${pid}" fi done < "${PID_FILE}" rm -f "${PID_FILE}" echo "所有 etcd 伪集群进程已停止"else echo "PID 文件不存在,无法停止 etcd 伪集群。"fi通过配置文件启动
使用脚本的好处就是部署方便,但是如果是真正的 etcd 集群(每个节点都在不同的机器上),那么就无法使用脚本进行集群部署。但是,etcd 同样支持使用 YAML 或 JSON 格式的配置文件来部署集群。首先需要创建一个配置文件,然后使用 --config-file 参数来指定这个配置文件即可。
由于克隆虚拟机也比较麻烦,所以下面使用配置文件的方式来部署 etcd 伪集群。
首先要为每个 etcd 成员创建一个配置文件。配置文件通常为 YAML 格式,下面是具体的配置文件:
# etcd01.yamlname: 'etcd01'data-dir: '/var/lib/etcd/etcd01'listen-peer-urls: 'http://127.0.0.1:12380'listen-client-urls: 'http://127.0.0.1:12379'advertise-client-urls: 'http://127.0.0.1:12379'initial-cluster: 'etcd01=http://127.0.0.1:12380,etcd02=http://127.0.0.1:22380,etcd03=http://127.0.0.1:32380'initial-advertise-peer-urls: 'http://127.0.0.1:12380'initial-cluster-token: 'etcd-cluster'initial-cluster-state: 'new'# etcd02.yamlname: 'etcd02'data-dir: '/var/lib/etcd/etcd02'listen-peer-urls: 'http://127.0.0.1:22380'listen-client-urls: 'http://127.0.0.1:22379'advertise-client-urls: 'http://127.0.0.1:22379'initial-cluster: 'etcd01=http://127.0.0.1:12380,etcd02=http://127.0.0.1:22380,etcd03=http://127.0.0.1:32380'initial-advertise-peer-urls: 'http://127.0.0.1:22380'initial-cluster-token: 'etcd-cluster'initial-cluster-state: 'new'# etcd03.yamlname: 'etcd03'data-dir: '/var/lib/etcd/etcd03'listen-peer-urls: 'http://127.0.0.1:32380'listen-client-urls: 'http://127.0.0.1:32379'advertise-client-urls: 'http://127.0.0.1:32379'initial-cluster: 'etcd01=http://127.0.0.1:12380,etcd02=http://127.0.0.1:22380,etcd03=http://127.0.0.1:32380'initial-advertise-peer-urls: 'http://127.0.0.1:32380'initial-cluster-token: 'etcd-cluster'initial-cluster-state: 'new'下面是配置文件每一行的解释:
name: 这个字段指定了 etcd 成员的名称,在同一个集群中必须是唯一的。data-dir: 这个字段指定了 etcd 数据的存储位置,etcd 将所有的数据存储在这个目录下。listen-peer-urls: etcd 服务端的监听 URL,可以有多个地址。这个地址是 etcd 用来监听来自其他 etcd 节点的请求。listen-client-urls: etcd 监听客户端请求的 URL。客户端请求是指来自如 etcdctl 或者其他使用 etcd 客户端库的应用程序的请求。advertise-client-urls: etcd 成员通告给客户端的 URL,这些是客户端用来连接服务的地址。initial-cluster: 这个配置项列出了集群中所有成员的名称和它们的 peer URLs,用于在启动时通告集群中的其他成员。initial-advertise-peer-urls: 这个 URL 用于告诉集群中的其他节点如何与该节点通信(对等通信)。它是集群内部通信用的。initial-cluster-token: 集群令牌,是集群的一个唯一标识符。这对于创建多个集群很有用,以防止不同集群的成员彼此通信。initial-cluster-state: 当一个成员首次启动时,这个值用来指示这个成员是加入一个已经存在的集群(existing),还是初始化一个新集群(new)。
由于 etcd 在启动时会把日志直接输出到终端,所以可以使用 nohup 命令解决这个问题。
nohup etcd --config-file ./etcd01.yaml &nohup etcd --config-file ./etcd02.yaml &nohup etcd --config-file ./etcd03.yaml &接下来设置临时环境变量:
export ETCDCTL_ENDPOINTS=http://127.0.0.1:12379,http://127.0.0.1:22379,http://127.0.0.1:32379结果如下:

使用 docker 搭建 etcd 集群
创建一个 docker 网络
在创建 etcd 集群时,如果所有的 etcd 容器实例都在同一台宿主机上运行,那么bridge网络就足够了。如果 etcd 容器需要跨多个宿主机通信,那么需要设置一个overlay网络。在这里由于是同一个宿主机,所以选择默认的 bridge 网络就行。
docker network create etcd-net拉取镜像
这里我使用的是 quay.io/coreos/etcd:v3.5.10 镜像。
docker pull quay.io/coreos/etcd:v3.5.10但是去官网查看这个镜像的 Dockerfile 会发现没有 CMD [“/bin/sh”]指令,因此在构建好 etcd 的容器后是无法通过 docker exec -it etcd-1 /bin/sh 命令来进入容器内部。所以,要么基于这个镜像自己构建一个具有/bin/sh 的 etcd 镜像,要么不使用/bin/sh,直接使用 etcdctl 命令来操作 etcd 服务器。
启动 etcd 容器
对于一个简单的三节点 etcd 集群,需要启动三个容器。每个节点需要知道其他节点的地址来形成集群。
# etcd01docker run -d --net etcd-net --name etcd-1 \-v /var/lib/etcd/docker/etcd01:/etcd-data \--env ETCD_NAME=etcd01 \--env ETCD_DATA_DIR=/etcd-data \--env ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 \--env ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \--env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-1:2379 \--env ETCDCTL_ENDPOINTS=http://etcd-1:2379,http://etcd-2:2379,http://etcd-3:2379 \--env ETCD_INITIAL_CLUSTER="etcd01=http://etcd-1:2380,etcd02=http://etcd-2:2380,etcd03=http://etcd-3:2380" \--env ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd-1:2380 \--env ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster-1 \--env ETCD_INITIAL_CLUSTER_STATE=new \quay.io/coreos/etcd:v3.5.10# etcd02docker run -d --net etcd-net --name etcd-2 \-v /var/lib/etcd/docker/etcd02:/etcd-data \--env ETCD_NAME=etcd02 \--env ETCD_DATA_DIR=/etcd-data \--env ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 \--env ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \--env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-2:2379 \--env ETCDCTL_ENDPOINTS=http://etcd-1:2379,http://etcd-2:2379,http://etcd-3:2379 \--env ETCD_INITIAL_CLUSTER="etcd01=http://etcd-1:2380,etcd02=http://etcd-2:2380,etcd03=http://etcd-3:2380" \--env ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd-2:2380 \--env ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster-1 \--env ETCD_INITIAL_CLUSTER_STATE=new \quay.io/coreos/etcd:v3.5.10# etcd03docker run -d --net etcd-net --name etcd-3 \-v /var/lib/etcd/docker/etcd03:/etcd-data \--env ETCD_NAME=etcd03 \--env ETCD_DATA_DIR=/etcd-data \--env ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 \--env ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \--env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-3:2379 \--env ETCDCTL_ENDPOINTS=http://etcd-1:2379,http://etcd-2:2379,http://etcd-3:2379 \--env ETCD_INITIAL_CLUSTER="etcd01=http://etcd-1:2380,etcd02=http://etcd-2:2380,etcd03=http://etcd-3:2380" \--env ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd-3:2380 \--env ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster-1 \--env ETCD_INITIAL_CLUSTER_STATE=new \quay.io/coreos/etcd:v3.5.10这样就成功启动三个 docker 容器,每个容器中都有一个 etcd 服务器。

查看集群状态
由于 quay.io/coreos/etcd:v3.5.10 镜像没有 shell 环境,不能进入容器内部的文件系统,所以只能通过 etcdctl 命令来查看集群的健康状态。

写入数据

刚开始的时候,我发现 etcd-1 这个节点是一个 leader 节点(主节点),而 etcd-2 和 etcd-3 则是从节点。但是当我在 etcd-3 这个节点上成功写入数据的时候就产生了一个疑问。etcd 集群不是使用 Raft 算法来保持一致性吗?既然是 Raft 算法,那么集群中只能有一个节点来执行写操作,这个节点就是 leader 节点。但是 etcd-3 节点又是从节点,为什么能执行写操作呢?
于是,在网上百度后找到了答案。当客户端向任何 etcd 节点(包括从节点)写入数据时,如果该节点不是 leader,它会自动将写请求转发到当前的 leader 节点。然后 leader 节点将这个写入操作复制到其它从节点。这个过程对于客户端是透明的。
举一反三:但是 Redis 的高可用集群就没有类似的功能,在 Redis 的高可用集群中,如果向从节点写入数据,则会报错。
