欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

搭建 etcd 集群

程序员文章站 2022-07-13 22:19:46
...

etcd 集群的初始化有 3 种方式:静态启动、基于 etcd 的服务发现、基于 DNS 的服务发现。

而无论使用哪种方式,都需要事先确定初始化集群的大小。

现在假设我们需要使用以下 3 个节点启动 etcd 集群:

Name Address Hostname
infra0 10.0.1.10 infra0.example.com
infra1 10.0.1.11 infra1.example.com
infra2 10.0.1.12 infra2.example.com

静态启动

静态启动的前提条件是我们已经知道三个节点的域名或 IP 地址以及要监听的端口。静态启动又称为离线启动,它不依赖网络中其他的服务。

在每个成员上都需要加上启动标识 initial-cluster 或环境变量 ETCD_INITIAL_CLUSTER,值为逗号分隔的键值对。每一个键值对都表示集群中的一个 etcd 服务节点。键是节点的名称,也就是启动标识 --name 的值;值则是对应的节点启动标识 --initial-advertise-peer-urls 的值。

同时还需要设置启动标识 initial-cluster-state 或环境变量 ETCD_INITIAL_CLUSTER_STATE 的值为 new

--initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
--initial-cluster-state new
ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380"
ETCD_INITIAL_CLUSTER_STATE=new

如果在同一个网络中需要启动多个 etcd 集群,为了避免相互干扰,你应该设置启动标识 initial-cluster-token 以区分不同的集群。

启动标识 listen-client-urls 的值是 etcd 用于接收客户端请求的地址。etcd 会将 advertise-client-urls 的值通告给其他的成员、代理、客户端。所以必须确保 advertise-client-urls 对客户端可达。一个常见的错误是将其设置为 localhost 或保持默认值。

在每台主机上,启动命令应该如下:

$ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 \
  --listen-peer-urls http://10.0.1.10:2380 \
  --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.10:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  --initial-cluster-state new
$ etcd --name infra1 --initial-advertise-peer-urls http://10.0.1.11:2380 \
  --listen-peer-urls http://10.0.1.11:2380 \
  --listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.11:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  --initial-cluster-state new
$ etcd --name infra2 --initial-advertise-peer-urls http://10.0.1.12:2380 \
  --listen-peer-urls http://10.0.1.12:2380 \
  --listen-client-urls http://10.0.1.12:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.12:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  --initial-cluster-state new

当后续再次启动 etcd 时,--initial-cluster 标识会被忽略。所以,在初始化启动完成后,将这个启动标识或环境变量删除也是没问题的。以后需要修改配置(比如增减成员)都应通过运行时配置做变更。

基于 etcd 的服务发现

在某些情况下,你可能只知道需要初始化的集群规模,但并不确定所有节点的地址。这在使用云服务的时候极为常见。我们的解决方式是服务发现。服务发现可以有两种办法,一种是基于已有的 etcd,另一种是基于 DNS SRV。

使用基于 etcd 的服务发现,你需要已有一个 etcd 集群(单节点也可以)提供发现服务。如果你的服务器可以联网,那也可以使用 CoreOS 官方提供的公共发现服务:https://discovery.etcd.io/。

假设我们本地有一个 etcd 服务 https://myetcd.local,那么我们需要向这个 etcd 写入数据:

curl -X PUT https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size -d value=3

通过设置 size 的值为 3,我们指定了集群的规模为 3 个节点。服务的 discovery URL 就是 https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83。所有 etcd 成员都应该使用这个 URL 加入集群。

每一个成员都需要使用不同的名称,否则会导致服务发现失败。

各成员启动命令如下:

$ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 \
  --listen-peer-urls http://10.0.1.10:2380 \
  --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.10:2379 \
  --discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
$ etcd --name infra1 --initial-advertise-peer-urls http://10.0.1.11:2380 \
  --listen-peer-urls http://10.0.1.11:2380 \
  --listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.11:2379 \
  --discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
$ etcd --name infra2 --initial-advertise-peer-urls http://10.0.1.12:2380 \
  --listen-peer-urls http://10.0.1.12:2380 \
  --listen-client-urls http://10.0.1.12:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.12:2379 \
  --discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83

这样,每个成员都会将自己注册到 etcd 发现服务上。当所有节点都注册上之后,集群就会启动了。

使用公共的 discovery.etcd.io 时,创建服务的命令略有不同:

$ curl https://discovery.etcd.io/new?size=3
https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de

这将返回一个初始化规模为 3 个节点的 discovery URL。启动时只需将 --discovery 的值设为此 URL 即可。

如果需要的话,你还可以设置环境变量 ETCD_DISCOVERY_PROXY。这样 etcd 会使用一个 HTTP 代理来访问发现服务。

发现服务协议

我们有必要深入学习下发现服务协议。

这个协议可以在集群初始化启动阶段让新的 etcd 成员通过一个共享的 URL 发现其他成员。

需要注意的是发现服务协议只能用在集群初始化启动的阶段,不能用在运行时配置或集群监控。

协议工作的流程大致如下:创建一个发现 token -> 提供初始化集群的大小 -> 启动 etcd 进程 -> 自注册 -> 检测状态 -> 等待其他成员。

为便于理解,我们接下来使用 curl 示范每一步操作。

