跨集群使用 ES|QL

编辑

ES|QL 的跨集群搜索处于技术预览阶段,可能在将来的版本中发生更改或移除。Elastic 将致力于修复任何问题,但技术预览中的功能不受官方 GA 功能支持服务水平协议的约束。

对于 8.16 或更高版本上使用 ES|QL 进行跨集群搜索,远程集群也必须是 8.16 或更高版本。

使用 ES|QL,您可以在多个集群上执行单个查询。

先决条件

编辑
  • 跨集群搜索需要远程集群。要在 Elasticsearch Service 上设置远程集群,请参阅 在 Elasticsearch Service 上配置远程集群。如果您在自己的硬件上运行 Elasticsearch,请参阅 远程集群

    要确保您的远程集群配置支持跨集群搜索,请参阅 支持的跨集群搜索配置

  • 为了获得完整的跨集群搜索功能,本地和远程集群必须处于相同的 订阅级别
  • 本地协调节点必须具有 remote_cluster_client 节点角色。
  • 如果您使用 嗅探模式,则本地协调节点必须能够连接到远程集群上的种子节点和网关节点。

    我们建议使用能够充当协调节点的网关节点。种子节点可以是这些网关节点的子集。

  • 如果您使用 代理模式,则本地协调节点必须能够连接到配置的 proxy_address。此地址处的代理必须能够将连接路由到远程集群上的网关节点和协调节点。
  • 跨集群搜索需要在本地集群和远程集群上使用不同的安全权限。请参阅 配置跨集群搜索的权限远程集群

安全模型

编辑

Elasticsearch 支持两种跨集群搜索 (CCS) 安全模型

要检查正在使用哪个安全模型连接您的集群,请运行 GET _remote/info。如果您使用的是 API 密钥认证方法,则会在响应中看到 "cluster_credentials" 密钥。

TLS 证书认证
编辑

TLS 证书认证使用双向 TLS 保护远程集群。当单个管理员完全控制两个集群时,这可能是首选模型。我们通常建议两个集群中的角色及其权限相同。

有关先决条件和详细的设置说明,请参阅 TLS 证书认证

API 密钥认证
编辑

以下信息与使用 ES|QL 跨集群以及 基于 API 密钥的安全模型 相关。您需要按照该页面上的步骤进行 完整的设置说明。此页面仅包含特定于 ES|QL 的其他信息。

基于 API 密钥的跨集群搜索 (CCS) 能够更精细地控制集群之间允许的操作。当您为不同的集群拥有不同的管理员并希望更好地控制谁可以访问哪些数据时,这可能是首选模型。在此模型中,集群管理员必须明确定义授予集群和用户的访问权限。

您需要

使用基于 API 密钥的安全模型的 ES|QL 需要一些可能在使用传统查询 DSL 基于搜索时不需要的其他权限。以下示例 API 调用创建了一个角色,该角色在使用基于 API 密钥的安全模型时可以使用 ES|QL 查询远程索引。最终权限 remote_cluster 允许远程丰富操作。

resp = client.security.put_role(
    name="remote1",
    cluster=[
        "cross_cluster_search"
    ],
    indices=[
        {
            "names": [
                ""
            ],
            "privileges": [
                "read"
            ]
        }
    ],
    remote_indices=[
        {
            "names": [
                "logs-*"
            ],
            "privileges": [
                "read",
                "read_cross_cluster"
            ],
            "clusters": [
                "my_remote_cluster"
            ]
        }
    ],
    remote_cluster=[
        {
            "privileges": [
                "monitor_enrich"
            ],
            "clusters": [
                "my_remote_cluster"
            ]
        }
    ],
)
print(resp)
const response = await client.security.putRole({
  name: "remote1",
  cluster: ["cross_cluster_search"],
  indices: [
    {
      names: [""],
      privileges: ["read"],
    },
  ],
  remote_indices: [
    {
      names: ["logs-*"],
      privileges: ["read", "read_cross_cluster"],
      clusters: ["my_remote_cluster"],
    },
  ],
  remote_cluster: [
    {
      privileges: ["monitor_enrich"],
      clusters: ["my_remote_cluster"],
    },
  ],
});
console.log(response);
POST /_security/role/remote1
{
  "cluster": ["cross_cluster_search"], 
  "indices": [
    {
      "names" : [""], 
      "privileges": ["read"]
    }
  ],
  "remote_indices": [ 
    {
      "names": [ "logs-*" ],
      "privileges": [ "read","read_cross_cluster" ], 
      "clusters" : ["my_remote_cluster"] 
    }
  ],
   "remote_cluster": [ 
        {
            "privileges": [
                "monitor_enrich"
            ],
            "clusters": [
                "my_remote_cluster"
            ]
        }
    ]
}

