“No Elasticsearch Node Available”

现象

现场的 ORG 服务使用了 ES 搜索,但是现场的搜索一直不能够正常工作。 现场情况如下:

  • ES 版本为 5.5.1, 部署在机器 B 上,采用 Docker 方式部署。
  • ORG 服务采用 go 语言开发,驱动是采用的 "gopkg.in/olivere/elastic.v5" 部署在机器A上,搜索数据一直返回空,在 ES 机器上用命令行直接搜索有结果数据。
  • TES 服务采用 node.js 开发,也连接 ES ,能够正常工作。
  • 机器 A 上 使用 curl es_addr 能够正常显示 ES 集群相关信息。
  • ORG 服务于 ES 服务部署在同一台机器上时,可以正常工作。

简单定位和 ORG 使用的 ES 驱动有一定关系。

排查

用 go 语言编写了一个最基本版本的 es 连接测试:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
    "gopkg.in/olivere/elastic.v5"
)

func main() {
    // 1. 测试 ES 连接的情况
    addrs := []string{"http://192.168.0.21:9205"}
    // retry intervals in milliseconds.
    retrier := elastic.NewBackoffRetrier(elastic.NewSimpleBackoff(0, 50, 200, 500, 1000, 2000, 4000, 8000))

    // elastic.SetSniff(false)
    cli, err := elastic.NewClient(elastic.SetURL(addrs...), elastic.SetRetrier(retrier))
    if err != nil {
        fmt.Println("connect to elastic failed:", err)
        return
    } else {
        fmt.Println("Family", "ElasticSearch", cli)
    }

    // 2. 测试使用 go http 直接连接 ES 的过程
    tr := &http.Transport{
        MaxIdleConns:       10,
        IdleConnTimeout:    30 * time.Second,
        DisableCompression: true,
    }
    client := &http.Client{Transport: tr}
    resp, err := client.Get(addrs[0])
    if err != nil {
        fmt.Println("http connect failed:", err)
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)

    if err != nil {
        fmt.Println("Read data failed:", err)
        return
    }
    fmt.Println(string(body))
}

运行以上命令得到以下信息:

  1. 通过 elastic.v5 驱动连接 ES 有具体报错:“no Elasticsearch node available”
  2. 通过 http 访问的访问仍然能够正常打印出来 ES 的提示信息

因此问题更加清晰定位到是 elastic.v5驱动的工作方式与 ES 配合的过程中存在某些问题。通过网上搜索错误,找到问题相关联的 “v5 connect panic: no Elasticsearch node available”,经过与问题的情况分析,部署方式和访问方式上和我们的情况完全一致,因此问题的解决更近一步。更加详细的分析可以参见: Connection Problems 中的 “How to figure out connection problems?” 章节。

分析

sniffing 模式被启用(默认启用),Elastic 使用 Nodes Info API 查找群集中的所有节点。 找到这些节点后,它会定期更新内部连接列表。

现在发现的大多数连接问题,都是因为 Elastic 无法调用节点信息API 或无法访问到 Nodes Info API 提供的 IP:Port组合地址 。 例如,部署在 Docker 容器内有时会返回内部 IP:Port 组合,这些内部 IP:Port 组合只能从Docker 容器中访问,而不能从 ES 集群外部访问; 这种情况下,您需要更改 Elasticsearch 的网络绑定以绑定到外部可访问的网络接口, 关于 ES 部署在 Docker 中相关情况,可以参见 https://github.com/olivere/elastic/wiki/Docker。

以下是 Nodes Info API 通常返回的内容以及 Elastic 用于查找 IP 的节点:集群节点的端口组合(较新版本稍微更改了返回值):

$ curl -s -XGET 'http://127.0.0.1:9200/_nodes/http?pretty=1'
{
  "_nodes" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "cluster_name" : "elasticsearch",
  "nodes" : {
    "v9vBH0xXQ1-GZw-bOfxlFQ" : {
      "name" : "v9vBH0x",
      "transport_address" : "127.0.0.1:9300",
      "host" : "127.0.0.1",
      "ip" : "127.0.0.1",
      "version" : "5.6.8",
      "build_hash" : "688ecce",
      "roles" : [
        "master",
        "data",
        "ingest"
      ],
      "attributes" : {
        "ml.max_open_jobs" : "10",
        "ml.enabled" : "true"
      },
      "http" : {
        "bound_address" : [
          "0.0.0.0:9200"
        ],
        "publish_address" : "127.0.0.1:9200",  # !!! important for sniffing mode
        "max_content_length_in_bytes" : 104857600
      }
    }
  }
}

可以看到 publish_address 字段包含节点的 IP:PORT。 如果可以 curl 该此地址,则连接不应存在任何问题。

如果已经启用了 sniffing,但是仍然无法连接到 Elasticsearch,请确认您已将 http:// 作为URL的一部分。 使用像 localhost:9200 这样的地址如果不能正常工作,则需要 http://localhost:9200

解决

Go ES 驱动解决

可以在 ES 初始化的时候,关闭 Sniffing ,设置如下。

func main() {
    // ...
    elastic.SetSniff(false)
    // ...
}

ES 配置解决

如果在 Docker 中运行 ES 单机或者集群,需要保证 ES 在 Nodes Info API 中返回的 publish_address 在容器外部能够可以访问。我们可以通过设置 network.publish_hostnetwork.host 来解决。ES 中具体配置参见 http章节配置

network.publish_host 设置允许控制节点将在集群内发布的主机,以便其他节点能够连接到它。 当然,这不能是任何本地地址,默认情况下,它将是第一个非环回地址(如果可能)或本地地址。

network.host 设置是一个简单的设置,可以自动将 network.bind_hostnetwork.publish_host 设置为相同的主机值。

例如以下方式:

docker run -d \
  -p 9200:9200 \
  -p 9300:9300 \
  ehazlett/elasticsearch \
  --cluster.name=unicast \
  --network.publish_host=192.168.1.10 \
  --discovery.zen.ping.multicast.enabled=false \
  --discovery.zen.ping.unicast.hosts=192.168.1.20 \
  --discovery.zen.ping.timeout=3s \
  --discovery.zen.minimum_master_nodes=1

扩展阅读:

  1. ES 在Docker 中各种网络情况下配置参见官方 Blog Docker Networking
  2. 如果采用 Docker-Compose 来使用 ES 可以参考 How to set network.publish_host for elasticsearch.yaml file using docker-compose.ymlbitnami/bitnami-docker-elasticsearch
  3. 另外也可以参考一下 Elasticsearch in docker container cluster

总结

  1. 在排查的过程中,ORG 服务虽然不能够正常连接 ES,但是在搜索的时候仍然返回了 200 和空结果,而没有提示连接 ES 报错,错误提示不友好导致了问题排查方向的误导,一直任务是索引建立和使用的问题;错误提示很重要!
  2. ES 默认工作在 sniffing 方式,而驱动库没有任何提示。此外驱动库在 search 使用的 BoolQuery 没有方法打印出来与 http curl 类似 json 方式的语句

发表评论

电子邮件地址不会被公开。 必填项已用*标注