第三部分。使用 Consul 实现 Ceph 对象网关 (RGW) 的全球负载均衡


第3部分:基于Consul的Ceph对象网关(RGW)的全局负载均衡 ¶
注意:本文档描述的功能预计将在即将发布的 Tentacle 版本中提供。
简介 ¶
在系列文章的第一和第二部分中,我们使用cephadm ingress 终结器、Consul 服务发现和 CoreDNS 重写构建了针对 Ceph 对象网关 (RGW) 的本地、感知数据中心健康的负载均衡。
在第三和第四部分中,我们将 Ceph 对象多站点(马德里和巴黎数据中心之间的有活动/活动数据复制)与 Consul 全局负载均衡控制平面相结合,确保单个 S3 端点始终路由到最近的健康 ingress,并在需要时跨站点故障转移,而无需任何客户端 URL 或 SDK 更改。每个站点保持独立(自己的控制平面和 Ceph 集群),同时通过 WAN 协同工作,以便客户端请求始终到达健康的站点。
在本篇文章中,我们将首先清晰地阐述数据复制、每节点 ingress 以及服务感知控制平面如何协同工作以实现 GLB。然后我们将启动 Consul WAN 联合(每个站点一个数据中心),并定义一个共享的 S3 准备查询,该查询编码了我们的策略:优先选择本地,只有在必要时才回退到对等站点。我们将通过 Consul API 和 Consul DNS 在 8600 端口(尚未进行面向客户端的 DNS 更改)验证端到端行为,并以一些运营强化说明结束。
高级架构概述 ¶
此解决方案结合了三个层级,以便客户端可以从任何站点可靠地访问 Ceph 对象网关 (RGW)。首先,Ceph 对象多站点在两个位置保持数据复制和可用。我们运行一个名为 Europe 的区域组,其中包含两个区域:马德里和巴黎。对象在两个方向上复制(活动/活动数据)。元数据复制由区域组主节点协调(活动/被动元数据,例如存储桶和用户)。这意味着任何站点都可以提供对象读取和写入;元数据更改在主节点进行,然后传播到对等节点。
其次,cephadm ingress 终结器在每个节点上的 RGW 前面放置一个 HAProxy。到达站点的请求由该站点的 HAProxy 和 RGW 本地处理,从而提高性能并消除站点内的单点故障。
第三,服务感知 DNS 将客户端引导至健康的入口点。每个站点运行 Consul 和 CoreDNS。Consul 持续检查每个 HAProxy 的健康状况,并维护一个目录,其中包含该站点中可用的 HAProxy。两个 Consul 数据中心通过 WAN 连接,因此在出现问题时,站点可以从其对等站点请求健康的端点。CoreDNS 在 TCP 端口 53 上为您的域(例如,cephlab.com)提供标准的 DNS 接口。它将公共名称 s3.cephlab.com 重写为内部 Consul 查找,将问题转发到本地 Consul DNS,然后将答案重写回去。应用程序继续使用稳定的 URL,而系统会安静地选择健康的网关。