本地集群需要 cross_cluster_search 集群权限。

通常,用户将具有读取本地和远程索引的权限。但是,对于角色仅用于搜索远程集群的情况,本地集群仍然需要 read 权限。要提供对本地集群的读取访问权限,但禁止读取本地集群中的任何索引,names 字段可以为空字符串。

允许读取访问远程集群的索引。配置的 跨集群 API 密钥 也必须允许读取此索引。

使用基于 API 密钥的安全模型的 ES|QL 跨集群时,始终需要 read_cross_cluster 权限。

这些权限适用的远程集群。此远程集群必须使用 跨集群 API 密钥 进行配置,并在可以查询远程索引之前连接到远程集群。使用 远程集群信息 API 验证连接。

允许远程丰富。没有它,用户就无法从远程集群上的 .enrich 索引中读取。remote_cluster 安全权限是在 8.15.0 版本中引入的。

然后,您需要一个具有上述权限的用户或 API 密钥。以下示例 API 调用创建了一个具有 remote1 角色的用户。

resp = client.security.put_user(
    username="remote_user",
    password="<PASSWORD>",
    roles=[
        "remote1"
    ],
)
print(resp)
const response = await client.security.putUser({
  username: "remote_user",
  password: "<PASSWORD>",
  roles: ["remote1"],
});
console.log(response);
POST /_security/user/remote_user
{
  "password" : "<PASSWORD>",
  "roles" : [ "remote1" ]
}

请记住,来自本地集群的所有跨集群请求都受跨集群 API 密钥的权限约束,这些权限由远程集群的管理员控制。

在 8.15.0 之前的版本中创建的跨集群 API 密钥需要替换或更新,以添加 ES|QL 与 ENRICH 需要的新的权限。

远程集群设置

编辑

配置安全模型后,您可以添加远程集群。

以下 集群更新设置 API 请求添加了三个远程集群:cluster_onecluster_twocluster_three

resp = client.cluster.put_settings(
    persistent={
        "cluster": {
            "remote": {
                "cluster_one": {
                    "seeds": [
                        "35.238.149.1:9300"
                    ],
                    "skip_unavailable": True
                },
                "cluster_two": {
                    "seeds": [
                        "35.238.149.2:9300"
                    ],
                    "skip_unavailable": False
                },
                "cluster_three": {
                    "seeds": [
                        "35.238.149.3:9300"
                    ]
                }
            }
        }
    },
)
print(resp)
response = client.cluster.put_settings(
  body: {
    persistent: {
      cluster: {
        remote: {
          cluster_one: {
            seeds: [
              '35.238.149.1:9300'
            ],
            skip_unavailable: true
          },
          cluster_two: {
            seeds: [
              '35.238.149.2:9300'
            ],
            skip_unavailable: false
          },
          cluster_three: {
            seeds: [
              '35.238.149.3:9300'
            ]
          }
        }
      }
    }
  }
)
puts response
const response = await client.cluster.putSettings({
  persistent: {
    cluster: {
      remote: {
        cluster_one: {
          seeds: ["35.238.149.1:9300"],
          skip_unavailable: true,
        },
        cluster_two: {
          seeds: ["35.238.149.2:9300"],
          skip_unavailable: false,
        },
        cluster_three: {
          seeds: ["35.238.149.3:9300"],
        },
      },
    },
  },
});
console.log(response);
PUT _cluster/settings
{
  "persistent": {
    "cluster": {
      "remote": {
        "cluster_one": {
          "seeds": [
            "35.238.149.1:9300"
          ],
          "skip_unavailable": true
        },
        "cluster_two": {
          "seeds": [
            "35.238.149.2:9300"
          ],
          "skip_unavailable": false
        },
        "cluster_three": {  
          "seeds": [
            "35.238.149.3:9300"
          ]
        }
      }
    }
  }
}

