现象
现场的 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))
}
运行以上命令得到以下信息:
- 通过
elastic.v5
驱动连接 ES 有具体报错:“no Elasticsearch node available” - 通过 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_host
或 network.host
来解决。ES 中具体配置参见 http章节配置
network.publish_host
设置允许控制节点将在集群内发布的主机,以便其他节点能够连接到它。 当然,这不能是任何本地地址,默认情况下,它将是第一个非环回地址(如果可能)或本地地址。
network.host
设置是一个简单的设置,可以自动将network.bind_host
和network.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
扩展阅读:
- ES 在Docker 中各种网络情况下配置参见官方 Blog Docker Networking
- 如果采用 Docker-Compose 来使用 ES 可以参考 How to set network.publish_host for elasticsearch.yaml file using docker-compose.yml 和 bitnami/bitnami-docker-elasticsearch
- 另外也可以参考一下 Elasticsearch in docker container cluster
总结
- 在排查的过程中,ORG 服务虽然不能够正常连接 ES,但是在搜索的时候仍然返回了 200 和空结果,而没有提示连接 ES 报错,错误提示不友好导致了问题排查方向的误导,一直任务是索引建立和使用的问题;错误提示很重要!
- ES 默认工作在 sniffing 方式,而驱动库没有任何提示。此外驱动库在 search 使用的 BoolQuery 没有方法打印出来与 http curl 类似 json 方式的语句
感谢,我就是没加http://