etcd 构建自身高可用集群主要有三种形式:
- 静态发现: 预先已知 etcd 集群中有哪些节点,在启动时通过 `--initial-cluster` 参数直接指定好 etcd 的各个节点地址
- etcd 动态发现: 通过已有的 etcd 集群作为数据交互点,然后在扩展新的集群时实现通过已有集群进行服务发现的机制。比如官方提供的: discovery.etcd.io
- DNS 动态发现: 通过 DNS 查询方式获取其他节点地址信息
本文将介绍如何通过静态发现这种方式来部署一个 etcd 集群,这种方式也是最简单的

# 环境准备
通常按照需求将集群节点部署为3,5,7,9个节点。这里能选择偶数个节点吗?最好不要这样。原因有二:
- 偶数个节点集群不可用风险更高,表现在选主过程中,有较大概率或等额选票,从而触发下一轮选举
- 偶数个节点集群在某些网络分割的场景下无法正常工作。当网络分割发生后,将集群节点对半分割开。此时集群将无法工作。按照RAFT协议,此时集群写操作无法使得大多数节点同意,从而导致写失败,集群无法正常工作

这里将部署一个3节点的集群, 以下为3台主机信息,系统环境为 Centos 7
```bash
节点名称 | 地址
CeH-7-11 | 192.168.10.11
CeH-7-12 | 192.168.10.12
CeH-7-13 | 192.168.10.13
```

# 安装etcd

# 配置etcd集群
修改 etcd 配置文件,我的配置环境是在 `/opt/etcd/config/etcd.conf`

CeH-7-11 配置示例
```bash
[root@CeH-7-11 ~]# vim /opt/etcd/config/etcd.conf
ETCD_NAME=CeH-7-11
ETCD_DATA_DIR="/var/lib/etcd/etcd711"
ETCD_LISTEN_PEER_URLS="http://192.168.10.11:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.10.11:2379,http://192.168.10.11:4001"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.10.11:2380"
ETCD_INITIAL_CLUSTER="CeH-7-11=http://192.168.10.11:2380,CeH-7-12=http://192.168.10.12:2380,CeH-7-13=http://192.168.10.13:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="hilinux-etcd-cluster"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.10.11:2379,http://192.168.10.11:4001"
```

CeH-7-12 配置示例
```bash
[root@CeH-7-12 ~]# vim /opt/etcd/config/etcd.conf
ETCD_NAME=CeH-7-12
ETCD_DATA_DIR="/var/lib/etcd/etcd712"
ETCD_LISTEN_PEER_URLS="http://192.168.10.12:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.10.12:2379,http://192.168.10.12:4001"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.10.12:2380"
ETCD_INITIAL_CLUSTER="CeH-7-11=http://192.168.10.11:2380,CeH-7-12=http://192.168.10.12:2380,CeH-7-13=http://192.168.10.13:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="hilinux-etcd-cluster"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.10.12:2379,http://192.168.10.12:4001"
```

CeH-7-13 配置示例
```bash
[root@CeH-7-13 ~]# vim /opt/etcd/config/etcd.conf
ETCD_NAME=CeH-7-13
ETCD_DATA_DIR="/var/lib/etcd/etcd713"
ETCD_LISTEN_PEER_URLS="http://192.168.10.13:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.10.13:2379,http://192.168.10.13:4001"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.10.13:2380"
ETCD_INITIAL_CLUSTER="CeH-7-11=http://192.168.10.11:2380,CeH-7-12=http://192.168.10.12:2380,CeH-7-13=http://192.168.10.13:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="hilinux-etcd-cluster"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.10.13:2379,http://192.168.10.13:4001"
```

以上配置参数说明:
```bash
ETCD_NAME: ETCD的节点名
ETCD_DATA_DIR: ETCD的数据存储目录
ETCD_SNAPSHOT_COUNTER: 多少次的事务提交将触发一次快照
ETCD_HEARTBEAT_INTERVAL: ETCD节点之间心跳传输的间隔,单位毫秒
ETCD_ELECTION_TIMEOUT: 该节点参与选举的最大超时时间,单位毫秒
ETCD_LISTEN_PEER_URLS: 该节点与其他节点通信时所监听的地址列表,多个地址使用逗号隔开,其格式可以划分为scheme://IP:PORT,这里的scheme可以是http、https
ETCD_LISTEN_CLIENT_URLS: 该节点与客户端通信时监听的地址列表
ETCD_INITIAL_ADVERTISE_PEER_URLS: 该成员节点在整个集群中的通信地址列表,这个地址用来传输集群数据的地址。因此这个地址必须是可以连接集群中所有的成员的。
ETCD_INITIAL_CLUSTER: 配置集群内部所有成员地址,其格式为:ETCD_NAME=ETCD_INITIAL_ADVERTISE_PEER_URLS,如果有多个使用逗号隔开
ETCD_ADVERTISE_CLIENT_URLS: 广播给集群中其他成员自己的客户端地址列表
ETCD_INITIAL_CLUSTER_STATE: 初始化集群状态,new表示新建
ETCD_INITIAL_CLUSTER_TOKEN: 初始化集群token
注意: 所有ETCD_MY_FLAG的配置参数也可以通过命令行参数进行设置,但是命令行指定的参数优先级更高,同时存在时会覆盖环境变量对应的值
```