总的来说,工作流程很简单
- 客户端使用最近的 CoreDNS(每个站点运行三个实例以实现高可用性)解析
s3.cephlab.com。 - CoreDNS 将请求重写为 Consul 准备查询,并将其转发到本地 Consul 在 TCP 端口
8600上。 - Consul 只返回本地站点的健康 HAProxy IP 地址;如果没有健康的 HAProxy,它将返回对等站点中的健康 IP 地址。
- 客户端连接到这些 IP 中的一个,端口为
8080,本地 HAProxy 将请求转发到其本地 RGW。 - 多站点确保对象数据存在于两个站点中,并且元数据通过区域组主节点保持一致。
Ceph 复制一致性与复制语义 ¶
如前所述,此实验室使用活动/活动模型进行对象数据:客户端可以写入任何站点。站点之间的复制是异步的(最终一致性),因此您应该期望短暂的传播延迟并计划由此产生的权衡。以下部分概述了使用 S3 API 的客户端/应用程序的关键注意事项。
- 写入冲突:对于未版本化的存储桶,来自不同站点的相同密钥的并发写入以“最后写入者胜出”(最新的更新在复制完成后覆盖较早的更新)来解决。如果您启用存储桶版本控制,则会保留版本,但您看到的“当前”版本仍然是提交的最新更新。
- 写入后的跨站点读取:由于复制是异步的,因此站点 A 中的客户端可能无法立即看到在站点 B 中执行的写入。如果您需要更强的“读取自己的写入”行为跨站点,请考虑以下一种或多种模式
- 优先选择每个密钥的单个写入者(或将数据集的所有写入路由到单个站点),让其他站点读取。
- 启用存储桶版本控制,并在正确性至关重要时按版本 ID 读取。
- 使用条件请求(例如,带有已知 ETag 的 If-Match)和带有退避/抖动的应用程序重试逻辑,直到看到预期的对象/版本为止。
- 保持客户端亲和性(将客户端固定到其最近的 DC),以最大限度地减少写入后的跨站点读取差距。
如果您的应用程序、工作负载或用例不符合活动/活动模型,则可以将 Ceph 对象多站点配置为活动/被动模式。在这种布局中,首选站点默认情况下提供所有客户端请求,而辅助站点保持待机状态,仅在主站点不可用时才接受客户端请求。
多 DC Consul 控制平面(WAN 联合) ¶
当您在两个站点上运行 Consul 时,您会为每个站点创建一个 Consul 数据中心。每个数据中心都有自己的一组服务器,这些服务器会选举出一个领导者并在本地 Raft 数据库中存储其目录。在站点内部,代理通过局域网进行通信。站点之间,指定的服务器还会打开 WAN 链路,以便两个数据中心可以看到彼此并进行协作。关键思想是分离与受控共享:日常查找保持本地以提高速度和隔离性,但系统可以在本地站点无法提供流量时故意跨 WAN 访问。
在设置 多数据中心 Consul 集群时,操作员必须确保每个数据中心的所有 Consul 服务器都可以通过其 WAN 广告网络地址直接从彼此访问,这可能很麻烦且从安全角度来看存在问题。希望简化其 WAN 部署并最大限度地减少暴露的安全面的操作员可以选择使用 网格网关 将这些数据中心连接在一起,以实现这一目标。

在此模型中,目录和健康状态在每个站点上都是独立的。您不会将站点合并到一个集群中。相反,您运行两个相互信任的集群通过 WAN。这使故障域更小,并清楚地了解哪个站点是健康的。
准备查询:执行模型和 GLB 策略 ¶
一个 准备查询 是一个 Consul 对象,它描述了如何响应服务的查找。它按名称选择服务(例如,ingress-rgw-s3),过滤到健康的实例,并应用一个可以优先选择本地端点并在必要时回退到其他站点的策略。准备查询存储在 Consul 的 Raft 状态中,并通过 DNS 公开。
每个准备查询都有一个 DNS 记录,格式为 <name>.query.consul.,并在您指定的站点中执行查询,通常是本地站点。您还可以通过使用 <name>.query.<datacenter>.consul. 强制在特定站点中执行。这种间接性使我们能够保持主机名的稳定,同时控制做出决策的位置。
在评估发生时,查询在接收问题的 Consul DNS 服务器所在的站点中执行。该站点仅返回已注册并健康检查通过的实例。如果没有,该查询可以咨询其故障转移数据中心列表,并从列表中的适当站点/DC 返回健康的端点。
CoreDNS 在 GLB 架构中 ¶
CoreDNS 位于 Consul 前面,并提供 DNS 接口在端口 53 上。我们每个数据中心运行三个 CoreDNS 实例以实现高可用性;每个 DC 都有自己的一组名称服务器 (NS) 地址,并且两者都作为内部存根区域(例如,委托的子域,如 s3.cephlab.com)的 NS 发布,以便解析器即使一个站点离线也可以到达任何站点。每个 CoreDNS 为您的子域提供一个小的区域文件,并将特殊的 .consul 域转发到本地 Consul DNS 在 TCP 端口 8600 上。当客户端查找全局名称 s3.cephlab.com 时,CoreDNS 会将其重写为内部准备查询形式(s3.query.consul. 以本地执行),将问题转发到 Consul,并将答案重写回去。因此,应用程序和 SDK 看到一个稳定的 URL。