由于 skip_unavailable 未在 cluster_three 上设置,因此它使用默认值 false。有关详细信息,请参阅 可选远程集群 部分。

跨多个集群查询

编辑

FROM 命令中,使用格式 <remote_cluster_name>:<target> 指定远程集群上的数据流和索引。例如,以下 ES|QL 请求查询名为 cluster_one 的单个远程集群上的 my-index-000001 索引

FROM cluster_one:my-index-000001
| LIMIT 10

同样,此 ES|QL 请求查询三个集群中的 my-index-000001 索引

  • 本地(“查询”)集群
  • 两个远程集群,cluster_onecluster_two
FROM my-index-000001,cluster_one:my-index-000001,cluster_two:my-index-000001
| LIMIT 10

同样,此 ES|QL 请求查询所有远程集群(cluster_onecluster_twocluster_three)中的 my-index-000001 索引

FROM *:my-index-000001
| LIMIT 10

跨集群元数据

编辑

使用 "include_ccs_metadata": true 选项,用户可以请求 ES|QL 跨集群搜索响应包含有关每个集群上的搜索的元数据(当响应格式为 JSON 时)。这里我们展示了一个使用异步搜索端点的示例。当请求时,同步搜索端点响应中也存在跨集群搜索元数据。

resp = client.esql.async_query(
    format="json",
    body={
        "query": "\n    FROM my-index-000001,cluster_one:my-index-000001,cluster_two:my-index*\n    | STATS COUNT(http.response.status_code) BY user.id\n    | LIMIT 2\n  ",
        "include_ccs_metadata": True
    },
)
print(resp)
const response = await client.esql.asyncQuery({
  format: "json",
  body: {
    query:
      "\n    FROM my-index-000001,cluster_one:my-index-000001,cluster_two:my-index*\n    | STATS COUNT(http.response.status_code) BY user.id\n    | LIMIT 2\n  ",
    include_ccs_metadata: true,
  },
});
console.log(response);
POST /_query/async?format=json
{
  "query": """
    FROM my-index-000001,cluster_one:my-index-000001,cluster_two:my-index*
    | STATS COUNT(http.response.status_code) BY user.id
    | LIMIT 2
  """,
  "include_ccs_metadata": true
}

它返回

{
  "is_running": false,
  "took": 42,  
  "columns" : [
    {
      "name" : "COUNT(http.response.status_code)",
      "type" : "long"
    },
    {
      "name" : "user.id",
      "type" : "keyword"
    }
  ],
  "values" : [
    [4, "elkbee"],
    [1, "kimchy"]
  ],
  "_clusters": {  
    "total": 3,
    "successful": 3,
    "running": 0,
    "skipped": 0,
    "partial": 0,
    "failed": 0,
    "details": { 
      "(local)": { 
        "status": "successful",
        "indices": "blogs",
        "took": 41,  
        "_shards": { 
          "total": 13,
          "successful": 13,
          "skipped": 0,
          "failed": 0
        }
      },
      "cluster_one": {
        "status": "successful",
        "indices": "cluster_one:my-index-000001",
        "took": 38,
        "_shards": {
          "total": 4,
          "successful": 4,
          "skipped": 0,
          "failed": 0
        }
      },
      "cluster_two": {
        "status": "successful",
        "indices": "cluster_two:my-index*",
        "took": 40,
        "_shards": {
          "total": 18,
          "successful": 18,
          "skipped": 1,
          "failed": 0
        }
      }
    }
  }
}

