程序印象

no Elasticsearch node available

2018/07/13 Share

现象

现场的 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 连接测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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 的节点:集群节点的端口组合(较新版本稍微更改了返回值):

1
$ curl -s -XGET 'http://127.0.0.1:9200/_nodes/http?pretty=1'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"_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 ,设置如下。

1
2
3
4
5
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 设置为相同的主机值。

例如以下方式:

1
2
3
4
5
6
7
8
9
10
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 方式的语句
CATALOG
  1. 1. 现象
  2. 2. 排查
  3. 3. 分析
  4. 4. 解决
    1. 4.1. Go ES 驱动解决
    2. 4.2. ES 配置解决
  5. 5. 总结