按照惯例,etcd 发现服务协议使用 key 前缀 _etcd/registry。如果服务发现的 etcd 地址是 http://example.com,那么键空间就是 http://example.com/v2/keys/_etcd/registry。

创建发现 token

首先需要生成一个唯一的 token 来代表集群,一个简单的办法是使用 uuidgen

UUID=$(uuidgen)
提供初始化集群的大小

发现服务需要通过所提供的初始化集群的大小来确定什么时候算是发现了所有成员。

curl -X PUT http://example.com/v2/keys/_etcd/registry/${UUID}/_config/size -d value=${cluster_size}

常用的集群大小是3、5、7。

启动 etcd 进程

启动 etcd 进程时需要设置启动标识 --discovery ,值为 discovery URL http://example.com/v2/keys/_etcd/registry/${UUID}。当设置了此启动标识后,所有 etcd 进程都会遵循后续的步骤。

自注册

etcd 进程首先要做的事情就是将自己作为一个成员注册到 discovery URL 中。这个是通过向discovery URL 创建 key 为成员 ID ${member_id} 实现的:

curl -X PUT http://example.com/v2/keys/_etcd/registry/${UUID}/${member_id}?prevExist=false -d value="${member_name}=${member_peer_url_1}&${member_name}=${member_peer_url_2}"
检测状态

etcd 进程检测 discovery URL 中预期的集群大小和注册状态,然后决定下一步的行为。

curl -X GET http://example.com/v2/keys/_etcd/registry/${UUID}/_config/size
curl -X GET http://example.com/v2/keys/_etcd/registry/${UUID}

如果已注册成员未满,它会等待剩余的成员出现。

如果已注册成员数量超过了预期的数量 N,它会将前 N 个已注册的成员视为集群的成员列表。

如果其自身在集群的成员列表内,那么发现过程成功结束。如果其不在成员列表中,发现过程就因为集群已满而以失败结束。

在 etcd 的实现中,成员可能会在自注册之前就去检测状态。这样在集群已满的时候就能迅速失败。

等待所有成员

etcd 进程通过检测当前已注册的成员数量来确定是否已等到所有成员的加入:

curl -X GET http://example.com/v2/keys/_etcd/registry/${UUID}?wait=true&waitIndex=${current_etcd_index}

等待变更的原理是使用长轮询来监控 key 的变化。上面的 GET 参数 wait=true 表示长轮询,监控的 key 为 _etcd/registry/${UUID}。当其值发生变化的时候,etcd 进程会接收到一个提醒,内容为变更前后的值。

基于 DNS 的服务发现

DNS SRV 记录也可以用于服务发现。启动标识 --discovery-srv 可以设为能找到 SRV 记录的 DNS 域名(如 example.com)。下面的 DNS SRV 记录会依序被查询:

  • _etcd-server-ssl._tcp.example.com
  • _etcd-server._tcp.example.com

如果找到了 _etcd-server-ssl._tcp.example.com,那么 etcd 会尝试使用 TLS 启动进程。

为了协助客户端发现 etcd 集群,下面的 DNS SRV 记录会依序被查询:

  • _etcd-client._tcp.example.com
  • _etcd-client-ssl._tcp.example.com

如果找到了 _etcd-client-ssl._tcp.example.com,那么客户端会尝试通过 SSL/TLS 与 etcd 集群通讯。

如果 etcd 未使用自定义的 CA 启用 TLS,那么发现域(比如 example.com)必须匹配 SRV 记录域(比如 infra1.example.com)。这是为了减缓伪造 SRV 记录到另一个域名的攻击。

创建 DNS SRV 记录

$ dig +noall +answer SRV _etcd-server._tcp.example.com
_etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra0.example.com.
_etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra1.example.com.
_etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra2.example.com.
$ dig +noall +answer SRV _etcd-client._tcp.example.com
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra0.example.com.
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra1.example.com.
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra2.example.com.
$ dig +noall +answer infra0.example.com infra1.example.com infra2.example.com
infra0.example.com.  300  IN  A  10.0.1.10
infra1.example.com.  300  IN  A  10.0.1.11
infra2.example.com.  300  IN  A  10.0.1.12

使用 DNS 启动 etcd 集群

etcd 集群的成员可以在域名也可以在 IP 地址上监听,启动进程会解析 DNS 的 A 记录。

--initial-advertise-peer-urls 中的 A 记录解析的结果必须匹配 SRV 目标中的地址。

$ etcd --name infra0 \
--discovery-srv example.com \
--initial-advertise-peer-urls http://infra0.example.com:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster-state new \
--advertise-client-urls http://infra0.example.com:2379 \
--listen-client-urls http://infra0.example.com:2379 \
--listen-peer-urls http://infra0.example.com:2380
$ etcd --name infra1 \
--discovery-srv example.com \
--initial-advertise-peer-urls http://infra1.example.com:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster-state new \
--advertise-client-urls http://infra1.example.com:2379 \
--listen-client-urls http://infra1.example.com:2379 \
--listen-peer-urls http://infra1.example.com:2380
$ etcd --name infra2 \
--discovery-srv example.com \
--initial-advertise-peer-urls http://infra2.example.com:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster-state new \
--advertise-client-urls http://infra2.example.com:2379 \
--listen-client-urls http://infra2.example.com:2379 \
--listen-peer-urls http://infra2.example.com:2380