整个搜索(跨所有集群)花费的时间(以毫秒为单位)。

此计数器部分显示所有可能的集群搜索状态以及当前处于该状态的集群搜索数量。集群可以具有以下状态之一:正在运行成功(所有分片上的搜索都成功)、已跳过(在标记为 skip_unavailable=true 的集群上搜索失败)或 失败(在标记为 skip_unavailable=false 的集群上搜索失败)。

_clusters/details 部分显示有关每个集群上搜索的元数据。

如果您在跨集群搜索中包含了您发送请求到的本地集群中的索引,则将其识别为“(本地)”。

每个集群上搜索花费的时间(以毫秒为单位)。这可以用于确定哪些集群的响应时间比其他集群慢。

该集群上搜索的分片详细信息,包括由于 can-match 阶段结果而跳过的分片数量。当分片无法包含任何匹配数据时,将跳过它们,因此不会包含在完整的 ES|QL 查询中。

跨集群元数据可用于确定是否有任何数据来自集群。例如,在下面的查询中,cluster-two 的通配符表达式未解析为具体索引(或索引)。因此,集群被标记为已跳过,并且搜索的分片总数设置为零。

resp = client.esql.async_query(
    format="json",
    body={
        "query": "\n    FROM cluster_one:my-index*,cluster_two:logs*\n    | STATS COUNT(http.response.status_code) BY user.id\n    | LIMIT 2\n  ",
        "include_ccs_metadata": True
    },
)
print(resp)
const response = await client.esql.asyncQuery({
  format: "json",
  body: {
    query:
      "\n    FROM cluster_one:my-index*,cluster_two:logs*\n    | STATS COUNT(http.response.status_code) BY user.id\n    | LIMIT 2\n  ",
    include_ccs_metadata: true,
  },
});
console.log(response);
POST /_query/async?format=json
{
  "query": """
    FROM cluster_one:my-index*,cluster_two:logs*
    | STATS COUNT(http.response.status_code) BY user.id
    | LIMIT 2
  """,
  "include_ccs_metadata": true
}

它返回

{
  "is_running": false,
  "took": 55,
  "columns": [
     ... // not shown
  ],
  "values": [
     ... // not shown
  ],
  "_clusters": {
    "total": 2,
    "successful": 2,
    "running": 0,
    "skipped": 0,
    "partial": 0,
    "failed": 0,
    "details": {
      "cluster_one": {
        "status": "successful",
        "indices": "cluster_one:my-index*",
        "took": 38,
        "_shards": {
          "total": 4,
          "successful": 4,
          "skipped": 0,
          "failed": 0
        }
      },
      "cluster_two": {
        "status": "skipped", 
        "indices": "cluster_two:logs*",
        "took": 0,
        "_shards": {
          "total": 0, 
          "successful": 0,
          "skipped": 0,
          "failed": 0
        }
      }
    }
  }
}

此集群被标记为已跳过,因为该集群上没有匹配的索引。

表示没有搜索到分片(由于没有任何匹配的索引)。

跨集群丰富化

编辑

跨集群的 ES|QL 丰富化操作类似于 本地丰富化。如果丰富化策略及其丰富化索引在所有集群中都一致,只需像没有远程集群一样编写丰富化命令即可。在此默认模式下,ES|QL 可以在本地集群或远程集群上执行丰富化命令,旨在最大程度地减少计算或集群间数据传输。确保策略在本地集群和远程集群上都存在且数据一致,对于 ES|QL 生成一致的查询结果至关重要。

使用基于 API 密钥的安全模型的跨集群 ES|QL 丰富化功能在 8.15.0 版本中引入。在 8.15.0 之前版本中创建的跨集群 API 密钥需要替换或更新以使用新的必需权限。请参阅API 密钥身份验证部分中的示例。

在以下示例中,使用 hosts 策略的丰富化可以在本地集群或远程集群 cluster_one 上执行。

