作者:David Pilato
我最近在讨论论坛上收到一个问题,关于如何修改官方 Docker 镜像以提供一个现成的 Elasticsearch 集群,其中已经包含一些数据。
说实话,我不喜欢这个想法,因为你必须通过提 entrypoint.sh 的分叉版本来破解 Elasticsearch 服务的启动方式。 这将使你的维护和升级变得更加困难。
相反,我发现使用其他解决方案来实现相同的目标会更好。
设置问题
首先,我们将考虑使用 Elasticsearch Docker 镜像并遵循文档:
docker pull docker.elastic.co/elasticsearch/elasticsearch:8.7.0
docker network create elastic
docker run --name es01 --net elastic -p 9200:9200 -it docker.elastic.co/elasticsearch/elasticsearch:8.7.0
请注意,我们没有在此处挂载 data 目录,因此该集群的数据将是短暂的,并且一旦节点关闭就会消失。 启动后,我们可以使用生成的密码检查它是否运行良好:
curl -s -k -u elastic:CHANGEME https://localhost:9200 | jq
请使用在安装时候显示的 Elasticsearch 密码来替换上面的 CHANGEME。上面的命令给出:
{
"name": "697bf734a5d5",
"cluster_name": "docker-cluster",
"cluster_uuid": "cMISiT__RSWkoKDYql1g4g",
"version": {
"number": "8.7.0",
"build_flavor": "default",
"build_type": "docker",
"build_hash": "09520b59b6bc1057340b55750186466ea715e30e",
"build_date": "2023-03-27T16:31:09.816451435Z",
"build_snapshot": false,
"lucene_version": "9.5.0",
"minimum_wire_compatibility_version": "7.17.0",
"minimum_index_compatibility_version": "7.0.0"
},
"tagline": "You Know, for Search"
}
因此,我们希望有一个可用的数据集。 让我们采用我在演示 Elasticsearch 时经常使用的示例数据集:人员数据集。 我创建了[一个生成器](https://github.com/dadoonet/injector) 来创建一些假数据。
首先,让我们下载注入器:
wget https://repo1.maven.org/maven2/fr/pilato/elasticsearch/injector/injector/8.7/injector-8.7.jar
然后我们将使用以下选项在磁盘上生成数据集:
mkdir data
java -jar injector-8.7.jar --console --silent > data/persons.json
我们有 1000000 个 json 文档,数据集如下所示:
head -2 data/persons.json
{"name":"Charlene Mickael","dateofbirth":"2000-11-01","gender":"female","children":3,"marketing":{"cars":1236,"shoes":null,"toys":null,"fashion":null,"music":null,"garden":null,"electronic":null,"hifi":1775,"food":null},"address":{"country":"Italy","zipcode":"80100","city":"Ischia","countrycode":"IT","location":{"lon":13.935138341699972,"lat":40.71842684204817}}}
{"name":"Kim Hania","dateofbirth":"1998-05-18","gender":"male","children":4,"marketing":{"cars":null,"shoes":null,"toys":132,"fashion":null,"music":null,"garden":null,"electronic":null,"hifi":null,"food":null},"address":{"country":"Germany","zipcode":"9998","city":"Berlin","countrycode":"DE","location":{"lon":13.164834451298645,"lat":52.604673827377155}}}
使用 shell 脚本
我们这里有 100 万个文档,因此我们无法真正使用批量请求来发送它。 我们需要:
- 分成 10000 个或更少的索引操作
- 对于每个文档,添加缺少的 bulk 标题
- 使用 _bulk API 发送文档 我最终编写了这个脚本,它要求你安装了 curl 和 jq:
#!/bin/bash
ELASTIC_PASSWORD=CHANGEME
mkdir tmp
echo "Split the source in 10000 items"
split -d -l10000 ../data/persons.json tmp/part
BULK_REQUEST_FILE="tmp/bulk_request.ndjson"
FILES="tmp/part*"
for f in $FILES
do
rm $BULK_REQUEST_FILE
echo "Preparing $f file..."
while read p; do
echo -e '{"index":{}}' >> $BULK_REQUEST_FILE
echo -e "$p" >> $BULK_REQUEST_FILE
done <$f
echo "Calling Elasticsearch Bulk API"
curl -XPOST -s -k -u elastic:$ELASTIC_PASSWORD https://localhost:9200/person/_bulk -H 'Content-Type: application/json' --data-binary "@$BULK_REQUEST_FILE" | jq '"Bulk executed in \(.took) ms with errors=\(.errors)"'
done
有关脚本输入文档的方法,你也可以参考文章 “Elasticsearch:如何使用 shell 脚本来写入数据到 Elasticsearch 中”。
这基本上打印:
Preparing tmp/part00 file...
Calling Elasticsearch Bulk API
"Bulk executed in 1673 ms with errors=false"
Preparing tmp/part01 file...
Calling Elasticsearch Bulk API
"Bulk executed in 712 ms with errors=false"
...
Preparing tmp/part99 file...
Calling Elasticsearch Bulk API
"Bulk executed in 366 ms with errors=false"
在我的机器上,运行它需要 8 分钟多。 大部分时间都花在编写批量请求上。 可能还有很大的改进空间,但我必须承认我不太擅长编写 shell 脚本。 哈? 你已经猜到了吗? 😅
使用logstash
Logstash 可以完成与我们手动完成的类似工作,但还提供更多功能,例如错误处理、监控,我们甚至不需要编写代码... 我们将在这里再次使用 Docker:
docker pull docker.elastic.co/logstash/logstash:8.7.0
让我们为此编写一个作业:
input {
file {
path => "/usr/share/logstash/persons/persons.json"
mode => "read"
codec => json { }
exit_after_read => true
}
}
filter {
mutate {
remove_field => [ "log", "@timestamp", "event", "@version" ]
}
}
output {
elasticsearch {
hosts => "${ELASTICSEARCH_URL}"
index => "person"
user => "elastic"
password => "${ELASTIC_PASSWORD}"
ssl_certificate_verification => false
}
}
我们现在可以运行该作业:
docker run --rm -it --name ls01 --net elastic \
-v $(pwd)/../data/:/usr/share/logstash/persons/:ro \
-v $(pwd)/pipeline/:/usr/share/logstash/pipeline/:ro \
-e XPACK_MONITORING_ENABLED=false \
-e ELASTICSEARCH_URL="https://es01:9200" \
-e ELASTIC_PASSWORD="CHANGEME" \
docker.elastic.co/logstash/logstash:8.7.0
在我的机器上,运行它只需要不到 2 分钟。
使用 docker 撰写
你可以更轻松地使用 docker compose 命令来根据需要运行所有内容,并向用户公开一个可供使用的集群,而不是手动运行所有这些内容。 这是一个简单的 .env 文件:
ELASTIC_PASSWORD=CHANGEME
STACK_VERSION=8.7.0
ES_PORT=9200
以及 docker-compose.yml:
version: "2.2"
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
ports:
- ${ES_PORT}:9200
environment:
- node.name=es01
- cluster.initial_master_nodes=es01
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- bootstrap.memory_lock=true
ulimits:
memlock:
soft: -1
hard: -1
healthcheck:
test:
[
"CMD-SHELL",
"curl -s -k https://localhost:9200 | grep -q 'missing authentication credentials'",
]
interval: 10s
timeout: 10s
retries: 120
logstash:
depends_on:
es01:
condition: service_healthy
image: docker.elastic.co/logstash/logstash:${STACK_VERSION}
volumes:
- type: bind
source: ../data
target: /usr/share/logstash/persons
read_only: true
- type: bind
source: pipeline
target: /usr/share/logstash/pipeline
read_only: true
environment:
- ELASTICSEARCH_URL=https://es01:9200
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- XPACK_MONITORING_ENABLED=false
我们仍然在 ../data 目录中保存有 person.json 文件。 它被安装为 /usr/share/logstash/persons/persons.json ,就像在前面的示例中一样。 因此,我们使用与之前相同的 pipeline/persons.conf 文件。 要运行它,我们现在只需输入:
docker compose up
并等待 with-compose-logstash-1 容器退出:
with-compose-logstash-1 | [2023-04-21T15:17:55,335][INFO ][logstash.runner ] Logstash shut down.
with-compose-logstash-1 exited with code 0
这表明我们的服务现在已准备好运行并完全加载了我们的示例数据集。
有关这个部分的内容,更多阅读请参考:数据集成的强大联盟:Elasticsearch、Kibana、Logstash、MySQL。
使用快照/恢复
你还可以使用创建快照 API 将 Elasticsearch 中的现有数据集备份到共享文件系统或 S3,然后使用 Restore Restore API 将其恢复到新集群。 假设你已经注册了一个名为 example 的存储库。 你可以使用以下命令创建快照:
# We force merge the segments first
POST /person/_forcemerge?max_num_segments=1
# Snapshot the data
PUT /_snapshot/sample/persons
{
"indices": "person",
"include_global_state": false
}
因此,无论何时启动新集群,你都可以使用以下命令恢复快照:
POST /_snapshot/sample/persons/_restore
你只需要小心使用此方法,当你将其升级到新的主要版本时,你拥有的快照仍然可以在集群中恢复。 例如,如果你使用版本 6.3 创建了快照,则无法在 8.2 中还原它。 有关更多详细信息,请参阅快照索引兼容性。 但好消息! 借助存档索引,Elasticsearch 现在能够访问较旧的快照存储库(返回到版本 5)... 你只需要了解一些限制即可。 为了保证你的快照始终完全兼容,你可能需要使用相同的脚本使用最新版本再次创建索引快照。 请注意,在这种情况下,Force Merger API 调用很重要,因为它将使用最新的 Elasticsearch/Lucene 版本重写所有段。
使用已挂载的目录
还记得我们启动集群时的情况吗?
docker run --name es01 --net elastic -p 9200:9200 -it docker.elastic.co/elasticsearch/elasticsearch:8.7.0
我们没有绑定挂载 data 和 config 目录。 但实际上我们可以这样做:
docker run --name es01 --net elastic -p 9200:9200 -it -v persons-data:/usr/share/elasticsearch/data -v persons-config:/usr/share/elasticsearch/config docker.elastic.co/elasticsearch/elasticsearch:8.7.0
我们可以检查刚刚创建的 Docker volume:
docker volume inspect persons-data persons-config
[
{
"CreatedAt": "2023-05-09T10:20:14Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/persons-data/_data",
"Name": "persons-data",
"Options": null,
"Scope": "local"
},
{
"CreatedAt": "2023-05-09T10:19:51Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/persons-config/_data",
"Name": "persons-config",
"Options": null,
"Scope": "local"
}
]
如果你想使用完全相同的命令行再次启动 Elasticsearch 节点,你可以稍后再次重用此挂载点。 如果需要与其他用户共享卷,可以将 /var/lib/docker/volumes/persons-config/ 和 /var/lib/docker/volumes/persons-data/ 中的数据备份到 /tmp/volume-backup:
然后你可以与其他用户共享 /tmp/volume-backup/persons.tgz 文件并让他们恢复它。
docker volume create persons-config
docker volume create persons-data
docker run --rm -it -v /tmp/volume-backup:/backup -v /var/lib/docker:/docker alpine:edge tar xfz /backup/persons.tgz -C /
并再次启动容器:
docker run --name es01 --net elastic -p 9200:9200 -it -v persons-data:/usr/share/elasticsearch/data -v persons-config:/usr/share/elasticsearch/config docker.elastic.co/elasticsearch/elasticsearch:8.7.0
使用 Elastic Cloud
当然,你可以使用之前创建的快照来配置新的 Elasticsearch Cloud 实例,而不是自行启动和管理本地 Elasticsearch 实例。 以下代码假设你已经定义了 API key。
POST /api/v1/deployments?validate_only=false
{
"resources": {
"elasticsearch": [
{
"region": "gcp-europe-west1",
"plan": {
"cluster_topology": [
{
"zone_count": 2,
"elasticsearch": {
"node_attributes": {
"data": "hot"
}
},
"instance_configuration_id": "gcp.es.datahot.n2.68x10x45",
"node_roles": [
"master",
"ingest",
"transform",
"data_hot",
"remote_cluster_client",
"data_content"
],
"id": "hot_content",
"size": {
"resource": "memory",
"value": 8192
}
}
],
"elasticsearch": {
"version": "8.7.1"
},
"deployment_template": {
"id": "gcp-storage-optimized-v5"
},
"transient": {
"restore_snapshot": {
"snapshot_name": "__latest_success__",
"source_cluster_id": "CLUSTER_ID"
}
}
},
"ref_id": "main-elasticsearch"
}
],
"kibana": [
{
"elasticsearch_cluster_ref_id": "main-elasticsearch",
"region": "gcp-europe-west1",
"plan": {
"cluster_topology": [
{
"instance_configuration_id": "gcp.kibana.n2.68x32x45",
"zone_count": 1,
"size": {
"resource": "memory",
"value": 1024
}
}
],
"kibana": {
"version": "8.7.1"
}
},
"ref_id": "main-kibana"
}
]
},
"settings": {
"autoscaling_enabled": false
},
"name": "persons",
"metadata": {
"system_owned": false
}
}
只需将 CLUSTER_ID 替换为从中获取快照的源集群即可。 集群启动后,你就拥有了一个功能齐全的实例,可以在互联网上使用你想要的默认数据集。 完成后,你可以使用以下命令轻松关闭部署:
POST /api/v1/deployments/DEPLOYMENT_ID/_shutdown
同样,只需将 DEPLOYMENT_ID 替换为你在创建部署时看到的部署 ID。
结论
与往常一样,对于 Elastic,特别是 Elasticsearch,你有多种方法可以实现你的目标。 我在这里列出了其中一些,但可能还有其他一些方法:
- 使用 Shell 脚本:你实际上不需要任何第三方工具,但这需要编写一些代码。 代码看起来很简单,只要偶尔运行一下就可以了。 如果你需要它更安全,例如具有捕获和重试功能,那么这将使你创建和维护更多代码。
- 使用 Logstash:它非常灵活,因为你还可以将数据发送到 Elasticsearch 之外的其他目的地,或者使用多个过滤器来修改/丰富源数据集。 启动速度有点慢,但出于测试目的,这不应该成为真正的问题。
- 使用 docker compose:我最喜欢的方法之一。 你只需运行 docker compose up 等,几分钟后就完成了。 但这可能需要时间和硬件资源。
- 使用快照/恢复:比以前的方法更快,因为数据已经建立索引。 但灵活性较差,因为快照需要与你要恢复到的集群兼容。 一般来说,我总是更喜欢再次注入数据,因为一切都是新鲜的,并且我可以从 Elasticsearch 和 Lucene 的所有改进中受益。
- 使用挂载目录:类似于快照,但更本地化。 老实说,我更喜欢使用 Elastic API,而不是手动挂载现有目录。 让 Elasticsearch 做它知道的事情让我感觉更安全。
- 使用 Elastic Cloud:我认为这是与其他人(例如客户或内部测试人员)共享数据集的最简单方法。 一切都已准备就绪,安全,可以通过适当的 SSL 证书使用。
根据你的品味和限制,你可以选择其中一种想法并根据你的需求进行调整。 如果你有其他好主意要分享,请在 Twitter 上告诉我们或讨论区。 许多很棒的想法/功能都来自社区。 分享你的!
原文:Preload Elasticsearch with your dataset | Elastic Blog