跨集群使用 ES|QL

编辑

ES|QL 的跨集群搜索为技术预览版,可能会在未来版本中更改或删除。Elastic 将努力修复任何问题,但技术预览版中的功能不受正式 GA 功能的支持 SLA 约束。

使用 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"
          ]
        }
      }
    }
  }
}

由于没有在cluster_three上设置skip_unavailable,因此它使用默认值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
        }
      }
    }
  }
}

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

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

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

如果您在跨集群搜索中包含了来自您发送请求的本地集群的索引,则它被标识为“(local)”。

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

该集群上搜索的分片详细信息,包括由于 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 生成一致的查询结果至关重要。

8.15.0 版本中引入了使用 API 密钥安全模型的跨集群 ES|QL 富化。在 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

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

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

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

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