集群形成与节点发现
概述
本指南涵盖了各种面向自动化的集群形成和节点发现功能。有关 RabbitMQ 集群的通用概述,请参阅 集群指南。
本指南假设您已对 RabbitMQ 集群 有基本了解,并侧重于节点发现子系统。例如,它不会涵盖节点间通信 必须打开的端口、节点如何相互认证等。除了发现机制和 它们的配置 之外,本指南还涵盖了 集群形成期间的功能可用性、节点重新加入、节点并行启动时 初始集群形成 的竞态条件问题,以及某些发现实现提供的 附加健康检查 等密切相关的主题。
本指南还涵盖了 节点发现故障排除 的基础知识。
什么是节点发现?
要形成集群,新节点(“空白”节点)需要能够发现它们的同伴。这可以通过各种机制(后端)来完成。某些机制假定所有集群成员都预先知道(例如,列在配置文件中),而其他机制是动态的(节点可以加入和离开)。
所有节点发现机制都假定新加入的节点能够成功联系集群中的同伴并进行身份验证。依赖外部服务(例如 DNS 或 Consul)或 API(例如 AWS 或 Kubernetes)的机制需要服务或 API 在其标准端口上可用且可达。无法访问这些服务将导致节点无法加入集群。
可用的发现机制
以下机制已内置于核心且始终可用
其他节点发现机制可通过插件获得。以下节点发现插件随 支持的 RabbitMQ 版本 一起提供
上述插件无需安装,但与所有 插件 一样,在使用它们之前必须 启用 或 预先配置。
对于必须在节点启动时可用的节点发现插件,这意味着它们必须在首次节点启动之前启用。下面的示例使用了 rabbitmq-plugins 的 --offline 模式
rabbitmq-plugins --offline enable <plugin name>
更具体的示例
rabbitmq-plugins --offline enable rabbitmq_peer_discovery_k8s
具有属于未启用节点发现插件的配置设置的节点将无法启动,并报告这些设置为未知。
指定节点发现机制
要使用的发现机制在 配置文件 中指定,并且还可以配置各种特定于机制的设置,例如发现服务主机名、凭据等。cluster_formation.peer_discovery_backend 是控制使用哪个发现模块(实现)的键。
cluster_formation.peer_discovery_backend = classic_config
# The backend can also be specified using its module name. Note that
# module names do not necessarily match plugin names exactly.
# cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config
该模块必须实现 rabbit_peer_discovery_backend 行为。因此,插件可以引入自己的发现机制。
节点发现如何工作
当节点启动并检测到它没有先前初始化的数据库时,它会检查是否配置了节点发现机制。如果是这种情况,它将执行发现并按顺序尝试联系每个发现的同伴。最后,它将尝试加入第一个可达同伴的集群。
根据使用的后端(机制),节点发现过程可能涉及联系外部服务,例如 AWS API 端点、Consul 节点或执行 DNS 查询。一些后端要求节点进行注册(告知后端节点已启动并应被视为集群成员):例如,Consul 和 etcd 都支持注册。使用其他后端时,节点列表会提前配置好(例如配置文件)。因此,这些后端被称为不支持节点注册。
在某些情况下,节点注册是隐式的或由外部服务管理的。AWS 自动扩展组是一个很好的例子:AWS 会跟踪组的成员资格,因此节点不必(或不能)显式注册。但是,集群成员列表不是预定义的。这些后端通常包括一个 no-op 注册步骤,并应用下面描述的 竞态条件缓解机制 之一。
如果配置的后端支持注册,则节点在收到停止指令时会注销。
可以使用配置选项 cluster_formation.registration.enabled 完全选择退出注册。
cluster_formation.registration.enabled = false
按此方式配置后,节点必须手动注册或使用其他机制进行注册,例如通过容器编排器,如 Nomad。
如果未配置节点发现,或者 重复失败,或者没有同伴可达,那么以前不是集群成员的节点将从头开始初始化并作为独立节点继续运行。节点会将节点发现的进度和结果 记录下来。
如果一个节点以前是集群成员,它会在一段时间内尝试联系并重新加入其“最后看到的”同伴。在这种情况下,不会执行节点发现。这适用于所有后端。
集群形成与功能可用性
总的来说,一个只部分形成的集群,也就是说,只有一部分节点加入它,客户端 **必须将其视为完全可用**。
单个节点将在集群形成之前接受 客户端连接。在这种情况下,客户端应准备好某些功能不可用。例如,仲裁队列 在集群节点数量不等于或超过配置的副本数量的仲裁之前不可用。
背后的 功能标志 的功能在集群形成完成之前也可能不可用。
节点重新加入其现有集群
新节点加入集群只是一个可能的情况。另一个常见场景是现有集群成员临时离开然后重新加入集群。虽然节点发现子系统不影响本节所述的行为,但了解节点在重启或故障后重新加入集群时的行为很重要。
现有集群成员 **不会执行节点发现**。相反,它们将尝试联系它们先前已知的同伴。
如果一个节点以前是集群成员,当它启动时,它会在一段时间内尝试联系其“最后看到的”同伴。如果该同伴未启动(例如,当执行完整的集群重启或升级时)或无法访问,节点将重试该操作若干次。
默认情况下,重试次数为 10 次,每次尝试 30 秒,总共 5 分钟。在节点启动时间可能很长和/或不均匀的环境中,建议增加重试次数。
如果节点在失去与集群的联系后被重置,它将 表现得像一个空白节点。请注意,其他集群成员可能仍将其视为集群成员,在这种情况下,双方将不一致,节点将无法加入。这些重置的节点还必须使用 rabbitmqctl forget_cluster_node 从集群中移除,该命令针对现有集群成员执行。
如果一个节点被操作员显式地从集群中移除然后重置,它将能够作为一个新成员加入集群。在这种情况下,它的行为将 与空白节点完全一样。
节点在节点名称或主机名更改后重新加入可能会启动为 空白节点,如果其数据目录路径因此而更改。这些节点将无法重新加入集群。当节点离线时,其同伴可能会被重置或使用空白数据目录启动。在这种情况下,恢复节点也无法重新加入其同伴,因为内部数据存储的集群标识将不再匹配。
考虑以下场景
- 形成了一个由 A、B 和 C 三个节点组成的集群
- 节点 A 被关闭
- 节点 B 被重置
- 节点 A 被启动
- 节点 A 尝试重新加入 B,但 B 的集群标识已更改
- 节点 B 不识别 A 为已知的集群成员,因为它已被重置
在这种情况下,节点 B 将拒绝来自 A 的集群尝试,并在日志中显示相应的错误消息。
Node 'rabbit@node1.local' thinks it's clustered with node 'rabbit@node2.local', but 'rabbit@node2.local' disagrees
在这种情况下,B 可以再次被重置,然后就能加入 A,或者 A 可以被重置,然后成功加入 B。
如何配置节点发现
节点发现插件就像核心服务器和其他插件一样配置:使用 配置文件。
cluster_formation.peer_discovery_backend 是 控制将使用哪个节点发现后端 的键。每个后端还将具有一些特定于它的配置设置。本指南的其余部分将介绍特定于特定机制的可配置设置,并为每个设置提供示例。
配置文件节点发现后端
配置文件节点发现概述
节点发现集群同伴的最基本方法是从配置文件中读取节点列表。假定集群成员集在部署时已知。
配置
同伴节点使用 cluster_formation.classic_config.nodes 配置设置列出。
cluster_formation.peer_discovery_backend = classic_config
# the backend can also be specified using its module name
# cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config
cluster_formation.classic_config.nodes.1 = rabbit@hostname1.eng.example.local
cluster_formation.classic_config.nodes.2 = rabbit@hostname2.eng.example.local
DNS 节点发现后端
此节点发现机制对 影响主机名解析 的操作系统和 RabbitMQ 配置很敏感。
例如,修改 本地 hosts 文件 的部署工具可能会影响(破坏)此节点发现机制。
DNS 节点发现概述
自 RabbitMQ 3.7.0 起,另一个内置的节点发现机制是基于 DNS 的。它依赖于具有 DNS A (或 AAAA) 记录的预配置主机名(“种子主机名”)以及反向 DNS 查找来执行节点发现。更具体地说,该机制将执行以下步骤:
- 查询种子主机名的 DNS A 记录。
- 对每个返回的 DNS 记录的 IP 地址执行反向 DNS 查找。
- 将当前节点的名称前缀(例如,`rabbit@hostname1.example.local` 中的
rabbit)附加到每个主机名并返回结果。
例如,我们考虑一个种子主机名为 discovery.eng.example.local。它有两个 DNS A 记录,返回两个 IP 地址:192.168.100.1 和 192.168.100.2。这些 IP 地址的反向 DNS 查找分别返回 node1.eng.example.local 和 node2.eng.example.local。当前节点名称未设置,默认为 rabbit@$(hostname)。发现的节点最终列表将包含两个节点:rabbit@node1.eng.example.local 和 rabbit@node2.eng.example.local。
配置
种子主机名使用 cluster_formation.dns.hostname 配置设置。
cluster_formation.peer_discovery_backend = dns
# the backend can also be specified using its module name
# cluster_formation.peer_discovery_backend = rabbit_peer_discovery_dns
cluster_formation.dns.hostname = discovery.eng.example.local
容器化环境中的主机文件修改
在某些容器化环境中,本地 hosts 文件 在容器启动时会被修改。这可能会影响主机上的主机名解析,并使此节点发现机制无法正常工作。
在某些容器化环境中,本地 hosts 文件 在容器启动时会被修改,例如,一个基于配置或约定的本地主机名可能会被添加到其中。
这可能会影响主机上的主机名解析,并使此节点发现机制无法正常工作。
Podman 是执行此类主机文件修改的工具的一个已知示例。为避免此问题,应将其 host_containers_internal_ip 设置 设置为空字符串。
在无法调整容器级别设置的环境中,运行时可以 配置为忽略标准本地 hosts 文件,并且仅使用 DNS 或一组预配置的主机名到 IP 地址的映射。
AWS (EC2) 上的节点发现
AWS (EC2) 节点发现概述
可通过插件获得 AWS (EC2) 特定 的发现机制。
与任何 插件 一样,在使用它之前必须启用它。对于节点发现插件,这意味着它们必须在首次节点启动之前 启用 或 预先配置。
rabbitmq-plugins --offline enable rabbitmq_peer_discovery_aws
该插件为节点发现其同伴提供了两种方式:
- 使用 EC2 实例标签
- 使用 AWS 自动扩展组的成员资格
这两种方法都依赖于 AWS 特定的 API(端点)和功能,因此无法在其他 IaaS 环境中工作。一旦检索到集群成员实例列表,最终的节点名称将使用实例主机名或 IP 地址计算。
配置和凭据
在节点能够对 AWS 执行任何操作之前,它需要一组已配置的 AWS 账户凭据。这可以通过几种方式完成:
- 通过 配置文件
- 使用环境变量
AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY
EC2 实例元数据服务 也会被查询。
以下示例片段配置 RabbitMQ 使用 AWS 节点发现后端,并提供有关 AWS 区域以及一组凭据的信息。
cluster_formation.peer_discovery_backend = aws
# the backend can also be specified using its module name
# cluster_formation.peer_discovery_backend = rabbit_peer_discovery_aws
cluster_formation.aws.region = us-east-1
cluster_formation.aws.access_key_id = ANIDEXAMPLE
cluster_formation.aws.secret_key = WjalrxuTnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY
如果区域未配置,默认将使用 us-east-1。配置文件中的敏感值可以 选择性地加密。
如果将 IAM 角色分配给运行 RabbitMQ 节点的 EC2 实例,则必须使用策略 允许这些实例使用 EC2 实例元数据服务。当插件配置为使用自动扩展组成员时,必须有一个策略 授予描述自动扩展组成员(实例)的访问权限。下面是一个涵盖这两种情况的策略示例。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"autoscaling:DescribeAutoScalingInstances",
"ec2:DescribeInstances"
],
"Resource": [
"*"
]
}
]
}
使用自动扩展组的成员资格
当使用基于自动扩展的节点发现时,将列出当前节点的 EC2 实例自动扩展组的成员,并用于生成发现的同伴列表。
要使用自动扩展组的成员资格,请将 cluster_formation.aws.use_autoscaling_group 键设置为 true。
cluster_formation.peer_discovery_backend = aws
cluster_formation.aws.region = us-east-1
cluster_formation.aws.access_key_id = ANIDEXAMPLE
cluster_formation.aws.secret_key = WjalrxuTnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY
cluster_formation.aws.use_autoscaling_group = true
使用 EC2 实例标签
当使用基于标签的节点发现时,该插件将使用 EC2 API 列出 EC2 实例,并通过配置的实例标签进行过滤。结果实例集将用于生成发现的同伴列表。
标签使用 cluster_formation.aws.instance_tags 键进行配置。下面的示例使用了三个标签:region、service 和 environment。
cluster_formation.peer_discovery_backend = aws
cluster_formation.aws.region = us-east-1
cluster_formation.aws.access_key_id = ANIDEXAMPLE
cluster_formation.aws.secret_key = WjalrxuTnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY
cluster_formation.aws.instance_tags.region = us-east-1
cluster_formation.aws.instance_tags.service = rabbitmq
cluster_formation.aws.instance_tags.environment = staging
使用私有 EC2 实例 IP
默认情况下,节点发现将使用私有 DNS 主机名来计算节点名称。此选项最方便,并且 **强烈推荐**。
但是,可以通过将 cluster_formation.aws.use_private_ip 键设置为 true 来选择使用私有 IP。要使此设置生效,必须在节点部署时将 RABBITMQ_NODENAME 设置 为私有 IP 地址。
RABBITMQ_USE_LONGNAME 也必须设置为 true,否则 IP 地址将不被视为节点名称的有效部分。
cluster_formation.peer_discovery_backend = aws
cluster_formation.aws.region = us-east-1
cluster_formation.aws.access_key_id = ANIDEXAMPLE
cluster_formation.aws.secret_key = WjalrxuTnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY
cluster_formation.aws.use_autoscaling_group = true
cluster_formation.aws.use_private_ip = true
Kubernetes 上的节点发现
在大多数情况下,部署到 Kubernetes 时无需担心节点发现。
集群运算符(部署到 Kubernetes 的推荐方式)以及流行的 Helm 图表都会为您预先配置节点发现。
Kubernetes 节点发现概述
可通过 插件 获得基于 Kubernetes 的发现机制。
由于节点发现发生在节点启动早期,您应该将 rabbitmq_peer_discovery_k8s 添加到 enabled_plugins 文件 中。在 Kubernetes 部署的情况下,通常是 ConfigMap。
自 RabbitMQ 4.1 起,此插件仅允许具有最低序数索引的节点(几乎总是后缀为 -0 的 pod)形成新集群。此节点称为种子节点。
所有其他节点将加入种子节点,或者如果无法加入,将一直尝试加入它。
在最常见的情况下,这意味着:
- 后缀为
-0的 pod 将立即启动,有效形成一个新的单节点集群。 - 任何其他 pod 将加入后缀为
-0的 pod 并与其同步集群元数据。
配置
在大多数情况下,除了启用此插件之外,不需要任何配置。
如果您在 StatefulSet 中使用 不同的序数启动值,则必须配置此插件以使用它。
cluster_formation.k8s.ordinal_start = N
其中 N 与 StatefulSet 的 .spec.ordinals.start 值匹配。
如果插件由于任何原因无法工作(非常不寻常的 Kubernetes 配置或主机名解析问题),并且您必须强制 RabbitMQ 使用与自动选择不同的种子节点,您可以这样做:
cluster_formation.k8s.seed_node = rabbit@seed-node-hostname
如果配置了 cluster_formation.k8s.seed_node,该插件将仅使用此值作为种子节点。如果您这样做,请在 GitHub 上打开一个问题并解释为什么插件对您不起作用,以便我们可以改进它。
使用 Consul 进行节点发现
Consul 节点发现概述
与任何 插件 一样,在使用它之前必须启用它。对于节点发现插件,这意味着它们必须在首次节点启动之前 启用 或 预先配置。
rabbitmq-plugins --offline enable rabbitmq_peer_discovery_consul
该插件支持 Consul 0.8.0 及更高版本。
节点在启动时向 Consul 注册,并在离开时注销。在注册之前,节点将尝试在 Consul 中获取锁,以降低 初始集群形成期间的竞态条件 的可能性。当节点向 Consul 注册时,它将为自己设置一个定期的 健康检查(稍后将详细介绍)。
配置
要使用 Consul 进行节点发现,请将 cluster_formation.peer_discovery_backend 设置为 consul 或其模块名称 rabbit_peer_discovery_consul(注意:模块名称与插件名称略有不同)。
cluster_formation.peer_discovery_backend = consul
# the backend can also be specified using its module name
# cluster_formation.peer_discovery_backend = rabbit_peer_discovery_consul
# Consul host (hostname or IP address). Default value is localhost
cluster_formation.consul.host = consul.eng.example.local
Consul 端点
可以配置 Consul 端口和 URI 方案。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
# 8500 is used by default
cluster_formation.consul.port = 8500
# http is used by default
cluster_formation.consul.scheme = http
Consul ACL Token
要配置 Consul ACL token,请使用 cluster_formation.consul.acl_token。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
cluster_formation.consul.acl_token = acl-token-value
服务名称(在 Consul 中注册的名称)默认为“rabbitmq”,但可以覆盖。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
# rabbitmq is used by default
cluster_formation.consul.svc = rabbitmq
服务地址
在 Consul 中注册的服务主机名(地址)将被同伴获取,因此必须能在所有节点上解析。主机名可以由插件计算,也可以由用户指定。自动计算时,可以使用一些节点和操作系统属性。
- 主机名(由
gethostname(2)返回) - 节点名称(不带
rabbit@前缀) - NIC(网络控制器接口)的 IP 地址
当 cluster_formation.consul.svc_addr_auto 设置为 false 时,将按原样从 cluster_formation.consul.svc_addr 获取服务名称。当设置为 true 时,下面的其他选项将生效。
在以下示例中,报告给 Consul 的服务地址被硬编码为 hostname1.rmq.eng.example.local,而不是根据环境自动计算。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
cluster_formation.consul.svc = rabbitmq
# do not compute service address, it will be specified below
cluster_formation.consul.svc_addr_auto = false
# service address, will be communicated to other nodes
cluster_formation.consul.svc_addr = hostname1.rmq.eng.example.local
# use long RabbitMQ node names?
cluster_formation.consul.use_longname = true
在此示例中,服务地址是从节点名称解析的(rabbit@ 前缀将被删除)。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
cluster_formation.consul.svc = rabbitmq
# do compute service address
cluster_formation.consul.svc_addr_auto = true
# compute service address using node name
cluster_formation.consul.svc_addr_use_nodename = true
# use long RabbitMQ node names?
cluster_formation.consul.use_longname = true
cluster_formation.consul.svc_addr_use_nodename 是一个布尔字段,指示 Consul 节点发现后端使用 RabbitMQ 节点名称计算服务地址。
在下一个示例中,服务地址是使用操作系统报告的主机名计算的,而不是节点名称。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
cluster_formation.consul.svc = rabbitmq
# do compute service address
cluster_formation.consul.svc_addr_auto = true
# compute service address using host name and not node name
cluster_formation.consul.svc_addr_use_nodename = false
# use long RabbitMQ node names?
cluster_formation.consul.use_longname = true
在下面的示例中,服务地址是通过获取提供的 NIC en0 的 IP 地址来计算的。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
cluster_formation.consul.svc = rabbitmq
# do compute service address
cluster_formation.consul.svc_addr_auto = true
# compute service address using the IP address of a NIC, en0
cluster_formation.consul.svc_addr_nic = en0
cluster_formation.consul.svc_addr_use_nodename = false
# use long RabbitMQ node names?
cluster_formation.consul.use_longname = true
服务端口
在 Consul 中注册的服务端口可以被覆盖。这仅在 RabbitMQ 使用 非标准端口 进行客户端(技术上是 AMQP 0-9-1 和 AMQP 1.0)连接时才有必要,因为默认值为 5672。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
# 5672 is used by default
cluster_formation.consul.svc_port = 6674
服务标签和元数据
可以提供 Consul 服务标签。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
# Define tags for the RabbitMQ service: "qa" and "3.8"
cluster_formation.consul.svc_tags.1 = qa
cluster_formation.consul.svc_tags.2 = 3.8
可以配置 Consul 服务元数据,这是一个字符串键到字符串值的映射,并具有某些限制(请参阅 Consul 文档了解更多信息)。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
# Define metadata for the RabbitMQ service. Both keys and values have a
# maximum length limit enforced by Consul. This can be used to provide additional
# context about the service (RabbitMQ cluster) for operators or other tools.
cluster_formation.consul.svc_meta.owner = team-xyz
cluster_formation.consul.svc_meta.service = service-one
cluster_formation.consul.svc_meta.stats_url = https://service-one.eng.megacorp.local/stats/
服务健康检查
当节点向 Consul 注册时,它将为自己设置一个定期的 健康检查。在线节点将定期向 Consul 发送健康检查更新,以指示服务可用。此间隔可以配置。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
# health check interval (node TTL) in seconds
# default: 30
cluster_formation.consul.svc_ttl = 40
未能通过 健康检查 的节点将被 Consul 视为处于警告状态。这些节点可能在一段时间后被 Consul 自动注销(注意:此间隔值与上面的 TTL 不同)。该时间间隔不得小于 60 秒。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
# health check interval (node TTL) in seconds
cluster_formation.consul.svc_ttl = 30
# how soon should nodes that fail their health checks be unregistered by Consul?
# this value is in seconds and must not be lower than 60 (a Consul requirement)
cluster_formation.consul.deregister_after = 90
请参阅下面的 自动清理节点 部分。
默认情况下,处于警告状态的节点将从节点发现结果中排除。可以通过将 cluster_formation.consul.include_nodes_with_warnings 设置为 true 来选择包含它们。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
# health check interval (node TTL) in seconds
cluster_formation.consul.svc_ttl = 30
# include node in the warning state into discovery result set
cluster_formation.consul.include_nodes_with_warnings = true
选择退出注册
如果配置的后端支持注册,则节点在收到停止指令时会注销。
可以使用配置选项 cluster_formation.registration.enabled 完全选择退出注册。
cluster_formation.registration.enabled = false
按此方式配置后,节点必须手动注册或使用其他机制进行注册,例如通过容器编排器,如 Nomad。
节点名称后缀
如果节点名称是计算出来的并且使用了长节点名称,则可以将后缀附加到从 Consul 检索到的节点名称。格式为 .node.{domain_suffix}。这在具有 DNS 约定的环境中可能很有用,例如,当所有服务节点都组织在一个单独的子域中时。这是一个例子:
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
cluster_formation.consul.svc = rabbitmq
# do compute service address
cluster_formation.consul.svc_addr_auto = true
# compute service address using node name
cluster_formation.consul.svc_addr_use_nodename = true
# use long RabbitMQ node names?
cluster_formation.consul.use_longname = true
# append a suffix (node.rabbitmq.example.local) to node names retrieved from Consul
cluster_formation.consul.domain_suffix = example.local
使用此设置,节点名称将计算为 rabbit@192.168.100.1.node.example.local 而不是 rabbit@192.168.100.1。
分布式锁获取
当节点尝试在启动时获取锁并且锁已被占用时,它将等待锁在有限的时间内可用。默认值为 300 秒,但可以配置。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
# lock acquisition timeout in seconds
# default: 300
# cluster_formation.consul.lock_wait_time is an alias
cluster_formation.consul.lock_timeout = 60
默认锁键前缀为 rabbitmq。它也可以被覆盖。
cluster_formation.peer_discovery_backend = consul
cluster_formation.consul.host = consul.eng.example.local
cluster_formation.consul.lock_timeout = 60
# should the Consul key used for locking be prefixed with something
# other than "rabbitmq"?
cluster_formation.consul.lock_prefix = environments-qa
使用 Etcd 进行节点发现
Etcd 节点发现概述
从 RabbitMQ 3.8.4 起,该插件使用 v3 API、基于 gRPC 的 etcd 客户端,并且 **需要 etcd 3.4 或更高版本**。
与任何 插件 一样,在使用它之前必须启用它。对于节点发现插件,这意味着它们必须在首次节点启动之前 启用 或 预先配置。
rabbitmq-plugins --offline enable rabbitmq_peer_discovery_etcd
节点在启动时通过在命名目录中创建键来向 etcd 注册。这些键具有较短的(例如,一分钟)过期时间。节点在干净停止时会被删除。
在注册之前,节点将尝试在 etcd 中获取锁,以降低 初始集群形成期间的竞态条件 的可能性。
每个节点的键都有一个关联的 租约,并带有可配置的 TTL。节点会保持其键的租约有效。如果节点失去连接并且无法更新其租约,则其键将在 TTL 过期后由 etcd 清理。这些节点将不会被新加入的节点发现。如果配置,这些节点可以被强制从集群中移除。
配置
Etcd 端点和身份验证
要使用 etcd 进行节点发现,请将 cluster_formation.peer_discovery_backend 设置为 etcd 或其模块名称 rabbit_peer_discovery_etcd(注意:模块名称与插件名称略有不同)。
该插件需要一个已配置的 etcd 端点供插件连接。
cluster_formation.peer_discovery_backend = etcd
# the backend can also be specified using its module name
# cluster_formation.peer_discovery_backend = rabbit_peer_discovery_etcd
# etcd endpoints. This property is required or peer discovery won't be performed.
cluster_formation.etcd.endpoints.1 = one.etcd.eng.example.local:2379
可以配置多个 etcd 端点。插件可以成功连接的第一个随机选择的端点将被使用。
cluster_formation.peer_discovery_backend = etcd
cluster_formation.etcd.endpoints.1 = one.etcd.eng.example.local:2379
cluster_formation.etcd.endpoints.2 = two.etcd.eng.example.local:2479
cluster_formation.etcd.endpoints.3 = three.etcd.eng.example.local:2579
如果 etcd 启用了身份验证,则可以配置插件使用一对凭据。
cluster_formation.peer_discovery_backend = etcd
cluster_formation.etcd.endpoints.1 = one.etcd.eng.example.local:2379
cluster_formation.etcd.endpoints.2 = two.etcd.eng.example.local:2479
cluster_formation.etcd.endpoints.3 = three.etcd.eng.example.local:2579
cluster_formation.etcd.username = rabbitmq
cluster_formation.etcd.password = s3kR37
可以使用 advanced.config 文件 加密配置文件中列出的密码值。在这种情况下,所有插件设置都必须移到高级配置文件中。
%% advanced.config file
[
{rabbit,
[{cluster_formation,
[{peer_discovery_etcd, [
{endpoints, [
"one.etcd.eng.example.local:2379",
"two.etcd.eng.example.local:2479",
"three.etcd.eng.example.local:2579"
]},
{etcd_prefix, "rabbitmq"},
{cluster_name, "default"},
{etcd_username, "etcd user"},
{etcd_password, {encrypted, <<"cPAymwqmMnbPXXRVqVzpxJdrS8mHEKuo2V+3vt1u/fymexD9oztQ2G/oJ4PAaSb2c5N/hRJ2aqP/X0VAfx8xOQ==">>}
}]
}]
}]
},
{config_entry_decoder, [
{passphrase, <<"decryption key passphrase">>}
]}
].
键命名
节点发现机制使用的目录和键遵循命名方案。由于 etcd v3 API 键空间是扁平的,因此使用了一个硬编码的前缀。这允许插件使用一个众所周知的字符串前缀来可预测地执行键范围查询。
# for node presence keys
/rabbitmq/discovery/{prefix}/clusters/{cluster name}/nodes/{node name}
# for registration lock keys
/rabbitmq/locks/{prefix}/clusters/{cluster name}/registration
下面是一个示例,其中节点 rabbit@hostname1 使用默认用户提供的键前缀和集群名称将使用的键:
/rabbitmq/discovery/rabbitmq/clusters/default/nodes/rabbit@hostname1
默认键前缀就是“rabbitmq”。它很少需要覆盖,但它是支持的。
cluster_formation.peer_discovery_backend = etcd
cluster_formation.etcd.endpoints.1 = one.etcd.eng.example.local:2379
cluster_formation.etcd.endpoints.2 = two.etcd.eng.example.local:2479
cluster_formation.etcd.endpoints.3 = three.etcd.eng.example.local:2579
# rabbitmq is used by default
cluster_formation.etcd.key_prefix = rabbitmq_discovery
如果多个 RabbitMQ 集群共享一个 etcd 安装,则每个集群都必须使用唯一的名称。
cluster_formation.peer_discovery_backend = etcd
cluster_formation.etcd.endpoints.1 = one.etcd.eng.example.local:2379
cluster_formation.etcd.endpoints.2 = two.etcd.eng.example.local:2479
cluster_formation.etcd.endpoints.3 = three.etcd.eng.example.local:2579
# default name: "default"
cluster_formation.etcd.cluster_name = staging
键租约和 TTL
用于节点注册的键将具有关联的租约和 TTL。在线节点将定期保持租约有效(刷新)。TTL 值可以配置。
cluster_formation.peer_discovery_backend = etcd
cluster_formation.etcd.endpoints.1 = one.etcd.eng.example.local:2379
cluster_formation.etcd.endpoints.2 = two.etcd.eng.example.local:2479
cluster_formation.etcd.endpoints.3 = three.etcd.eng.example.local:2579
# node TTL in seconds
# default: 30
cluster_formation.etcd.node_ttl = 40
当节点运行时并且插件保持启用状态时,键租约会定期更新。
可以将未能刷新其键的节点强制从集群中移除。这将在本指南后面介绍。
锁
当节点尝试在启动时获取锁并且锁已被占用时,它将等待锁在有限的时间内可用。默认值为 300 秒,但可以配置。
cluster_formation.peer_discovery_backend = etcd
cluster_formation.etcd.endpoints.1 = one.etcd.eng.example.local:2379
cluster_formation.etcd.endpoints.2 = two.etcd.eng.example.local:2479
# lock acquisition timeout in seconds
# default: 300
# cluster_formation.consul.lock_wait_time is an alias
cluster_formation.etcd.lock_timeout = 60
检查键
要列出 etcd 基于节点发现机制使用的所有键,请使用 etcdctl get,如下所示:
etcdctl get --prefix=true "/rabbitmq"
TLS
可以配置该插件以在连接到 etcd 时 使用 TLS。如果配置了下面列出的任何 TLS 选项,则会启用 TLS,否则连接将使用“明文 TCP”而无需 TLS。
该插件充当 TLS 客户端。必须提供 受信任的 CA 证书 文件以及客户端证书和私钥对。
cluster_formation.peer_discovery_backend = etcd
cluster_formation.etcd.endpoints.1 = one.etcd.eng.example.local:2379
cluster_formation.etcd.endpoints.2 = two.etcd.eng.example.local:2479
# trusted CA certificate file path
cluster_formation.etcd.ssl_options.cacertfile = /path/to/ca_certificate.pem
# client certificate (public key) file path
cluster_formation.etcd.ssl_options.certfile = /path/to/client_certificate.pem
# client private key file path
cluster_formation.etcd.ssl_options.keyfile = /path/to/client_key.pem
# use TLSv1.2 for connections
cluster_formation.etcd.ssl_options.versions.1 = tlsv1.2
# enables peer verification (the plugin will verify the certificate chain of the server)
cluster_formation.etcd.ssl_options.verify = verify_peer
cluster_formation.etcd.ssl_options.fail_if_no_peer_cert = true
还支持更多 TLS 选项,例如密码套件和客户端会话重新协商选项。
cluster_formation.peer_discovery_backend = etcd
cluster_formation.etcd.endpoints.1 = one.etcd.eng.example.local:2379
cluster_formation.etcd.endpoints.2 = two.etcd.eng.example.local:2479
# trusted CA certificate file path
cluster_formation.etcd.ssl_options.cacertfile = /path/to/ca_certificate.pem
# client certificate (public key) file path
cluster_formation.etcd.ssl_options.certfile = /path/to/client_certificate.pem
# client private key file path
cluster_formation.etcd.ssl_options.keyfile = /path/to/client_key.pem
# use TLSv1.2 for connections
cluster_formation.etcd.ssl_options.versions.1 = tlsv1.2
# enables peer verification (the plugin will verify the certificate chain of the server)
cluster_formation.etcd.ssl_options.verify = verify_peer
cluster_formation.etcd.ssl_options.fail_if_no_peer_cert = true
# use secure session renegotiation
cluster_formation.etcd.ssl_options.secure_renegotiate = true
# Explicitly list enabled cipher suites. This can break connectivity
# and is not necessary most of the time.
cluster_formation.etcd.ssl_options.ciphers.1 = ECDHE-ECDSA-AES256-GCM-SHA384
cluster_formation.etcd.ssl_options.ciphers.2 = ECDHE-RSA-AES256-GCM-SHA384
cluster_formation.etcd.ssl_options.ciphers.3 = ECDH-ECDSA-AES256-GCM-SHA384
cluster_formation.etcd.ssl_options.ciphers.4 = ECDH-RSA-AES256-GCM-SHA384
cluster_formation.etcd.ssl_options.ciphers.5 = DHE-RSA-AES256-GCM-SHA384
cluster_formation.etcd.ssl_options.ciphers.6 = DHE-DSS-AES256-GCM-SHA384
cluster_formation.etcd.ssl_options.ciphers.7 = ECDHE-ECDSA-AES128-GCM-SHA256
cluster_formation.etcd.ssl_options.ciphers.8 = ECDHE-RSA-AES128-GCM-SHA256
cluster_formation.etcd.ssl_options.ciphers.9 = ECDH-ECDSA-AES128-GCM-SHA256
cluster_formation.etcd.ssl_options.ciphers.10 = ECDH-RSA-AES128-GCM-SHA256
cluster_formation.etcd.ssl_options.ciphers.11 = DHE-RSA-AES128-GCM-SHA256
cluster_formation.etcd.ssl_options.ciphers.12 = DHE-DSS-AES128-GCM-SHA256
初始集群形成期间的竞态条件
为了成功形成集群,最初应该只有一个节点形成集群,也就是说,它作为独立节点启动并初始化其数据库。如果不是这种情况,则会形成多个集群而不是一个,这违反了操作员的期望。
考虑一种一次性配置整个集群的部署,并且所有节点都并行启动。在这种情况下,在启动节点之间会发生自然的竞态条件。为了防止多个节点形成单独的集群,节点发现后端在形成集群(种子)或加入同伴时会尝试获取锁。使用的锁因后端而异:
- 经典的配置文件、K8s 和 AWS 后端使用运行时提供的内置 锁定库。
- Consul 节点发现后端在 Consul 中设置锁。
- etcd 节点发现后端在 etcd 中设置锁。
节点健康检查和强制移除
使用节点发现形成的集群中的节点可能会失败、变得不可用或被永久移除(退役)。一些操作员可能希望在一段时间后自动将这些节点从集群中移除。这种自动强制移除也可能产生不可预见的副作用,因此 RabbitMQ 不强制执行此行为。 **应非常谨慎使用**,并且仅当副作用被完全理解并考虑在内时才使用。
例如,考虑一个使用 AWS 后端并配置为使用自动扩展组成员资格的集群。如果该组中的一个 EC2 实例失败,然后被重新创建为一个新节点,那么它的原始“化身”将被视为同一集群中一个独立的、现在永久不可用的节点。
对于提供动态节点管理的节点发现后端(而不是,例如,配置文件中的固定节点列表),可以记录这些未知节点或将其强制从集群中移除。
它们是:
强制节点移除可能很危险,应仔细考虑。例如,一个暂时不可用但将重新加入(或使用其先前化身的持久存储重新附加而重新创建)的节点可能会被自动清理永久踢出集群,从而无法重新加入。
在启用下面涵盖的配置键之前,请确保已启用兼容的节点发现插件。如果不是这种情况,节点将报告设置未知并且无法启动。
要记录未知节点的警告,应将 cluster_formation.node_cleanup.only_log_warning 设置为 true。
# Don't remove cluster members unknown to the peer discovery backend but log
# warnings.
#
# This setting can only be used if a compatible peer discovery plugin is enabled.
cluster_formation.node_cleanup.only_log_warning = true
这是默认行为。
要强制删除未知节点,应将 cluster_formation.node_cleanup.only_log_warning 设置为 false。
# Forcefully remove cluster members unknown to the peer discovery backend. Once removed,
# the nodes won't be able to rejoin. Use this mode with great care!
#
# This setting can only be used if a compatible peer discovery plugin is enabled.
cluster_formation.node_cleanup.only_log_warning = false
请注意,此选项应谨慎使用,尤其是在 AWS 以外的发现后端上。
清理检查会定期执行。默认间隔为 60 秒,可以覆盖。
# perform the check every 90 seconds
cluster_formation.node_cleanup.interval = 90
一些后端(Consul、etcd)支持节点健康检查或 TTL。这些检查不应与 监控健康检查 混淆。它们允许节点发现服务(如 etcd 或 Consul)跟踪哪些节点仍然存在(最近已签到)。
通过服务发现健康检查,节点会为其键设置 TTL,并/或定期通知其各自的发现服务它们仍然存在。如果在一段时间后没有收到来自节点的通知,该节点的键最终将过期(在 Consul 中,这些节点将被视为处于警告状态)。
使用 etcd,这些节点将不再出现在发现结果中。使用 Consul,它们可以被移除(取消注册)或报告其警告状态。请参阅这些后端的文档以了解更多信息。
当失败/停用的节点将被新节点替换时(包括持久存储未从其先前化身重新附加的情况),自动清理缺席节点最有意义。
当自动节点清理被禁用(切换到警告模式)时,操作员必须使用 rabbitmqctl forget_cluster_node 显式移除缺席的集群节点。
自动移除的负面副作用
自动节点移除存在一些操作员应注意的负面副作用。例如,一个暂时无法访问的节点,因为它与网络的其他部分失去了连接或其 VM 被暂时挂起,将被移除然后重新出现。这样的节点将无法 重新加入其集群 并会记录类似的消息。
Node 'rabbit@node1.local' thinks it's clustered with node 'rabbit@node2.local', but 'rabbit@node2.local' disagrees
此外,这些节点可能会开始 监控健康检查 失败,因为它们处于永久“分区”状态。即使这样的节点可能已被新节点替换并且集群按预期运行,但这些自动移除和替换的节点可能会产生监控误报。
副作用列表不限于这两种情况,但它们都有相同的根本原因:一个自动移除的节点可能会在不知道自己已被踢出集群的情况下重新出现。监控系统和操作员也不会立即意识到这一事件。
节点发现失败和重试
在最新版本中,如果节点发现尝试失败,它将重试直到达到一定次数,每次重试之间都有延迟。这类似于节点 重启后上线时 执行的同伴同步重试。
例如,使用 Kubernetes 节点发现机制,这意味着 Kubernetes API 列出 pod 的请求在失败时将被重试。使用 AWS 机制,EC2 API 请求将被重试,依此类推。
这些重试绝不能处理所有可能的失败场景,但它们在实践中提高了节点发现以及集群和节点部署的弹性。但是,如果集群节点 无法相互认证,重试只会导致集群形成失败。
未能执行节点发现的节点将 记录 其剩余的恢复尝试。
2020-06-27 06:35:36.426 [error] <0.277.0> Trying to join discovered peers failed. Will retry after a delay of 500 ms, 4 retries left...
2020-06-27 06:35:36.928 [warning] <0.277.0> Could not auto-cluster with node rabbit@hostname2: {badrpc,nodedown}
2020-06-27 06:35:36.930 [warning] <0.277.0> Could not auto-cluster with node rabbit@hostname3: {badrpc,nodedown}
2020-06-27 06:35:36.930 [error] <0.277.0> Trying to join discovered peers failed. Will retry after a delay of 500 ms, 3 retries left...
2020-06-27 06:35:37.432 [warning] <0.277.0> Could not auto-cluster with node rabbit@hostname2: {badrpc,nodedown}
2020-06-27 06:35:37.434 [warning] <0.277.0> Could not auto-cluster with node rabbit@hostname3: {badrpc,nodedown}
如果节点未能执行节点发现并用尽所有重试次数,强烈建议 启用调试日志 以便 进行故障排除。
重试次数和延迟可以配置。
# These are the default values
# Retry peer discovery operations up to ten times
cluster_formation.discovery_retry_limit = 10
# 500 milliseconds
cluster_formation.discovery_retry_interval = 500
默认值覆盖了服务、API 端点或参与节点发现的节点五秒钟的不可用时间。这些值足以应对偶发性故障。在依赖服务(DNS、etcd、Consul 等)可能与 RabbitMQ 集群部署并发供应,因此可能在一段时间后才能使用的环境中,需要增加这些值。
HTTP 代理设置
使用 HTTP 与其依赖项(例如 AWS、Consul 和 etcd)交互的节点发现机制可以通过 HTTP 代理来代理其请求。
HTTP 和 HTTPS 有单独的代理设置。
# example HTTP and HTTPS proxy servers, values in your environment
# will vary
cluster_formation.proxy.http_proxy = 192.168.0.98
cluster_formation.proxy.https_proxy = 192.168.0.98
某些主机可以被排除在代理之外,例如链路本地 AWS 实例元数据 IP 地址。
# example HTTP and HTTPS proxy servers, values in your environment
# will vary
cluster_formation.proxy.http_proxy = 192.168.0.98
cluster_formation.proxy.https_proxy = 192.168.0.98
# requests to these hosts won't go via proxy
cluster_formation.proxy.proxy_exclusions.1 = 169.254.169.254
cluster_formation.proxy.proxy_exclusions.2 = excluded.example.local
故障排除
节点发现子系统和各个机制实现会在 info 日志级别记录重要的发现过程步骤。更详细的日志记录可在 debug 级别获得。依赖可通过 HTTP 访问的外部服务的机制将在 debug 级别记录所有出站 HTTP 请求和响应代码。有关日志记录配置的更多信息,请参阅 日志记录指南。
如果日志不包含任何证明节点发现进度的条目,例如,机制检索到的节点列表或集群尝试,这可能意味着节点已经具有初始化的数据目录或已经是集群的成员。在这些情况下,将不会执行节点发现。
节点发现依赖于节点间的网络连接以及通过共享密钥的成功认证。验证节点之间是否可以通信以及是否使用预期的 Erlang cookie 值(该值在所有集群节点之间也相同)。有关更多信息,请参阅主要的 集群指南。
网络连接故障排除的方法以及常用工具包含在 网络连接故障排除 指南中。