ES单节点扩容到三节点集群
将已有数据的单节点 ES 扩展为三节点集群,数据不会丢失,操作风险可控,但必须遵循严谨的步骤,核心是先备份、再改配置、后加节点。
一、操作前的关键认知
在开始前,你需要理解三个核心概念,这能帮你避免大部分问题:
-
cluster.name是集群的唯一标识:所有节点必须使用完全相同的cluster.name,否则新节点无法加入,会报handshake failed错误。 -
cluster.initial_master_nodes只在首次引导集群时使用:这是一个“一次性”配置。对于已存在的集群,新加入的节点绝对不要配置这个参数,否则会引发脑裂或集群无法启动。 -
副本分片是数据安全的基石:单节点集群的状态是
yellow,因为没有副本。加入新节点后,ES 会自动分配副本,集群状态会变为green,此时才真正具备故障转移能力。
二、操作步骤
假设原节点 IP 为 192.168.149.135,新节点为 192.168.149.136 和 192.168.149.137。
步骤 1:操作前务必备份现有数据
这是所有操作中最关键的一步。绝对不要直接拷贝 data 目录,这会导致数据损坏、无法恢复。必须使用 ES 官方的 Snapshot(快照) 功能。
1.1 在原节点配置快照仓库路径
编辑原节点的 elasticsearch.yml,添加:
path.repo: ["/usr/share/elasticsearch/backup"]
修改 docker-compose.yml,给备份目录挂载加 :z:
volumes:
- ./es-backup:/usr/share/elasticsearch/backup:z
修正宿主机目录权限
# 停止集群
docker-compose down
# 修改目录所有者 (UID 1000 是 ES 容器内的用户)
sudo chown -R 1000:1000 /usr/local/es-cluster/es-backup
sudo chmod -R 775 /usr/local/es-cluster/es-backup
# 重新启动
docker-compose up -d
1.2 重启原节点使配置生效
# 如果是 Docker
docker restart <原ES容器名>
# 或如果是系统服务
systemctl restart elasticsearch
1.3 注册快照仓库并创建快照
# 注册文件系统仓库
curl -X PUT "http://192.168.149.135:9200/_snapshot/my_backup" -H 'Content-Type: application/json' -d'
{
"type": "fs",
"settings": {
"location": "/usr/local/es-cluster/backup"
}
}'
# 创建快照(my_snapshot_1 是快照名,可自定义)
curl -X PUT "http://192.168.149.135:9200/_snapshot/my_backup/my_snapshot_1?wait_for_completion=true"
快照创建完成后,即使后续操作失败,你也有完整的备份可以恢复。
步骤 2:修改原节点的集群配置
备份完成后,修改原节点 192.168.149.135 的 elasticsearch.yml:
# 保持原有的 cluster.name 不变
cluster.name: your-existing-cluster-name # 注意:不要改这个值
# 节点名保持不变
node.name: es-node1
# 关键:添加 discovery.seed_hosts,列出所有主节点候选(包括自己和新节点)
discovery.seed_hosts: ["192.168.149.135:9300", "192.168.149.136:9300", "192.168.149.137:9300"]
# 如果你原来的配置里有 cluster.initial_master_nodes,现在必须删除这行
# cluster.initial_master_nodes: ["es-node1"] # 删除或注释掉
# 确保 network.host 和 network.publish_host 配置正确
network.host: 0.0.0.0
network.publish_host: 192.168.149.135
修改后,暂时不要重启原节点,等新节点准备好后一起操作。
步骤 3:准备新节点
在两台新服务器(136、137)上,按照与原节点完全相同的方式安装 ES 7.10.2(Docker 或二进制包均可)。
然后分别创建各自的 elasticsearch.yml:
节点 136 配置:
cluster.name: your-existing-cluster-name # 必须与原节点完全一致
node.name: es-node2
node.roles: [master, data] # 7.x 默认角色,也可不写
network.host: 0.0.0.0
network.publish_host: 192.168.149.136
http.port: 9200
transport.tcp.port: 9300
discovery.seed_hosts: ["192.168.149.135:9300", "192.168.149.136:9300", "192.168.149.137:9300"]
# 注意:新节点不要配置 cluster.initial_master_nodes
节点 137 配置:
cluster.name: your-existing-cluster-name
node.name: es-node3
node.roles: [master, data]
network.host: 0.0.0.0
network.publish_host: 192.168.149.137
http.port: 9200
transport.tcp.port: 9300
discovery.seed_hosts: ["192.168.149.135:9300", "192.168.149.136:9300", "192.168.149.137:9300"]
# 同样不要配置 cluster.initial_master_nodes
关键提醒:
discovery.seed_hosts中列出的地址是主节点候选列表,新节点会通过它来发现集群中的其他节点。所有节点的cluster.name必须严格一致。
步骤 4:依次启动节点
4.1 先重启原节点
# Docker
docker restart es-node1
# 或 systemctl restart elasticsearch
原节点会以单节点模式启动,但配置中已经有了 seed_hosts,它会等待其他节点加入。
4.2 启动第一个新节点(136)
docker start es-node2 # 或 systemctl start elasticsearch
启动后,观察日志:
docker logs -f es-node2
你应该能看到类似 Adding node [es-node2] to cluster 或 joined 的日志。验证节点是否加入:
curl "http://192.168.149.135:9200/_cat/nodes?v"
此时应该看到两个节点 es-node1 和 es-node2。
4.3 确认 136 加入后,再启动第二个新节点(137)
docker start es-node3
再次验证:
curl "http://192.168.149.135:9200/_cat/nodes?v"
应显示三个节点。
步骤 5:调整副本数,让数据自动冗余
集群从 1 节点变为 3 节点后,不会自动增加副本数。你需要手动调整现有索引的副本数。
5.1 查看当前索引的副本设置
curl "http://192.168.149.135:9200/_cat/indices?v"
查看 rep 列,通常单节点时是 0。
5.2 为所有索引设置副本数为 1
curl -X PUT "http://192.168.149.135:9200/_all/_settings" -H 'Content-Type: application/json' -d'
{
"index": {
"number_of_replicas": 1
}
}'
执行后,ES 会自动在各个节点间分配副本分片,这个过程是自动的,不需要你手动干预。
注意:设置副本数为
1意味着数据会有一份完整冗余。如果数据量很大,这个过程可能需要一些时间,会占用网络和磁盘 I/O。
步骤 6:验证集群健康和数据完整性
6.1 检查集群健康状态
curl "http://192.168.149.135:9200/_cluster/health?pretty"
等待一段时间后,status 应该变为 green,number_of_nodes 为 3,number_of_data_nodes 为 3。
6.2 验证分片分布
curl "http://192.168.149.135:9200/_cat/shards?v&h=index,shard,prirep,state,docs,store,node"
你应该能看到每个索引的主分片和副本分片均匀分布在三个节点上。
6.3 验证数据完整性
# 查看所有索引的文档总数
curl "http://192.168.149.135:9200/_cat/indices?v&h=index,docs.count"
# 或者随机查询几条数据
curl -X GET "http://192.168.149.135:9200/your_index/_search?size=5"
确保文档数量与操作前一致,并且能正常搜索到数据。
步骤 7:更新 Nginx 负载均衡配置
集群扩展完成后,需要更新你现有的 Nginx 配置,将新节点加入 upstream:
upstream es_cluster {
server 192.168.149.135:9200;
server 192.168.149.136:9200; # 新增
server 192.168.149.137:9200; # 新增
}
然后重载 Nginx:
nginx -t && nginx -s reload
三、常见问题与排查
Q1:新节点启动后无法加入集群
最常见的原因是 cluster.name 不一致,或原节点的 network.publish_host 配置错误(不能是 127.0.0.1)。查看新节点日志中的错误信息,通常会明确指出原因。
Q2:集群健康状态一直为 yellow
可能原因:
-
副本数设置后,分片正在分配中,需要等待
-
磁盘空间不足(ES 默认磁盘使用率超过 85% 会停止分配分片)
-
检查
curl "http://IP:9200/_cat/allocation?v"看磁盘情况
Q3:操作后数据不一致或丢失
如果严格按照“先快照备份 → 再修改配置 → 后加入节点”的顺序操作,数据不会丢失。如果出现异常,可以使用快照恢复:
# 先关闭索引写入(可选,防止恢复期间数据变化)
curl -X POST "http://IP:9200/your_index/_close"
# 恢复快照
curl -X POST "http://IP:9200/_snapshot/my_backup/my_snapshot_1/_restore"
# 恢复后重新打开索引
curl -X POST "http://IP:9200/your_index/_open"
Q4:原节点重启后,新节点还没启动,集群状态异常
这是正常的。原节点重启后,cluster.initial_master_nodes 已删除,它会在 discovery.seed_hosts 中寻找其他节点。如果其他节点未启动,它会等待。只要在合理时间内启动新节点,集群就能正常形成。
四、总结:核心操作顺序
① 快照备份原节点数据(必做)
↓
② 修改原节点配置:加 discovery.seed_hosts,删 cluster.initial_master_nodes
↓
③ 准备两台新节点,配置 cluster.name 与原节点一致,不配 initial_master_nodes
↓
④ 依次重启原节点 → 启动新节点1 → 验证 → 启动新节点2
↓
⑤ 设置索引副本数为 1,让 ES 自动分配副本
↓
⑥ 验证集群状态 green、节点数 3、数据完整
↓
⑦ 更新 Nginx upstream,加入两个新节点
只要第一步的快照备份做到位,后续操作即使出现问题,你也可以随时从快照恢复,数据安全有充分保障。
快照恢复时,目标索引名必须不存在。 你集群里已经有一个叫 test-index 的索引了,所以 ES 拒绝恢复。
有 三种处理方式,根据你的需求选一种。
方式一:删除现有索引后恢复(最常用)
如果你确认现有 test-index 的数据不重要,或者就是要用快照覆盖它:
# 1. 删除现有索引
curl -X DELETE "http://192.168.149.135:9200/test-index"
# 2. 确认已删除
curl "http://192.168.149.135:9200/_cat/indices?v" | grep test-index
# 3. 恢复快照
curl -X POST "http://192.168.149.135:9200/_snapshot/my_backup/my_snapshot_1/_restore?wait_for_completion=true" -H 'Content-Type: application/json' -d'
{
"indices": "test-index",
"ignore_unavailable": true,
"include_global_state": false
}'
方式二:关闭现有索引后恢复(保留数据,可回滚)
如果你不想删除现有数据,可以先关闭索引,恢复完再决定:
# 1. 关闭现有索引(数据保留在磁盘,只是不可读写)
curl -X POST "http://192.168.149.135:9200/test-index/_close"
# 2. 恢复快照
curl -X POST "http://192.168.149.135:9200/_snapshot/my_backup/my_snapshot_1/_restore?wait_for_completion=true" -H 'Content-Type: application/json' -d'
{
"indices": "test-index",
"include_global_state": false
}'
# 3. 恢复完成后打开索引
curl -X POST "http://192.168.149.135:9200/test-index/_open"
注意:关闭索引再恢复,同名的关闭索引会被覆盖。如果想保留原数据,用方式三。
方式三:恢复到不同名称(最安全,推荐)
用 rename_pattern 和 rename_replacement 把恢复的索引改名,这样原索引完全不动:
curl -X POST "http://192.168.149.135:9200/_snapshot/my_backup/my_snapshot_1/_restore?wait_for_completion=true" -H 'Content-Type: application/json' -d'
{
"indices": "test-index",
"ignore_unavailable": true,
"include_global_state": false,
"rename_pattern": "(.+)",
"rename_replacement": "restored_$1"
}'
参数解释:
-
rename_pattern: "(.+)":匹配原索引名(捕获整个名字) -
rename_replacement: "restored_$1":$1是捕获组,最终得到restored_test-index
恢复后你会看到:
curl "http://192.168.149.135:9200/_cat/indices?v"
# 输出:
# test-index ← 原有的
# restored_test-index ← 从快照恢复的
这样你可以先对比两个索引的数据,确认无误后再删除旧索引,把新索引改名回去。
恢复前先查看快照里有哪些索引
为了避免盲操作,先看看快照里包含了什么:
# 查看快照详情
curl "http://192.168.149.135:9200/_snapshot/my_backup/my_snapshot_1?pretty"
# 查看快照中包含的索引
curl "http://192.168.149.135:9200/_snapshot/my_backup/my_snapshot_1/_status?pretty"
# 或列出快照中的所有索引
curl -X GET "http://192.168.149.135:9200/_snapshot/my_backup/my_snapshot_1" | grep -o '"indices":\[[^]]*\]'
常用恢复参数速查
| 参数 | 作用 |
|---|---|
indices |
指定恢复哪些索引,支持通配符 test-* |
ignore_unavailable |
快照中不存在的索引是否忽略 |
include_global_state |
是否恢复集群全局状态(模板、ILM 等),一般设 false |
rename_pattern |
重命名匹配正则 |
rename_replacement |
重命名替换规则 |
wait_for_completion |
是否等待恢复完成再返回 |
partial |
是否允许恢复不完整的分片 |
完整恢复流程示例
假设你要把快照里的 test-index 恢复到当前集群,但当前集群已有同名索引,推荐这样操作:
# ===== 1. 先看快照里到底有什么 =====
curl "http://192.168.149.135:9200/_snapshot/my_backup/my_snapshot_1?pretty" | grep -E '"indices"|"state"'
# ===== 2. 看当前集群有哪些索引 =====
curl "http://192.168.149.135:9200/_cat/indices?v"
# ===== 3. 用改名方式恢复(原数据不动)=====
curl -X POST "http://192.168.149.135:9200/_snapshot/my_backup/my_snapshot_1/_restore?wait_for_completion=true" \
-H 'Content-Type: application/json' -d'
{
"indices": "test-index",
"ignore_unavailable": true,
"include_global_state": false,
"rename_pattern": "(.+)",
"rename_replacement": "restored_$1"
}'
# ===== 4. 对比两个索引的文档数 =====
curl "http://192.168.149.135:9200/test-index/_count?pretty"
curl "http://192.168.149.135:9200/restored_test-index/_count?pretty"
# ===== 5. 确认恢复的数据没问题后,替换原索引 =====
# 删掉旧的
curl -X DELETE "http://192.168.149.135:9200/test-index"
# 把恢复出来的改名回去
curl -X POST "http://192.168.149.135:9200/_reindex?pretty" -H 'Content-Type: application/json' -d'
{
"source": { "index": "restored_test-index" },
"dest": { "index": "test-index" }
}'
# 删掉临时索引
curl -X DELETE "http://192.168.149.135:9200/restored_test-index"
恢复过程监控
恢复是异步的,用这个命令看进度:
curl "http://192.168.149.135:9200/_cat/recovery?v&active_only=true"
或看集群健康:
curl "http://192.168.149.135:9200/_cluster/health?pretty"
恢复期间集群状态可能是 yellow,等分片分配完变 green 就完成了。
一句话总结
快照恢复要求目标索引名当前不存在。三种解法:
删掉现有索引再恢复(数据丢失)
关闭现有索引再恢复(覆盖式)
改名恢复(
rename_pattern+rename_replacement,最安全,原数据保留)生产环境推荐方式三,先改名恢复到
restored_xxx,验证无误后再替换。

浙公网安备 33010602011771号