0 专辑概述
etcd 是云原生架构中重要的基础组件,由 CNCF 孵化托管。etcd 在微服务和 Kubernates 集群中不仅可以作为服务注册与发现,还可以作为 key-value 存储的中间件。

《彻底搞懂 etcd 系列文章》将会从 etcd 的基本功能实践、API 接口、实现原理、源码分析,以及实现中的踩坑经验等几方面具体展开介绍 etcd。预计会有 20 篇左右的文章,笔者将会每周持续更新,欢迎关注。

1 etcd 集群部署
在生产环境中,为了整个集群的高可用,etcd 正常都会集群部署,避免单点故障。本节将会介绍如何进行 etcd 集群部署。引导 etcd 集群的启动有以下三种机制:

静态
etcd 动态发现
DNS 发现
静态启动 etcd 集群要求每个成员都知道集群中的另一个成员。 在许多情况下,群集成员的 IP 可能会提前未知。在这些情况下,可以在发现服务的帮助下引导 etcd 群集。

下面我们将会分别介绍这几种方式。

2 静态方式启动 etcd 集群
单机安装
如果想要在一台机器上实践 etcd 集群的搭建,可以通过 goreman 工具。

goreman 是一个 Go 语言编写的多进程管理工具,是对 Ruby 下广泛使用的 foreman 的重写(foreman 原作者也实现了一个 Go 版本:forego,不过没有 goreman 好用)。

我们需要确认 Go 安装环境,然后直接执行:

go get github.com/mattn/goreman
编译后的文件放在 $GOPATH/bin 中,$GOPATH/bin目录已经添加到了系统 $PATH 中,所以我们可以方便执行命令 goreman 命令。下面就是编写 Procfile 脚本,我们启动三个 etcd,具体对应如下:

HostName ip 客户端交互端口 peer 通信端口
infra1 127.0.0.1 12379 12380
infra2 127.0.0.1 22379 22380
infra3 127.0.0.1 32379 32380
Procfile 脚本如下:

etcd1: etcd --name infra1 --listen-client-urls http://127.0.0.1:12379 --advertise-client-urls http://127.0.0.1:12379 --listen-peer-urls http://127.0.0.1:12380 --initial-advertise-peer-urls http://127.0.0.1:12380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2... --initial-cluster-state new --enable-pprof --logger=zap --log-outputs=stderr
etcd2: etcd --name infra2 --listen-client-urls http://127.0.0.1:22379 --advertise-client-urls http://127.0.0.1:22379 --listen-peer-urls http://127.0.0.1:22380 --initial-advertise-peer-urls http://127.0.0.1:22380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2... --initial-cluster-state new --enable-pprof --logger=zap --log-outputs=stderr
etcd3: etcd --name infra3 --listen-client-urls http://127.0.0.1:32379 --advertise-client-urls http://127.0.0.1:32379 --listen-peer-urls http://127.0.0.1:32380 --initial-advertise-peer-urls http://127.0.0.1:32380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2... --initial-cluster-state new --enable-pprof --logger=zap --log-outputs=stderr
配置项说明:

--name:etcd集群中的节点名,这里可以随意,可区分且不重复就行
--listen-peer-urls:监听的用于节点之间通信的url,可监听多个,集群内部将通过这些url进行数据交互(如选举,数据同步等)
--initial-advertise-peer-urls:建议用于节点之间通信的url,节点间将以该值进行通信。
--listen-client-urls:监听的用于客户端通信的url,同样可以监听多个。
--advertise-client-urls:建议使用的客户端通信 url,该值用于 etcd 代理或 etcd 成员与 etcd 节点通信。
--initial-cluster-token: etcd-cluster-1,节点的 token 值,设置该值后集群将生成唯一 id,并为每个节点也生成唯一 id,当使用相同配置文件再启动一个集群时,只要该 token 值不一样,etcd 集群就不会相互影响。
--initial-cluster:也就是集群中所有的 initial-advertise-peer-urls 的合集。
--initial-cluster-state:new,新建集群的标志
注意上面的脚本,etcd 命令执行时需要根据本地实际的安装地址进行配置。下面我们启动 etcd 集群。