FROM my-index-000001,cluster_one:my-index-000001
| ENRICH hosts ON ip
| LIMIT 10

仅针对远程集群使用 ES|QL 查询进行丰富化也可以在本地集群上发生。这意味着以下查询也需要在本地集群上存在 hosts 丰富化策略。

FROM cluster_one:my-index-000001,cluster_two:my-index-000001
| LIMIT 10
| ENRICH hosts ON ip
使用协调器模式的丰富化
编辑

ES|QL 提供了丰富化 _coordinator 模式,以强制 ES|QL 在本地集群上执行丰富化命令。当远程集群上不可用丰富化策略或在集群之间维护丰富化索引的一致性具有挑战性时,应使用此模式。

FROM my-index-000001,cluster_one:my-index-000001
| ENRICH _coordinator:hosts ON ip
| SORT host_name
| LIMIT 10

使用 _coordinator 模式的丰富化通常会增加集群间数据传输和本地集群的工作负载。

使用远程模式的丰富化
编辑

ES|QL 还提供了丰富化 _remote 模式,以强制 ES|QL 在目标索引所在的每个远程集群上独立执行丰富化命令。此模式可用于管理每个集群上不同的丰富化数据,例如每个区域的主机的详细信息,其中目标(主)索引包含来自这些主机的日志事件。

在以下示例中,需要在所有远程集群上存在 hosts 丰富化策略:querying 集群(因为包含本地索引)、远程集群 cluster_onecluster_two

FROM my-index-000001,cluster_one:my-index-000001,cluster_two:my-index-000001
| ENRICH _remote:hosts ON ip
| SORT host_name
| LIMIT 10

_remote 丰富化不能在 stats 命令之后执行。以下示例将导致错误

FROM my-index-000001,cluster_one:my-index-000001,cluster_two:my-index-000001
| STATS COUNT(*) BY ip
| ENRICH _remote:hosts ON ip
| SORT host_name
| LIMIT 10
多个丰富化命令
编辑

您可以在同一个查询中包含多个具有不同模式的丰富化命令。ES|QL 将尝试相应地执行它们。例如,此查询执行两次丰富化,首先使用 hosts 策略在任何集群上执行,然后使用 vendors 策略在本地集群上执行。

FROM my-index-000001,cluster_one:my-index-000001,cluster_two:my-index-000001
| ENRICH hosts ON ip
| ENRICH _coordinator:vendors ON os
| LIMIT 10

_remote 丰富化命令不能在 _coordinator 丰富化命令之后执行。以下示例将导致错误。

FROM my-index-000001,cluster_one:my-index-000001,cluster_two:my-index-000001
| ENRICH _coordinator:hosts ON ip
| ENRICH _remote:vendors ON os
| LIMIT 10

从 ES|QL 查询中排除集群或索引

编辑

要排除整个集群,请在 FROM 命令中在集群别名前添加减号,例如:-my_cluster:*

FROM my-index-000001,cluster*:my-index-000001,-cluster_three:*
| LIMIT 10

要排除特定的远程索引,请在 FROM 命令中在索引前添加减号,例如 my_cluster:-my_index

FROM my-index-000001,cluster*:my-index-*,cluster_three:-my-index-000001
| LIMIT 10

可选远程集群

编辑

ES|QL 的跨集群搜索目前不尊重 skip_unavailable 设置。因此,如果请求中指定的远程集群不可用或发生故障,则无论设置如何,ES|QL 查询的跨集群搜索都将失败。

我们正在积极努力使 ES|QL 的跨集群搜索行为与其他跨集群搜索 API 保持一致。

升级期间跨集群查询

编辑

在本地集群上执行滚动升级时,您仍然可以搜索远程集群。但是,本地协调节点的“升级自”和“升级至”版本必须与远程集群的网关节点兼容。

在升级持续时间之外,不支持在同一集群中运行多个版本的 Elasticsearch。

有关升级的更多信息,请参阅 升级 Elasticsearch