我们还公开了按站点固定的名称,例如 s3.madrid.cephlab.com 和 s3.paris.cephlab.com,这迫使客户端请求到一个站点或另一个站点,同时仍然保留故障转移(如果该站点不健康)。这种安排为您提供了跨站点的弹性 DNS、默认的本地性、确定性路由(在需要时)、简单的延迟/合规性测试以及由 Consul 健康检查驱动的无缝跨站点故障转移。

动手实践。Consul 部署和设置 ¶
预配置资产 ¶
- 两个站点(数据中心):马德里和巴黎。
- 马德里服务器:
ceph-node-00 (192.168.122.12)、ceph-node-01 (192.168.122.179)、ceph-node-02 (192.168.122.94)。 - 巴黎服务器:
ceph-node-04 (192.168.122.138)、ceph-node-05 (192.168.122.175)、ceph-node-06 (192.168.122.214)。 - cephadm ingress 已部署,HAProxy 集中器位于每个 RGW 主机上;每个主机监视本地 RGW 在 TCP 端口
1967上,并在8080上前端 S3。请查看我们的博客系列 第一部分,了解有关设置此配置的详细步骤。 - Ceph 对象多站点复制已在站点之间配置。您可以查看我们的 Ceph 多站点异步复制博客系列,以获取此配置的步骤。
- Ceph 对象多站点区域组配置包括以下子域作为主机名部分中的逗号分隔列表:
s3.cephlab.com,s3.madrid.cephlab.com, s3.paris.cephlab.com。
Consul:服务器代理 ¶
在配置查询和 DNS 之前,我们将 Consul 服务器代理作为 cephadm 管理的容器在每个站点的三个服务器节点上运行。在 cephadm 下运行可以提供与集群其余部分一致的生命周期管理(放置、健康、重启),并且关键的是,在容器重启和节点重新启动期间保持状态持久化。
每个容器绑定挂载
/etc/consul.d用于配置文件:consul.hcl/opt/consul用于 Consul 的 Raft 数据目录,映射到主机的/var/lib/consul。这保留了目录、领导者选举状态和准备查询,以便它们在容器重启和节点重新启动后仍然存在。
Consul 配置和设置 ¶
为马德里 DC 创建 consul.hcl 配置文件。
将其放置在每个马德里节点上的 /etc/consul.d/consul.hcl,自定义每个主机的 bind_addr 和检查 URL。此文件是每个节点的,值不同;不要在不编辑的情况下将相同的文件 `scp` 到所有节点。通过 Ansible 或类似工具进行模板化处理效果很好。
ceph-node-00# mkdir -p /etc/consul.d /var/lib/consul
ceph-node-00# cat >/etc/consul.d/consul.hcl <<'EOF'
datacenter = "madrid"
data_dir = "/opt/consul"
bind_addr = "192.168.122.12"
client_addr = "0.0.0.0"
retry_join = [
"192.168.122.12",
"192.168.122.179",
"192.168.122.94"
]
retry_join_wan = [
"192.168.122.138",
"192.168.122.175",
"192.168.122.214"
]
server = true
bootstrap_expect = 3
enable_local_script_checks = true
services = [
{
name = "ingress-rgw-s3"
port = 8080
check = {
id = "http-check"
name = "HAProxy Check"
http = "http://192.168.122.12:1967/health"
interval = "10s"
timeout = "2s"
}
tags = ["region:eu", "site:mad"]
}
]
EOF
为巴黎 DC 创建 consul.hcl 配置文件(根据每个节点进行调整)
ceph-node-04# mkdir -p /etc/consul.d /var/lib/consul
ceph-node-04# cat >/etc/consul.d/consul.hcl <<'EOF'
datacenter = "paris"
data_dir = "/opt/consul"
bind_addr = "192.168.122.138"
client_addr = "0.0.0.0"
retry_join = [
"192.168.122.138",
"192.168.122.175",
"192.168.122.214"
]
retry_join_wan = [
"192.168.122.12",
"192.168.122.179",
"192.168.122.94"
]
server = true
bootstrap_expect = 3
services = [
{
name = "ingress-rgw-s3"
port = 8080
check = {
id = "http-check"
name = "HAProxy Check"
http = "http://192.168.122.138:1967/health"
interval = "10s"
timeout = "2s"
}
tags = ["region:eu", "site:paris"]
}
]
EOF
以下是马德里 Consul 服务的 cephadm 服务规范。我们创建 cephadm 规范文件,然后使用 ceph orch apply 应用配置。
ceph-node-00# cat >consul-madrid.yml <<'EOF'
service_type: container
service_id: consul
placement:
hosts:
- ceph-node-00
- ceph-node-01
- ceph-node-02
spec:
image: docker.io/hashicorp/consul:latest
entrypoint: '["consul", "agent", "-config-dir=/consul/config"]'
args:
- "--net=host"
ports:
- 8500
- 8600
- 8300
- 8301
- 8302
bind_mounts:
- ['type=bind','source=/etc/consul.d','destination=/consul/config', 'ro=false']
- ['type=bind','source=/var/lib/consul','destination=/opt/consul','ro=false']
EOF
ceph-node-00# ceph orch apply -i consul-madrid.yml
ceph-node-00# ceph orch ps --daemon_type container | grep consul
container.consul.ceph-node-00 ceph-node-00 *:8500,8600,8300,8301,8302 running (2m) 2m ago 28.1M - <unknown> ee6d75ac9539 cc2c232e59a3
container.consul.ceph-node-01 ceph-node-01 *:8500,8600,8300,8301,8302 running (2m) 2m ago 23.0M - <unknown> ee6d75ac9539 5127481b0f0a
container.consul.ceph-node-02 ceph-node-02 *:8500,8600,8300,8301,8302 running (2m) 2m ago 23.4M - <unknown> ee6d75ac9539 b39d3f904579
ceph-node-00# podman exec -it $(podman ps | awk '/consul/ {print $1; exit}') consul members
Node Address Status Type Build Protocol DC Partition Segment
ceph-node-00 192.168.122.12:8301 alive server 1.21.4 2 madrid default <all>
ceph-node-01 192.168.122.179:8301 alive server 1.21.4 2 madrid default <all>
ceph-node-02 192.168.122.94:8301 alive server 1.21.4 2 madrid default <all>
以下是巴黎 Consul 服务的 cephadm 服务规范。我们在巴黎 Ceph 集群中遵循相同的步骤。
ceph-node-04# cat >consul-paris.yml <<'EOF'
service_type: container
service_id: consul
placement:
hosts:
- ceph-node-04
- ceph-node-05
- ceph-node-06
spec:
image: docker.io/hashicorp/consul:latest
entrypoint: '["consul", "agent", "-config-dir=/consul/config"]'
args:
- "--net=host"
ports:
- 8500
- 8600
- 8300
- 8301
- 8302
bind_mounts:
- ['type=bind','source=/etc/consul.d','destination=/consul/config', 'ro=false']
- ['type=bind','source=/var/lib/consul','destination=/opt/consul','ro=false']
EOF
ceph-node-04# ceph orch apply -i consul-paris.yml
ceph-node-04# ceph orch ps --daemon_type container | grep consul
container.consul.ceph-node-04 ceph-node-04 *:8500,8600,8300,8301,8302 running (2m) 2m ago 22.9M - <unknown> ee6d75ac9539 a1b2c3d4e5f6
container.consul.ceph-node-05 ceph-node-05 *:8500,8600,8300,8301,8302 running (2m) 2m ago 23.1M - <unknown> ee6d75ac9539 b2c3d4e5f6a1
container.consul.ceph-node-06 ceph-node-06 *:8500,8600,8300,8301,8302 running (2m) 2m ago 23.0M - <unknown> ee6d75ac9539 c3d4e5f6a1b2
ceph-node-04# podman exec -it $(podman ps | awk '/consul/ {print $1; exit}') consul members
Node Address Status Type Build Protocol DC Partition Segment
ceph-node-04 192.168.122.138:8301 alive server 1.21.4 2 paris default <all>
ceph-node-05 192.168.122.175:8301 alive server 1.21.4 2 paris default <all>
ceph-node-06 192.168.122.214:8301 alive server 1.21.4 2 paris default <all>
验证 Consul WAN 联合是否设置并正常工作
ceph-node-00# podman exec -it $(podman ps | awk '/consul/ {print $1; exit}') consul members -wan
Node Address Status Type Build Protocol DC Partition Segment
ceph-node-00.madrid 192.168.122.12:8302 alive server 1.21.4 2 madrid default <all>
ceph-node-01.madrid 192.168.122.179:8302 alive server 1.21.4 2 madrid default <all>
ceph-node-02.madrid 192.168.122.94:8302 alive server 1.21.4 2 madrid default <all>
ceph-node-04.paris 192.168.122.138:8302 alive server 1.21.4 2 paris default <all>
ceph-node-05.paris 192.168.122.175:8302 alive server 1.21.4 2 paris default <all>
ceph-node-06.paris 192.168.122.214:8302 alive server 1.21.4 2 paris default <all>
Consul 准备好的 GLB 查询 ¶
在两个数据中心中创建名为 s3 的 准备好的查询。此查询是 GLB 的“大脑”:它要求 Consul 提供 ingress-rgw-s3 服务的端点,优先选择本地运行状况良好的实例,并且仅在没有可用/运行状况良好的实例时,才会回退到对等站点/DC。我们将使用的 S3 准备好的查询 JSON 在下面设置了几个关键的旋钮,我们将对其进行解释
Service.Service:目录中的确切服务名称(ingress-rgw-s3,如本指南中所用)。如果您的服务使用不同的名称,请更改它。OnlyPassing: true:仅返回健康检查通过的实例。这使得 DNS 响应具有健康感知能力。Near: "_agent":评估并排序结果“靠近”查询 Consul 代理;实际上,这意味着本地数据中心的服务器。这默认情况下为您提供本地性。Failover:控制在没有本地通过实例时跨站点行为。NearestN: 1表示“优先选择最近的数据中心作为一个组”。Datacenters: ["madrid","paris"]是允许的回退集。如果在马德里执行且本地没有任何运行状况良好的实例,Consul 将返回巴黎端点;同样,在巴黎,它可以返回马德里端点。DNS.TTL: "5s":短 TTL 可确保客户端在故障和恢复期间快速刷新,从而最大限度地减少不必要的抖动。
此单个 S3 查询存在于两个站点,s3.query.consul,始终在接收 DNS 查询的站点(本地优先,需要时回退)处执行。
创建 S3 查询 JSON 文件,并使用 cURL 和 POST 请求将其加载到两个站点的 Consul 中。我们从我们的马德里站点和 ceph-node-00 开始
ceph-node-00# cat >/var/lib/consul/s3-query.json <<'EOF'
{
"Name": "s3",
"Service": {
"Service": "ingress-rgw-s3",
"OnlyPassing": true,
"Near": "_agent",
"Failover": { "NearestN": 1, "Datacenters": ["madrid","paris"] }
},
"DNS": { "TTL": "5s" }
}
EOF
ceph-node-00# curl -i -X POST -H 'Content-Type: application/json' \
--data-binary @/var/lib/consul/s3-query.json \
http://127.0.0.1:8500/v1/query
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept-Encoding
X-Consul-Default-Acl-Policy: allow
Date: Thu, 28 Aug 2025 16:56:40 GMT
Content-Length: 45
{"ID":"d5ea3072-7975-3690-5b65-f8745bcc21a6"}
然后我们对我们的巴黎 DC Consul 配置执行相同的操作
ceph-node-04# cat >/var/lib/consul/s3-query.json <<'EOF'
{
"Name": "s3",
"Service": {
"Service": "ingress-rgw-s3",
"OnlyPassing": true,
"Near": "_agent",
"Failover": { "NearestN": 1, "Datacenters": ["madrid","paris"] }
},
"DNS": { "TTL": "5s" }
}
EOF
ceph-node-04# curl -i -X POST -H 'Content-Type: application/json' \
--data-binary @/var/lib/consul/s3-query.json \
http://127.0.0.1:8500/v1/query
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept-Encoding
X-Consul-Default-Acl-Policy: allow
Date: Thu, 28 Aug 2025 17:06:11 GMT
Content-Length: 45
{"ID":"c94d7a90-3e5f-9de0-c966-99a04bd9eeed"}
在以下命令中,我们确认查询已创建,并且端到端解析正常工作
- 查询本地 Consul HTTP API 以列出准备好的查询,并检查 S3 查询是否存在于 Raft 中。
- 从马德里执行 S3 查询并检查结果,检索来自马德里的健康 HAproxy Concentrator 端点列表。
- 从马德里和巴黎通过 DNS(TCP 端口
8600)执行准备好的查询,以确保它返回本地站点的通过的入口 IP 地址。
ceph-node-00# curl -s http://127.0.0.1:8500/v1/query | jq '.[].Name' | sort -u
"s3"
ceph-node-00# curl -s 'http://127.0.0.1:8500/v1/query/s3/execute' \
| jq '{Datacenter, Nodes: [.Nodes[] | {dc:.Node.Datacenter, ip:.Node.Address, svc:.Service.Service, port:.Service.Port}]}'
{
"Datacenter": "madrid",
"Nodes": [
{
"dc": "madrid",
"ip": "192.168.122.12",
"svc": "ingress-rgw-s3",
"port": 8080
},
{
"dc": "madrid",
"ip": "192.168.122.94",
"svc": "ingress-rgw-s3",
"port": 8080
},
{
"dc": "madrid",
"ip": "192.168.122.179",
"svc": "ingress-rgw-s3",
"port": 8080
}
]
}
ceph-node-00# dig -p 8600 @192.168.122.12 s3.query.consul A +short
192.168.122.12
192.168.122.179
192.168.122.94
ceph-node-00# dig -p 8600 @192.168.122.138 s3.query.consul A +short
192.168.122.138
192.168.122.175
192.168.122.214
结论 ¶
现在我们拥有一个用于 Ceph 对象网关的全局控制平面,该平面忠于数据中心边界:两个独立的 Consul 数据中心(马德里、巴黎)通过 WAN 联合,以及在两者中定义的单个准备好的查询 (s3)。该查询编码了我们的 GLB 策略,优先选择本地运行状况良好的入口,并且仅在必要时才回退到对等方。我们通过通过 HTTP API 和通过 TCP 端口 8600 上的 Consul DNS 执行它来证明它有效。有了这个,谁应该服务这个请求?的问题就解决了,而无需触及应用程序 URL 或集中状态。
接下来,在 第 4 部分 中,我们将 Consul 策略转换为标准的 DNS 体验。通过在每个站点(每个 DC 三个)冗余运行 CoreDNS,全局名称 s3.cephlab.com 始终解析为健康的、附近的入口,默认情况下。如果本地网关不可用,它会自动切换到其他站点。我们还将保留每个站点的固定名称(s3.madrid.cephlab.com、s3.paris.cephlab.com),以便在需要时进行确定性路由(测试、合规性、故障排除)。
作者感谢 IBM 对社区的支持,为我们提供时间来创建这些帖子。