常用配置的参数和说明:
```bash
--name:方便理解的节点名称,默认为default,在集群中应该保持唯一,可以使用 hostname
--data-dir:服务运行数据保存的路径,默认为 [root@CeH-7-11 ~]#{name}.etcd
--snapshot-count:指定有多少事务(transaction)被提交时,触发截取快照保存到磁盘
--heartbeat-interval:leader 多久发送一次心跳到 followers。默认值是 100ms
--eletion-timeout:重新投票的超时时间,如果 follow 在该时间间隔没有收到心跳包,会触发重新投票,默认为 1000 ms
--listen-peer-urls:和同伴通信的地址,比如 http://ip:2380,如果有多个,使用逗号分隔。需要所有节点都能够访问,所以不要使用 localhost!
--listen-client-urls:对外提供服务的地址:比如 http://ip:2379,http://127.0.0.1:2379,客户端会连接到这里和 etcd 交互
--advertise-client-urls:对外公告的该节点客户端监听地址,这个值会告诉集群中其他节点
--initial-advertise-peer-urls:该节点同伴监听地址,这个值会告诉集群中其他节点
--initial-cluster:集群中所有节点的信息,格式为 node1=http://ip1:2380,node2=http://ip2:2380,…。注意:这里的 node1 是节点的 --name 指定的名字;后面的 ip1:2380 是 --initial-advertise-peer-urls 指定的值
--initial-cluster-state:新建集群的时候,这个值为new;假如已经存在的集群,这个值为 existing --initial-cluster-token:创建集群的token,这个值每个集群保持唯一。这样的话,如果你要重新创建集群,即使配置和之前一样,也会再次生成新的集群和节点 uuid;否则会导致多个集群之间的冲突,造成未知的错误 所有以 --init 开头的配置都是在bootstrap集群的时候才会用到,后续节点的重启会被忽略
```

# 测试etcd集群
按上面配置好各集群节点后,分别在各节点启动 etcd
```bash
[root@CeH-7-11 ~]# systemctl start etcd
[root@CeH-7-12 ~]# systemctl start etcd
[root@CeH-7-13 ~]# systemctl start etcd
```

启动完成后,在任意节点执行 `etcdctl member list` 可列所有集群节点信息,如下所示:
```bash
[root@CeH-7-11 ~]# etcdctl --endpoints http://192.168.10.11:2379 member list
607ff8dfddf5756a: name=CeH-7-11 peerURLs=http://192.168.10.11:2380 clientURLs=http://192.168.10.11:2379,http://192.168.10.11:4001 isLeader=false
815e60d46cd3bb74: name=CeH-7-13 peerURLs=http://192.168.10.13:2380 clientURLs=http://192.168.10.13:2379,http://192.168.10.13:4001 isLeader=true
86c747dee8a81b91: name=CeH-7-12 peerURLs=http://192.168.10.12:2380 clientURLs=http://192.168.10.12:2379,http://192.168.10.12:4001 isLeader=false
```

这里指定了集群地址,如不指定会报如下错误:
```bash
[root@CeH-7-11 ~]# etcdctl member list
Error: client: etcd cluster is unavailable or misconfigured; error #0: dial tcp 127.0.0.1:4001: getsockopt: connection refused ; error #1: dial tcp 127.0.0.1:2379: getsockopt: connection refused error
#0: dial tcp 127.0.0.1:4001: getsockopt: connection refused error
#1: dial tcp 127.0.0.1:2379: getsockopt: connection refused
```

如上的错误,是因为 `ETCD_LISTEN_CLIENT_URLS` 参数中增加 `http://127.0.0.1:2379`即可
```bash
ETCD_LISTEN_CLIENT_URLS="http://192.168.10.12:2379,http://192.168.10.12:4001,http://127.0.0.1:2379"
```

