前言

本文采用两台虚拟机为 sample 来演示 etcd 集群的一些行为。

IPHOSTNAME
10.211.55.106etcd0
10.211.55.108etcd1

什么是 ETCD

ETCD 是用于共享配置和服务发现的分布式,一致性的 KV 数据库。具体信息请参考 项目首页Github ,而 ETCD 的一致性、原子性则依赖于 Raft 协议。

对比 ZooKeeper

一致性协议

ETCD 使用 Raft 协议, ZK 使用 ZAB(类 PAXOS 协议),前者容易理解,方便工程实现。

运维方面

ETCD 方便运维,ZK 难以运维。

API

ETCD 提供 HTTP & JSON,以及 gRPC 接口,跨平台跨语言,ZK 需要使用其客户端。

访问安全

ETCD 支持 HTTPS 访问,ZK 在这方面缺失。

一些概念

Raft

对于集群而言最核心内容就是保证数据一致性,在业界有很多算法、协议,例如 Paxos 和 Raft。

Raft 协议相比 Paxos 等,算是年轻的协议,而且 Raft 协议比较简单,容易实现。

至于 Raft 的通信逻辑可以参考:http://thesecretlivesofdata.com/raft/ ,十分简洁易懂。

API v2 与 API v3

API 版本与 etcd 的客户端发行版本无关,详情见下文提到的 查看 etcd 的版本号

API v2 存储的数据与 API v3 存储的数据不互通,相互独立。

支持 API v3 的 etcd 也支持 API v2 ,但两者数据独立。

启动一个单节点的 ETCD 集群

(虽然直接无参数执行 etcd 即可,但为了能够通适在这里还是写完整)

/etc/etcd/etcd.conf

https://pasteme.cn/7128

LOCAL_IP="CHANGE_THIS"
ETCD_DATA_DIR="/var/lib/etcd/data"
ETCD_LISTEN_PEER_URLS="http://${LOCAL_IP}:2380"
ETCD_LISTEN_CLIENT_URLS="http://${LOCAL_IP}:2379,http://127.0.0.1:2379"
ETCD_ADVERTISE_CLIENT_URLS="http://${LOCAL_IP}:2379"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"

ETCD_NAME="CHANGE_THIS"
ETCD_INITIAL_CLUSTER="${ETCD_NAME}=http://${LOCAL_IP}:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://${LOCAL_IP}:2380"
ETCD_INITIAL_CLUSTER_STATE="new"

/lib/systemd/system/etcd.service

https://pasteme.cn/7126