goreman -f /opt/procfile start
使用如上的命令启动启动 etcd 集群,启动完成之后查看集群内的成员。

$ etcdctl --endpoints=http://localhost:22379 member list

8211f1d0f64f3269, started, infra1, http://127.0.0.1:12380, http://127.0.0.1:12379, false
91bc3c398fb3c146, started, infra2, http://127.0.0.1:22380, http://127.0.0.1:22379, false
fd422379fda50e48, started, infra3, http://127.0.0.1:32380, http://127.0.0.1:32379, false
我们在单机搭建的伪集群成功,需要注意的是在集群启动时,我们是通过静态的方式指定集群的成员,在实际环境中,集群成员的 ip 可能不会提前知道。这时候就需要采用动态发现的机制。

docker 启动集群
etcd 使用 gcr.io/etcd-development/etcd 作为容器的主要加速器, quay.io/coreos/etcd 作为辅助的加速器。可惜这两个加速器我们都没法访问,如果下载不了,可以使用笔者提供的地址:

docker pull bitnami/etcd:3.4.7
然后将拉取的镜像重新 tag:

docker image tag bitnami/etcd:3.4.7 quay.io/coreos/etcd:3.4.7
镜像设置好之后,我们启动 3 个节点的 etcd 集群,脚本命令如下:

REGISTRY=quay.io/coreos/etcd

For each machine

ETCD_VERSION=3.4.7
TOKEN=my-etcd-token
CLUSTER_STATE=new
NAME_1=etcd-node-0
NAME_2=etcd-node-1
NAME_3=etcd-node-2
HOST_1= 192.168.202.128
HOST_2= 192.168.202.129
HOST_3= 192.168.202.130
CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_3}=http://${HOST_3}:2380
DATA_DIR=/var/lib/etcd

For node 1

THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
docker run \
-p 2379:2379 \
-p 2380:2380 \
--volume=${DATA_DIR}:/etcd-data \
--name etcd ${REGISTRY}:${ETCD_VERSION} \
/usr/local/bin/etcd \
--data-dir=/etcd-data --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://0.0.0.0:2380 \
--advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://0.0.0.0:2379 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}

For node 2

THIS_NAME=${NAME_2}
THIS_IP=${HOST_2}
docker run \
-p 2379:2379 \
-p 2380:2380 \
--volume=${DATA_DIR}:/etcd-data \
--name etcd ${REGISTRY}:${ETCD_VERSION} \
/usr/local/bin/etcd \
--data-dir=/etcd-data --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://0.0.0.0:2380 \
--advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://0.0.0.0:2379 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}

For node 3

THIS_NAME=${NAME_3}
THIS_IP=${HOST_3}
docker run \
-p 2379:2379 \
-p 2380:2380 \
--volume=${DATA_DIR}:/etcd-data \
--name etcd ${REGISTRY}:${ETCD_VERSION} \
/usr/local/bin/etcd \
--data-dir=/etcd-data --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://0.0.0.0:2380 \
--advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://0.0.0.0:2379 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}
注意,上面的脚本是部署在三台机器上面,每台机器执行对应的脚本即可。在运行时可以指定 API 版本:

docker exec etcd /bin/sh -c "export ETCDCTL_API=3 && /usr/local/bin/etcdctl put foo bar"
docker 的安装方式比较简单,读者根据需要可以定制一些配置。

3 动态发现启动 etcd 集群
如前面所述,在实际环境中,集群成员的 ip 可能不会提前知道。在这种情况下,需要使用自动发现来引导 etcd 集群,而不是指定静态配置,这个过程被称为「发现」。我们启动三个 etcd,具体对应如下:

HostName ip 客户端交互端口 peer 通信端口
etcd1 192.168.202.128 2379 2380
etcd2 192.168.202.129 2379 2380
etcd3 192.168.202.130 2379 2380
协议的原理
Discovery service protocol 帮助新的 etcd 成员使用共享 URL 在集群引导阶段发现所有其他成员。

