跨集群搜索(Cross-cluster search,CCS)让一个 OpenSearch 集群查询其他集群中的索引,用于检索和分析分散在多个集群的数据。Security 插件默认提供这一能力,但每个集群仍须配置远程连接和访问权限。
本文中的“协调集群”接收客户端请求,“远程集群”保存被查询的数据。Docker 示例使用当前官方页面列出的 OpenSearch 3.9.0;远程角色评估选项于 3.9 引入,不能据此断言旧版本具备相同选项。以下命令、容器 ID、IP 地址、响应和密码均为官方示例,本次没有部署集群或执行请求。
前置条件
如果参与 CCS 的任一节点在 opensearch.yml 中显式覆盖了 node.roles,角色列表必须包含 remote_cluster_client:
node.roles: [<other_roles>, remote_cluster_client]
这里的 <other_roles> 表示需要保留的其他角色。不要把占位符当成实际配置,也不要因加入远程客户端角色而覆盖节点原有职责。
认证与授权流程
一次跨集群请求依次经历以下过程:
- 协调集群上的 Security 插件认证用户。
- 协调集群获取该用户的后端角色。
- 请求连同已认证用户的信息一起转发到远程集群。
- 远程集群评估用户是否有权访问目标数据。
协调集群与远程集群可以采用不同的认证和授权配置;官方建议保持相同设置,以减少配置差异。
远程集群如何评估角色
默认情况下,远程集群会综合协调集群传来的安全角色及自己的角色映射来判断权限。因此,用户在协调集群拥有较宽权限,例如 all_access,这些角色也会参与远程集群的权限评估。
从 OpenSearch 3.9 开始,可以在远程集群设置以下选项,让远程集群独立决定 CCS 用户的权限:
PUT /_cluster/settings
{
"persistent": {
"plugins.security.ccs.ignore_source_security_roles": true
}
}
设置为 true 后,远程集群会去掉协调集群传播的安全角色,只使用自己的 roles_mapping.yml 进行授权。默认值为 false,因此未更改此选项的现有部署继续沿用原来的行为。
这一选项有两项限制:
- 远程集群不会重新认证 CCS 用户,因此不会读取
internal_users.yml中的角色分配。启用该选项后,必须通过远程集群的roles_mapping.yml授权;在internal_users.yml中设置opendistro_security_roles对 CCS 授权没有作用。 - 它只影响跨集群搜索请求,不影响其他跨集群操作。
为远程索引设置权限
用户必须对远程索引具有 READ 或 SEARCH 权限。如果查询参数包含 ccs_minimize_roundtrips=false,还需要下面的索引权限:
indices:admin/shards/search_shards
该参数要求 OpenSearch 不再尽量减少与远程集群之间的请求往返次数。更多参数见官方 Search API 参数说明。
官方给出的 roles.yml 示例为:
humanresources:
cluster:
- CLUSTER_COMPOSITE_OPS_RO
indices:
'humanresources':
'*':
- READ
- indices:admin/shards/search_shards # needed when the search request includes parameter setting 'ccs_minimize_roundtrips=false'.
官方页面另附 OpenSearch Dashboards 创建角色的界面图。其关键操作是对目标索引配置读取权限,并在使用上述参数时加入 indices:admin/shards/search_shards。这里将教学信息写成可检索文字,不复制截图中的界面数据。
Docker 示例:两个独立的单节点集群
将以下配置保存为 docker-compose.yml,在自己的测试环境运行 docker compose up,可以启动两个位于同一 Docker 网络中的单节点集群:
version: '3'
services:
opensearch-ccs-node1:
image: opensearchproject/opensearch:3.9.0
container_name: opensearch-ccs-node1
environment:
- cluster.name=opensearch-ccs-cluster1
- discovery.type=single-node
- bootstrap.memory_lock=true # along with the memlock settings below, disables swapping
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
- "OPENSEARCH_INITIAL_ADMIN_PASSWORD=<custom-admin-password>" # The initial admin password used by the demo configuration
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- opensearch-data1:/usr/share/opensearch/data
ports:
- 9200:9200
- 9600:9600 # required for Performance Analyzer
networks:
- opensearch-net
opensearch-ccs-node2:
image: opensearchproject/opensearch:3.9.0
container_name: opensearch-ccs-node2
environment:
- cluster.name=opensearch-ccs-cluster2
- discovery.type=single-node
- bootstrap.memory_lock=true # along with the memlock settings below, disables swapping
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
- "OPENSEARCH_INITIAL_ADMIN_PASSWORD=<custom-admin-password>" # The initial admin password used by the demo configuration
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- opensearch-data2:/usr/share/opensearch/data
ports:
- 9250:9200
- 9700:9600 # required for Performance Analyzer
networks:
- opensearch-net
volumes:
opensearch-data1:
opensearch-data2:
networks:
opensearch-net:
<custom-admin-password> 是自定义管理员密码占位符,须在实际测试中替换。示例中的 -k 跳过 TLS 证书验证,用于这里的演示配置;真实连接应按部署的证书和信任链配置客户端。下面的混合代码块同时展示命令与官方省略后的响应,响应中的 ... 不是可以执行的 JSON。
确认集群名称
curl -X GET -u 'admin:<custom-admin-password>' -k 'https://localhost:9200'
{
"cluster_name" : "opensearch-ccs-cluster1",
...
}
curl -X GET -u 'admin:<custom-admin-password>' -k 'https://localhost:9250'
{
"cluster_name" : "opensearch-ccs-cluster2",
...
}
两个集群都通过 localhost 访问,因此必须区分端口:本例用 HTTP 端口 9200 的 opensearch-ccs-node1 作为远程集群,用 HTTP 端口 9250 的 opensearch-ccs-node2 作为协调集群。
找到远程集群的传输地址
先查看远程节点的容器 ID。以下列表是官方示例输出,实际容器 ID 会不同:
docker ps
CONTAINER ID IMAGE PORTS NAMES
6fe89ebc5a8e opensearchproject/opensearch:3.9.0 0.0.0.0:9200->9200/tcp, 0.0.0.0:9600->9600/tcp, 9300/tcp opensearch-ccs-node1
2da08b6c54d8 opensearchproject/opensearch:3.9.0 9300/tcp, 0.0.0.0:9250->9200/tcp, 0.0.0.0:9700->9600/tcp opensearch-ccs-node2
再查看该容器在 Docker 网络中的 IP 地址:
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 6fe89ebc5a8e
172.31.0.3
容器 IP 也是示例值,必须使用自己的检查结果。远程连接使用节点间的传输端口 9300,而不是面向客户端的 HTTP 端口 9200。
在协调集群添加远程连接
将远程集群别名与种子节点地址写入协调集群。本例只有一个种子节点:
curl -k -X PUT -H 'Content-Type: application/json' -u 'admin:<custom-admin-password>' 'https://localhost:9250/_cluster/settings' -d '
{
"persistent": {
"cluster.remote": {
"opensearch-ccs-cluster1": {
"seeds": ["172.31.0.3:9300"]
}
}
}
}'
这些 cURL 请求也可在 OpenSearch Dashboards 的 Dev Tools 中发送。官方 Dev Tools 配图展示的关键内容是 PUT /_cluster/settings、同一段 persistent.cluster.remote 请求体,以及向协调集群发送请求;具体命令已完整保留在上方。
写入文档并以管理员查询
在远程集群的 books 索引写入一条文档:
curl -XPUT -k -H 'Content-Type: application/json' -u 'admin:<custom-admin-password>' 'https://localhost:9200/books/_doc/1' -d '{"Dracula": "Bram Stoker"}'
然后向协调集群查询 远程集群别名:索引名。下面是官方管理员请求与示例响应:
curl -XGET -k -u 'admin:<custom-admin-password>' 'https://localhost:9250/opensearch-ccs-cluster1:books/_search?pretty'
{
...
"hits": [{
"_index": "opensearch-ccs-cluster1:books",
"_id": "1",
"_score": 1.0,
"_source": {
"Dracula": "Bram Stoker"
}
}]
}
检查普通用户的权限
官方接着在两个集群创建 booksuser。password 是文档中的演示字符串,不是本站或任何账户的真实凭据,也不宜作为实际账户密码:
curl -XPUT -k -u 'admin:<custom-admin-password>' 'https://localhost:9200/_plugins/_security/api/internalusers/booksuser' -H 'Content-Type: application/json' -d '{"password":"password"}'
curl -XPUT -k -u 'admin:<custom-admin-password>' 'https://localhost:9250/_plugins/_security/api/internalusers/booksuser' -H 'Content-Type: application/json' -d '{"password":"password"}'
官方示例用这个尚未获授权的用户搜索,并展示权限错误。原页面的这一条 URL 在 cluster 1 中多了一个空格;下面保留原例以便核对,但该字面 URL 本身有排版错误,不能用它验证所示的 403 响应:
curl -X GET -k -u booksuser:password 'https://localhost:9250/opensearch-ccs-cluster 1:books/_search?pretty'
{
"error" : {
"root_cause" : [
{
"type" : "security_exception",
"reason" : "no permissions for [indices:admin/shards/search_shards, indices:data/read/search] and User [name=booksuser, roles=[], requestedTenant=null]"
}
],
"type" : "security_exception",
"reason" : "no permissions for [indices:admin/shards/search_shards, indices:data/read/search] and User [name=booksuser, roles=[], requestedTenant=null]"
},
"status" : 403
}
对应本例先前设置的集群别名,应使用下面这条更正命令;这里仅作静态修正,没有执行或重新验证响应:
curl -X GET -k -u booksuser:password 'https://localhost:9250/opensearch-ccs-cluster1:books/_search?pretty'
在远程集群创建具有相应权限的 booksrole,并把 booksuser 映射到该角色:
curl -XPUT -k -u 'admin:<custom-admin-password>' -H 'Content-Type: application/json' 'https://localhost:9200/_plugins/_security/api/roles/booksrole' -d '{"index_permissions":[{"index_patterns":["books"],"allowed_actions":["indices:admin/shards/search_shards","indices:data/read/search"]}]}'
curl -XPUT -k -u 'admin:<custom-admin-password>' -H 'Content-Type: application/json' 'https://localhost:9200/_plugins/_security/api/rolesmapping/booksrole' -d '{"users" : ["booksuser"]}'
本例在两个集群创建同一用户,由协调集群检查凭据,由远程集群检查数据访问权限;新增角色及角色映射的请求发往远程集群。部署时还应结合前文的角色传播规则判断实际权限,尤其是是否启用了 ignore_source_security_roles,不能把演示中的命令数等同于任何部署的完整权限配置。
再次查询时,官方展示的结果为:
curl -XGET -k -u booksuser:password 'https://localhost:9250/opensearch-ccs-cluster1:books/_search?pretty'
{
...
"hits": [{
"_index": "opensearch-ccs-cluster1:books",
"_id": "1",
"_score": 1.0,
"_source": {
"Dracula": "Bram Stoker"
}
}]
}
裸机或虚拟机部署
在裸机或虚拟机上,流程相同,只需将 Docker 示例中的地址换成实际集群 IP 或域名。例如,从 opensearch-domain-1 配置到 opensearch-domain-2 的远程连接:
curl -k -X PUT -H 'Content-Type: application/json' -u 'admin:<custom-admin-password>' 'https://opensearch-domain-1:9200/_cluster/settings' -d '
{
"persistent": {
"cluster.remote": {
"opensearch-ccs-cluster2": {
"seeds": ["opensearch-domain-2:9300"]
}
}
}
}'
种子列表可以先指向远程集群中的一个节点;连接后的节点发现会查询集群其他节点。应保证发现的地址也可从协调集群访问。
官方查询示例与响应如下:
curl -XGET -k -u 'admin:<custom-admin-password>' 'https://opensearch-domain-1:9200/opensearch-ccs-cluster2:books/_search?pretty'
{
...
"hits": [{
"_index": "opensearch-ccs-cluster2:books",
"_id": "1",
"_score": 1.0,
"_source": {
"Dracula": "Bram Stoker"
}
}]
}
Kubernetes 与 Helm 部署
官方 使用 Helm 安装 OpenSearch 示例创建的服务类型是 ClusterIP,通常只能在 Kubernetes 集群内部访问。跨集群连接需要协调集群能访问的外部端点,例如适当配置的 LoadBalancer 或支持 TCP 传输的 Ingress。
curl -k -XPUT -H 'Content-Type: application/json' -u 'admin:<custom-admin-password>' 'https://opensearch-domain-1:9200/_cluster/settings' -d '
{
"persistent": {
"cluster.remote": {
"opensearch-ccs-cluster2": {
"seeds": ["ingress:9300"]
}
}
}
}'
ingress 是示例地址,9300 对应节点传输协议。普通 HTTP 路由不能自动替代这条 TCP 连接;应根据自己的控制器、负载均衡器和网络拓扑确认协议支持及后续发现地址的可达性。
通过代理连接远程集群
CCS 也可以连接位于代理后方的集群。官方用 NGINX 演示不终止 TLS 的基本反向代理配置;此例要求 OpenSearch 同时启用传输层 TLS 和 HTTP TLS。证书配置参见官方 TLS 证书指南。
使用代理模式前,应确保:
- 协调集群的节点可以连接所配置的
proxy_address。 - 代理能够把连接转发到远程集群节点。
NGINX 示例
stream {
upstream opensearch-transport {
server <opensearch>:9300;
}
upstream opensearch-http {
server <opensearch>:9200;
}
server {
listen 8300;
ssl_certificate /.../3.9.0/config/esnode.pem;
ssl_certificate_key /.../3.9.0/config/esnode-key.pem;
ssl_trusted_certificate /.../3.9.0/config/root-ca.pem;
proxy_pass opensearch-transport;
ssl_preread on;
}
server {
listen 443;
listen [::]:443;
ssl_certificate /.../3.9.0/config/esnode.pem;
ssl_certificate_key /.../3.9.0/config/esnode-key.pem;
ssl_trusted_certificate /.../3.9.0/config/root-ca.pem;
proxy_pass opensearch-http;
ssl_preread on;
}
}
HTTP 代理入口为 443,传输代理入口为 8300。<opensearch> 和证书路径都是占位符;stream、ssl_preread 等指令也要求相应 NGINX 模块可用。上面的证书指令按官方例子保留,示例并未在 listen 上启用 TLS 终止,不能据此声称代理已完成服务器证书验证或重新加密。本次没有运行 NGINX 配置检查。
OpenSearch 的代理模式配置
在需要发起远程连接的集群上,指定远程别名、proxy 模式及代理地址:
curl -k -XPUT -H 'Content-Type: application/json' -u 'admin:<custom-admin-password>' 'https://opensearch:9200/_cluster/settings' -d '
{
"persistent": {
"cluster.remote": {
"opensearch-remote-cluster": {
"mode": "proxy",
"proxy_address": "<remote-cluster-proxy>:8300"
}
}
}
}'
这里使用的是刚才设置的传输入口 8300。官方段落称“配置远程集群指向代理”,实际请求配置的是调用该 API 的集群所要连接的远程别名;应按协调端与远程端的真实关系选择请求目标。
来源、许可与修改说明
本文依据 OpenSearch 文档贡献者的 Cross-cluster search 完整主要正文整理与汉化,核对日期为 2026-10-03。文档仓库 README 的许可声明 指定 Apache License 2.0;完整许可见 仓库 LICENSE。原 NOTICE 为:Copyright OpenSearch contributors.
修改包括中文表述、章节重排、两张教学截图的信息文字化,以及对示例地址、权限传播、版本边界和 NGINX 配置语义的补充说明。保留了原页面全部 20 个代码或输出块;另增加 1 条已明确标注的 URL 排版修正。文中示例响应来自原文,未声称本次复现。所有修改遵循 Apache License 2.0,原材料按该许可提供,不附带保证;项目名称用于来源归属,并不表示官方认可本站内容。











暂无评论内容