[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
WorkingDirectory=/var/lib/etcd
EnvironmentFile=/etc/etcd/etcd.conf
User=root
# set GOMAXPROCS to number of processors
ExecStart=/bin/bash -c \
    "GOMAXPROCS=$(nproc) \
    /usr/local/etcd/etcd \
    --name=${ETCD_NAME} \
    --data-dir=${ETCD_DATA_DIR} \
    --listen-peer-urls=${ETCD_LISTEN_PEER_URLS} \
    --listen-client-urls=${ETCD_LISTEN_CLIENT_URLS} \
    --initial-advertise-peer-urls=${ETCD_INITIAL_ADVERTISE_PEER_URLS} \
    --advertise-client-urls=${ETCD_ADVERTISE_CLIENT_URLS} \
    --initial-cluster-state=${ETCD_INITIAL_CLUSTER_STATE} \
    --initial-cluster-token=${ETCD_INITIAL_CLUSTER_TOKEN} \
    --initial-cluster=${ETCD_INITIAL_CLUSTER}"
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

加载并启动服务

$ systemctl daemon-reload  # 刷新
$ systemctl enable etcd  # 开机自启
$ systemctl start etcd

查看 etcd 的版本号

$ etcd --version
etcd Version: 3.3.12
Git SHA: d57e8b8
Go Version: go1.10.8
Go OS/Arch: linux/amd64

需要注意的是,当启动一个集群时,集群会向下兼容,所以集群运行时采用的协议版本等于集群中版本最低的 etcd 节点采用的协议版本。

比如集群中有 2 个节点的 version 为 3.3.12 ,但是另一个节点为 2.7.3 ,那么这个集群最终是以 2.7.3 为统一基准运行的,推荐集群中的所有节点版本都保持一致,避免兼容性问题。

若要查看最终集群采用的协议版本,可以通过 etcd 的启动 log 查看。

操作集群

etcdctl

一般通过 etcdctl 对集群进行一些操作,在操作时需要指明节点的地址,不指明 --endpoints 参数的话默认是 http://localhost:2379,同时操作多个节点时通过英文 , 分隔。

还有一点需要注意的是,etcdctl 会读取环境变量 ${ETCDCTL_API} 的值,默认 ETCDCTL_API=2 ,而 API v2 早在几年前就停止支持了,所以以下的所有操作均建立在 ETCDCTL_API=3 的基础上。

小伙伴们可以通过以下两条命令将 etcdctl 的 API 版本默认设为 3 。

$ echo 'export ETCDCTL_API=3' >> ~/.bash_profile
$ source ~/.bash_profile

如果需要临时用 API2 的话使用 ETCDCTL_API=2 etcdctl 来执行命令就可以啦,也可以用以下方法用 etcdctlv2 来代表 API v2etcdctl

$ echo "alias etcdctlv2='ETCDCTL_API=2 etcdctl'" >> ~/.bash_profile
$ source ~/.bash_profile

查看 etcdctl 的版本号

API v3 通过 version 来查看版本号,API v2 通过 --version 来查看版本号。

$ ETCDCTL_API=3 etcdctl version
etcdctl version: 3.3.12
API version: 3.3
$ ETCDCTL_API=2 etcdctl --version
etcdctl version: 3.3.12
API version: 2

需要注意的是 API v2API v3 的操作、数据皆不互通。

$ ETCDCTL_API=3 etcdctl --version
Error: unknown flag: --version
$ ETCDCTL_API=2 etcdctl version
No help topic for 'version'

添加节点

在启动完毕一个集群(包括单节点)之后,可以通过 member add 加入新的节点,加入的新节点会自动与旧节点进行数据同步,一般很快就能数据一致。

$ ETCDCTL_API=3 etcdctl \
--endpoints=http://localhost:2379 \
member add ${node_name} \
--peer-urls=http://${node_ip}:2380

现在我们将 etcd1 加入刚才 etcd0 创建的单节点集群中:

$ ETCDCTL_API=3 etcdctl member add etcd1 --peer-urls=http://10.211.55.108:2380
Member ff81e3e0ba901ae3 added to cluster 24462beb7ff6e3a2

ETCD_NAME="etcd1"
ETCD_INITIAL_CLUSTER="etcd1=http://10.211.55.108:2380,etcd0=http://10.211.55.106:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.211.55.108:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

将得到的:

ETCD_NAME="etcd1"
ETCD_INITIAL_CLUSTER="etcd1=http://10.211.55.108:2380,etcd0=http://10.211.55.106:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.211.55.108:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

复制进 etcd1/etc/etcd/etcd.conf 中,然后在 etcd1 中执行 systemctl start etcd 启动节点 etcd1 即可。

移除节点

有的时候可能会产生节点故障,此时需要通过 member remove 来将节点移除。

  1. 查询节点 ID

假设我们要移除节点 etcd0 ,我们先查询要移除的节点的 ID

etcdctl -w=table member list
+------------------+---------+-------+---------------------------+---------------------------+
|        ID        | STATUS  | NAME  |        PEER ADDRS         |       CLIENT ADDRS        |
+------------------+---------+-------+---------------------------+---------------------------+
| 2ccace8809ee70c2 | started | etcd0 | http://10.211.55.106:2380 | http://10.211.55.106:2379 |
| ff81e3e0ba901ae3 | started | etcd1 | http://10.211.55.108:2380 | http://10.211.55.108:2379 |
+------------------+---------+-------+---------------------------+---------------------------+
  1. 执行 member remove

可以看到 etcd0IDff81e3e0ba901ae3 ,我们删除这个节点:

$ etcdctl member remove ff81e3e0ba901ae3
Member ff81e3e0ba901ae3 removed from cluster 24462beb7ff6e3a2
  1. 查看集群成员以确保删除成功
etcdctl --endpoints=http://etcd1:2379 member list
ff81e3e0ba901ae3, started, etcd1, http://10.211.55.108:2380, http://10.211.55.108:2379

需要注意的是我们已经把 etcd0 从集群中移除了,如果继续在 etcd0 所在的宿主机上执行 etcdctl 的话默认会请求 http://localhost:2379 ,所以我们需要指定我们需要请求的节点。

节点迁移

有时候可能需要将集群从一个机群迁移到另一个机群,可以在线上进行热迁移。

定义当前集群中的三个节点为 a、b、c,新节点为 A、B、C 。

  1. 将 A、B、C 节点添加至集群中
$ ETCDCTL_API=3 etcdctl --endpoints=${endpoints} member add ${etcd_name} --peer-urls=${url}
  1. 等待同步
$ ETCDCTL_API=3 etcdctl --endpoints=${endpoints} endpoint status | awk '{ print $8 }'
  1. 移除 a、b、c 节点
$ ETCDCTL_API=3 etcdctl --endpoints=${endpoints} member remove ${node_id}

升级 etcd 客户端版本

虽然升级过程无毒无害,绿色环保,但仍然建议进行一次 数据备份

  1. 停止 etcd 节点
$ systemctl stop etcd
  1. 替换 etcd 客户端
  2. 启动 etcd 节点
$ systemctl start etcd
最后修改:2022 年 04 月 24 日
谢谢老板!