该协议使用新的发现令牌来引导一个唯一的 etcd 集群。一个发现令牌只能代表一个 etcd 集群。只要此令牌上的发现协议启动,即使它中途失败,也不能用于引导另一个 etcd 集群。

「提示」:Discovery service protocol 仅用于集群引导阶段,不能用于运行时重新配置或集群监视。

Discovery protocol 使用内部 etcd 集群来协调新集群的引导程序。首先,所有新成员都与发现服务交互,并帮助生成预期的成员列表。之后,每个新成员使用此列表引导其服务器,该列表执行与 --initial-cluster 标志相同的功能,即设置所有集群的成员信息。

获取 discovery 的 token
生成将标识新集群的唯一令牌。 在以下步骤中,它将用作发现键空间中的唯一前缀。 一种简单的方法是使用uuidgen:

UUID=$(uuidgen)
指定集群的大小
获取令牌时,必须指定群集大小。 发现服务使用该大小来了解何时发现了最初将组成集群的所有成员。

curl -X PUT http://10.0.10.10:2379/v2/key... -d value=3
我们需要把该 url 地址 http://10.0.10.10:2379/v2/key... 作为 --discovery 参数来启动 etcd。

节点会自动使用 http://10.0.10.10:2379/v2/key... 目录进行 etcd 的注册和发现服务。

公共发现服务
当我们本地没有可用的 etcd 集群,etcd 官网提供了一个可以公网访问的 etcd 存储地址。我们可以通过如下命令得到 etcd 服务的目录,并把它作为 --discovery 参数使用。

公共发现服务 discovery.etcd.io 以相同的方式工作,但是有一层修饰,可以提取丑陋的 URL,自动生成 UUID,并提供针对过多请求的保护。公共发现服务在其上仍然使用 etcd 群集作为数据存储。

$ curl http://discovery.etcd.io/new?...

http://discovery.etcd.io/3e86...
以动态发现方式启动集群
etcd 发现模式下,启动 etcd 的命令如下:

etcd1 启动

$ /opt/etcd/bin/etcd --name etcd1 --initial-advertise-peer-urls http://192.168.202.128:2380 \
--listen-peer-urls http://192.168.202.128:2380 \
--data-dir /opt/etcd/data \
--listen-client-urls http://192.168.202.128:2379,h... \
--advertise-client-urls http://192.168.202.128:2379 \
--discovery https://discovery.etcd.io/3e8...

etcd2 启动

/opt/etcd/bin/etcd --name etcd2 --initial-advertise-peer-urls http://192.168.202.129:2380 \
--listen-peer-urls http://192.168.202.129:2380 \
--data-dir /opt/etcd/data \
--listen-client-urls http://192.168.202.129:2379,h... \
--advertise-client-urls http://192.168.202.129:2379 \
--discovery https://discovery.etcd.io/3e8...

etcd3 启动

/opt/etcd/bin/etcd --name etcd3 --initial-advertise-peer-urls http://192.168.202.130:2380 \

--listen-peer-urls http://192.168.202.130:2380 \
--data-dir /opt/etcd/data \
--listen-client-urls http://192.168.202.130:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.202.130:2379 \
--discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de

需要注意的是,在我们完成了集群的初始化后,这些信息就失去了作用。当需要增加节点时,需要使用 etcdctl 进行操作。为了安全,每次启动新 etcd 集群时,都使用新的 discovery token 进行注册。另外,如果初始化时启动的节点超过了指定的数量,多余的节点会自动转化为 Proxy 模式的 etcd。

结果验证
集群启动好之后,进行验证,我们看一下集群的成员:

$ /opt/etcd/bin/etcdctl member list

结果如下

40e2ac06ca1674a7, started, etcd3, http://192.168.202.130:2380, http://192.168.202.130:2379, false
c532c5cedfe84d3c, started, etcd1, http://192.168.202.128:2380, http://192.168.202.128:2379, false
db75d3022049742a, started, etcd2, http://192.168.202.129:2380, http://192.168.202.129:2379, false

结果符合预期,再看下节点的健康状态:

$ /opt/etcd/bin/etcdctl --endpoints="http://192.168.202.128:2379,


已注销
1 声望2 粉丝