# etcd集群基本管理
**查看集群健康状态及成员信息**
```bash
[root@CeH-7-11 ~]# etcdctl member list
607ff8dfddf5756a: name=CeH-7-11 peerURLs=http://192.168.10.11:2380 clientURLs=http://192.168.10.11:2379 isLeader=true
815e60d46cd3bb74: name=CeH-7-13 peerURLs=http://192.168.10.13:2380 clientURLs=http://192.168.10.13:2379 isLeader=false
86c747dee8a81b91: name=CeH-7-12 peerURLs=http://192.168.10.12:2380 clientURLs=http://192.168.10.12:2379 isLeader=false
```

**更新节点**
如果更新一个节点的 IP(peerURLS),首先需要知道那个节点的 ID
```bash
[root@CeH-7-11 ~]# etcdctl member update 607ff8dfddf5756a http://192.168.10.111:2380 Updated member with ID 607ff8dfddf5756a in cluster
```

**删除节点**
```bash
[root@CeH-7-11 ~]# etcdctl member remove 86c747dee8a81b91
Removed member 86c747dee8a81b91 from cluster
[root@CeH-7-11 ~]# etcdctl member list
607ff8dfddf5756a: name=CeH-7-11 peerURLs=http://192.168.10.11:2380 clientURLs=http://192.168.10.11:2379 isLeader=true
815e60d46cd3bb74: name=CeH-7-13 peerURLs=http://192.168.10.13:2380 clientURLs=http://192.168.10.13:2379 isLeader=false
```

**增加一个新节点**
注意:步骤很重要,不然会报集群 ID 不匹配

**a. 将目标节点添加到集群**
```bash
[root@CeH-7-11 ~]# etcdctl --endpoints "http://192.168.10.11:2379" member add etcd714 http://192.168.10.12:2380
Added member named etc712 with ID bd2f8486365e6fe0 to cluster

ETCD_NAME="etcd712"
ETCD_INITIAL_CLUSTER="CeH-7-11=http://192.168.10.11:2380,CeH-7-13=http://192.168.10.13:2380,etcd712=http://192.168.10.12:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"
```

**b.查看新增成员列表**
CeH-7-12 状态现在为 unstarted
```bash
[root@CeH-7-11 ~]# etcdctl --endpoints "http://192.168.10.11:2379" member list
607ff8dfddf5756a: name=CeH-7-11 peerURLs=http://192.168.10.11:2380 clientURLs=http://192.168.10.11:2379 isLeader=true
815e60d46cd3bb74: name=CeH-7-13 peerURLs=http://192.168.10.13:2380 clientURLs=http://192.168.10.13:2379 isLeader=false
bd2f8486365e6fe0[unstarted]: peerURLs=http://192.168.10.14:2380
```

**c. 清空目标节点数据**
目标节点从集群中删除后,成员信息会更新。新节点是作为一个全新的节点加入集群,如果 data-dir 有数据,etcd 启动时会读取己经存在的数据,仍然用旧的 memberID 会造成无法加入集群,所以一定要清空新节点的 data-dir
```bash
[root@CeH-7-11 ~]# rm -rf /var/lib/etcd/etcd712
```

**d. 在目标节点上启动新增加的成员**
修改配置文件中 `ETCD_INITIAL_CLUSTER_STATE` 标记为 `existing`,如果为 `new`,则会自动生成一个新的 `memberID`,这和前面添加节点时生成的 ID 不一致,故日志中会报节点 ID 不匹配的错
```bash
[root@CeH-7-11 ~]# vim /opt/etcd/config/etcd.conf ETCD_INITIAL_CLUSTER_STATE="existing"
[root@CeH-7-11 ~]# systemctl start etcd
```

**查看新节点是否成功加入**
```bash
[root@CeH-7-11 ~]# etcdctl --endpoints "http://192.168.10.11:2379" member list
607ff8dfddf5756a: name=CeH-7-11 peerURLs=http://192.168.10.11:2380 clientURLs=http://192.168.10.11:2379 isLeader=true
815e60d46cd3bb74: name=CeH-7-13 peerURLs=http://192.168.10.13:2380 clientURLs=http://192.168.10.13:2379 isLeader=false
bd2f8486365e6fe0: name=CeH-7-12 peerURLs=http://192.168.10.12:2380 clientURLs=http://192.168.10.12:2379 isLeader=false
```