在分布式系统中,特别是在像 etcd 这样的共识驱动系统中,运行时重配置是最具挑战性和易出错的功能之一。etcd 作为一个分布式一致性数据库,如何确保在运行时安全地修改集群配置,是其设计中的重要一环。在本文中,我们将深入探讨 etcd 运行时重配置命令的设计原理,并分析如何解决这些问题。
两阶段配置变更:确保集群安全
etcd 中的每次运行时重配置都必须经过两个阶段,以确保集群的安全性。例如,若要向集群添加一个新成员,首先需要通知集群新的配置,然后再启动新成员。
阶段 1 - 通知集群新配置
在 etcd 集群中添加一个新成员,首先需要通过 API 调用来请求将新成员添加到集群中。这是将新成员添加到现有集群的唯一方法。API 调用会在集群对配置更改达成一致后返回。
在这个阶段,etcd 集群会验证新成员的配置是否与当前集群的配置一致,并确保不会由于配置错误导致集群状态混乱。如果新成员的 ID 与已存在的成员冲突,集群会立即在阶段一失败,而不会影响现有集群的运行。
阶段 2 - 启动新成员
一旦阶段一成功完成,接下来就可以启动新成员。新成员需要正确设置 initial-cluster
和 initial-cluster-state
参数,以确保其正确加入现有集群。当新成员启动时,它会首先与现有集群联系,验证当前集群的配置是否符合 initial-cluster
中指定的期望配置。只有在新成员成功启动并与集群达成一致时,集群配置才会被认为已经更新完成。
通过将配置更改分为两个独立的阶段,etcd 强制用户明确操作集群成员的变更,从而增加了配置过程的可控性与灵活性。例如,在阶段一,若试图添加一个与现有成员 ID 相同的新成员,操作会立即失败而不会影响集群的正常运行。
这种明确的工作流可以有效防止集群成员的意外更改。没有这个明确的工作流,etcd 将容易受到意外的集群成员变更影响。例如,如果 etcd 在类似 systemd
的初始化系统下运行,在通过 API 移除某个成员后,etcd 会被重启,并试图在启动时重新加入集群。这个循环将会持续发生,直到手动干预。
永久失去法定人数需要重新创建集群
如果 etcd 集群永久失去了大多数成员,就需要通过从旧的数据目录恢复来启动一个新的集群,以恢复之前的状态。
虽然强制移除失败的成员以恢复集群是可行的,但我们决定不支持这种方式,因为它绕过了正常的共识提交阶段,这是不安全的。如果要移除的成员实际上并未宕机,或者通过集群中的其他成员强制移除,etcd 可能会导致集群出现分歧,并保持相同的 clusterID
。这种情况非常危险,并且很难调试和修复。
在正确部署的情况下,永久失去法定人数的可能性非常低。然而,这种情况足够严重,需要特别关注。我们强烈建议,在将 etcd 投入生产环境之前,详细阅读灾难恢复文档并为可能的永久法定人数丧失做好准备。
运行时重配置不应依赖公共发现服务
公共发现服务仅应在集群启动时使用,帮助初始化集群。要加入一个现有集群,应该使用运行时重配置 API。
公共发现服务的设计初衷是帮助在云环境中初始化一个 etcd 集群,此时所有成员的 IP 地址尚未预先知道。一旦集群成功初始化,所有成员的 IP 地址就会被确定。技术上讲,公共发现服务在集群启动后应当不再需要。
虽然使用公共发现服务似乎是一个便捷的方式来进行运行时重配置,因为公共发现服务已经拥有所有集群的配置信息,但依赖公共发现服务会带来一些问题:
- 引入外部依赖:公共发现服务需要在整个集群生命周期内提供支持,而不仅仅是在集群初始化阶段。如果集群与公共发现服务之间的网络出现问题,整个集群将受到影响。
- 运行时配置一致性问题:公共发现服务必须反映集群的正确运行时配置,并且需要具备安全机制以避免恶意行为,这在实践中非常困难。
- 高负载压力:公共发现服务需要处理成千上万的集群配置。这对于公共发现服务的后台来说,可能会产生巨大的负载,尤其是在集群规模较大时。
为了避免这些问题,最佳的做法是构建一个私有的发现服务来支持运行时重配置。这样可以确保集群成员的 IP 地址和配置信息始终是最新且安全的,同时避免公共服务的潜在风险。
结论
etcd 的运行时重配置是确保集群安全和稳定的核心功能之一。通过引入两阶段配置变更流程,etcd 能够显著降低因错误操作带来的风险,同时使用户在操作时更加清晰和可控。通过避免使用公共发现服务进行运行时配置,我们可以避免外部依赖和安全问题,从而使集群的运行更加可靠。对于生产环境中的 etcd 集群,我们建议认真阅读相关文档并为可能发生的永久法定人数丧失做好准备,以确保集群的高可用性和数据安全。