前言

本文采用两台虚拟机为 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 v2 的 etcdctl 。

$ 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 v2 和 API 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

可以看到 etcd0 的 ID 为 ff81e3e0ba901ae3 ,我们删除这个节点:

$ 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 日
谢谢老板!