8.集群部署与管理
在构建高可用的 etcd 集群之前,理解其底层架构是至关重要的。etcd 基于 Raft 共识算法,这意味着集群中的所有节点通过选举一个领导者(Leader)来协调数据的写入。一个标准的 etcd 集群通常由奇数个节点组成(例如 3 个或 5 个),以确保在发生网络分区时能够维持法定人数(Quorum)。
etcd 集群中的节点通过两类网络连接进行通信:
- 客户端网络(Client Network):用于与客户端通信,处理键值对的读写请求。通常监听在
2379端口。 - 对等网络(Peer Network):用于集群内部节点间的通信,如同步 Raft 日志、选举领导者等。通常监听在
2380端口。
在启动集群时,每个节点都需要明确自己的身份(Name)、监听地址(Listen URLs)以及向其他节点广播的地址(Advertise URLs)。配置的正确性直接决定了集群能否成功启动。例如,initial-advertise-peer-urls 必须是其他节点可达的地址,如果配置为 localhost,那么其他节点将无法与之建立连接,导致集群无法形成。
静态集群配置
静态配置是最直接、最可靠的集群启动方式,适用于所有节点的 IP 地址或主机名在部署前就已知的场景。这种方式不依赖任何外部服务,仅通过本地配置即可完成集群的引导。
在静态配置中,我们需要在每个节点上指定 --initial-cluster 参数。这个参数是一个列表,包含了集群中所有节点的名称及其对应的对等 URL。例如,对于一个包含三个节点的集群,配置可能如下所示:
--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,表示这是一个全新的集群。
让我们看一个具体的启动示例。假设我们有三台机器,IP 分别为 10.0.1.10、10.0.1.11 和 10.0.1.12。在第一台机器 infra0 上,启动命令如下:
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 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
这个命令做了几件事:
--name infra0:为当前节点指定一个唯一的名称。--listen-peer-urls和--listen-client-urls:定义节点监听的地址,0.0.0.0表示监听所有网络接口。--initial-advertise-peer-urls和--advertise-client-urls:向集群其他成员和客户端广播的地址。--initial-cluster:定义整个集群的成员列表。
在配置静态集群时,有一个常见的陷阱:initial-cluster 中指定的 URL 必须与每个节点上 --initial-advertise-peer-urls 的配置完全一致。如果出现不匹配,etcd 将会报错并退出。例如,如果在 infra0 上将 --initial-advertise-peer-urls 设置为 http://127.0.0.1:2380,但在 --initial-cluster 中它被列为 http://10.0.1.10:2380,etcd 会检测到这种不一致并拒绝启动。这是为了防止节点在集群中使用错误的身份标识。
此外,为了在测试环境中区分不同的集群,可以使用 --initial-cluster-token 参数为集群设置一个唯一的 ID。这可以防止不同集群之间的数据混淆。
etcd 发现服务
在很多云环境或动态网络中,节点的 IP 地址可能在部署时是未知的,或者会频繁变动。在这种情况下,静态配置就显得力不从心。etcd 提供了一种基于现有 etcd 集群来引导新集群的机制,称为“发现服务”(Discovery Service)。
发现服务的核心思想是利用一个中心化的注册表(由一个独立的 etcd 集群提供服务)来协调新集群的成员。新节点启动时,会向这个注册表注册自己,并等待其他节点的注册。当注册的节点数量达到预期的集群大小时,所有节点就可以获取到完整的成员列表,从而完成集群的引导。
自建 etcd 发现服务
如果你有一个现有的 etcd 集群(或者为了引导新集群而临时启动一个),你可以将其用作私有的发现服务。
首先,你需要创建一个发现 URL。这实际上是向该 etcd 集群写入一个配置键,指定期望的集群大小。例如,使用 curl 创建一个大小为 3 的集群发现 URL:
curl -X PUT https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size -d value=3
这里的 6c007a14875d53d9bf0ef5a6fc0257c817f0fb83 是一个随机生成的 UUID,用于唯一标识这个新集群。
然后,在每个新节点的启动命令中,使用 --discovery 参数指向这个 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
一个关键的注意事项是,每个节点必须使用不同的 --name。如果两个节点使用了相同的名称,发现服务会因为名称冲突而失败。
使用公共发现服务
对于没有私有 etcd 集群的用户,etcd 官方曾提供一个公共的发现服务(discovery.etcd.io),但请注意,该服务已在 2023 年 1 月 31 日停止服务。因此,在生产环境中,强烈建议使用私有发现服务或静态配置。
尽管如此,了解其工作原理仍然有价值。使用公共服务的流程是类似的,首先通过一个简单的 HTTP 请求获取一个唯一的发现 URL:
# 注意:此服务已停止,仅作演示
curl https://discovery.etcd.io/new?size=3
# 输出: https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
然后将这个 URL 传递给 --discovery 参数。其内部机制与私有发现服务完全相同。
DNS SRV 记录集成
除了基于 etcd 的发现服务,etcd 还支持使用 DNS SRV 记录进行服务发现。这对于那些已经建立了成熟 DNS 基础设施的环境非常有用。
DNS SRV 记录允许你将服务的域名和端口信息通过 DNS 查询来获取。etcd 会查询特定的 SRV 记录来发现集群的对等节点。
要使用 DNS SRV 发现,你需要在 DNS 服务器上配置以下记录:
- SRV 记录:用于发现 etcd 服务器节点。
_etcd-server-ssl._tcp.example.com(用于 TLS)_etcd-server._tcp.example.com(用于非 TLS)
- A 记录:将 SRV 记录中使用的主机名解析为 IP 地址。
例如,你的 DNS 记录可能如下:
_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.
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 节点时,只需指定 --discovery-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://0.0.0.0:2379 \
--listen-peer-urls http://0.0.0.0:2380
etcd 会自动查询 _etcd-server._tcp.example.com SRV 记录,获取所有潜在的对等节点地址,然后尝试与它们通信以完成集群引导。
安全提示:如果 etcd 配置了 TLS,那么发现服务使用的域名(例如 example.com)必须包含在 TLS 证书的 Subject Alternative Name (SAN) 字段中,否则节点间的 TLS 握手会失败。
运行时成员管理
集群启动后,我们可能需要根据业务需求或硬件故障来调整集群的成员。etcd 支持在集群运行期间动态地添加、删除或更新成员,这被称为“运行时重配置”。
运行时重配置是通过 etcdctl member 命令或相应的 gRPC/HTTP API 来完成的。这些操作会修改集群的元数据,因此必须在集群健康且能够达成共识(即大多数节点在线)的情况下进行。
重配置的核心原则是安全第一。etcd 的设计中包含了一系列保护机制,以防止误操作导致集群不可用。例如,它会严格检查重配置操作是否会导致集群丢失仲裁(Quorum)。
一个常见的场景是替换一个故障的节点。这个过程分为两步:
- 从集群中移除故障节点。
- 添加一个新节点来替代它。
在执行任何重配置操作之前,强烈建议先了解当前集群的状态。使用 etcdctl member list 可以查看所有成员的 ID、状态、名称和地址。这是进行任何变更前的必备步骤。
成员增删操作
下面我们详细讲解如何使用 etcdctl 进行成员的添加和删除。
移除成员
假设一个名为 infra3 的节点(ID 为 278c654c9a6dfd3b)发生了故障,需要将其从集群中移除。
首先,确认要移除的成员 ID。你可以通过 etcdctl member list 获取。
export ETCDCTL_API=3
etcdctl --endpoints=http://10.0.1.10:2379,http://10.0.1.11:2379 member list
输出会类似这样:
6e3bd23ae5f1eae0, started, infra1, http://10.0.1.11:2380, http://10.0.1.11:2379
924e2e83e93f2560, started, infra2, http://10.0.1.12:2380, http://10.0.1.12:2379
a8266ecf031671f3, started, infra3, http://10.0.1.13:2380, http://10.0.1.13:2379
确认 ID 后,执行移除命令:
etcdctl member remove a8266ecf031671f3
成功后,你会看到类似 Removed member a8266ecf031671f3 from cluster 的提示。被移除的节点会感知到自己被踢出集群,并自动关闭。
添加成员
添加一个新成员(例如 infra4,IP 为 10.0.1.14)来替代被移除的节点。
使用 etcdctl member add 命令:
etcdctl member add infra4 --peer-urls=http://10.0.1.14:2380
这个命令会通知集群有一个新成员将要加入,并返回一些关键的环境变量,这些变量是启动新节点所必需的:
Member 2be1b35fc7761a23 added to cluster ef37d9efc7284bdc
ETCD_NAME="infra4"
ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra4=http://10.0.1.14:2380"
ETCD_INITIAL_CLUSTER_STATE=existing
注意:这里 ETCD_INITIAL_CLUSTER_STATE 的值是 existing,这非常关键。它告诉新节点这是一个已有集群的扩展,而不是创建一个全新的集群。
现在,登录到新节点 10.0.1.14,使用这些返回的环境变量来启动 etcd 进程:
export ETCD_NAME="infra4"
export ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra4=http://10.0.1.14:2380"
export ETCD_INITIAL_CLUSTER_STATE=existing
etcd --data-dir=/var/lib/etcd \
--name ${ETCD_NAME} \
--initial-advertise-peer-urls http://10.0.1.14:2380 \
--listen-peer-urls http://10.0.1.14:2380 \
--listen-client-urls http://10.0.1.14:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://10.0.1.14:2379 \
--initial-cluster ${ETCD_INITIAL_CLUSTER} \
--initial-cluster-state ${ETCD_INITIAL_CLUSTER_STATE}
新节点启动后,会尝试联系现有集群中的任何一个成员,验证配置并同步数据,最终成功加入集群。
更新成员
除了增删,有时也需要更新现有成员的对等 URL(例如,节点 IP 变更或端口调整)。
首先,获取要更新的成员 ID。
etcdctl member list
然后使用 member update 命令:
# 假设要更新 ID 为 a8266ecf031671f3 的成员
etcdctl member update a8266ecf031671f3 --peer-urls=http://10.0.1.20:2380
更新操作会修改集群的元数据。之后,你需要重启该成员,并更新其启动参数中的 --initial-advertise-peer-urls 和 --listen-peer-urls 以匹配新的地址。
通过这些运行时管理工具,你可以灵活地维护 etcd 集群,应对各种运维场景,确保服务的持续高可用。