ES 滚动升级与零停机变更
ES 滚动升级与零停机变更
一、滚动升级(Rolling Upgrade)概述
滚动升级是 ES 官方推荐的零停机升级方式。基本思路:一次只停一个节点,升级后启动,等集群恢复 Green,再升级下一个。
节点 1 → 暂停分配 → 停服 → 升级 → 启动 → 恢复 → 等 Green → 节点 2 → ...
二、完整滚动升级流程
Step 0:升级前检查
# 检查集群健康状态(必须是 Green)
GET /_cluster/health
# 检查 deprecation API,找出待升级的配置
GET /_migration/deprecations
# 查看索引版本兼容性(ES 7.x 检查 6.x 索引是否需要 reindex)
GET /_migration/system_features?pretty
GET /_migration/deprecations 返回示例:
{
"cluster_settings": [
{
"level": "warning",
"message": "The [cluster.routing.allocation.disk.watermark.low] setting...",
"url": "https://..."
}
],
"index_settings": {
"my_index": [
{
"level": "critical",
"message": "Index created in version 6.x should be reindexed in 7.x"
}
]
}
}
必须处理所有 critical 级别的 deprecation,否则升级后可能出现功能异常。
Step 1:暂停分片分配
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.enable": "primaries"
}
}
这个设置的含义:
"all":允许所有分片分配(默认值)"primaries":仅允许主分片分配"new_primaries":仅允许新索引的主分片分配"none":禁止所有分片分配
用 "primaries" 而不是 "none" 的好处:主分片仍然可以被分配,避免集群变 Red(如果主分片丢失);但不分配副本分片,避免节点重启引发大量不必要的分片搬迁。
Step 2:执行同步刷新(可选但推荐)
POST /_flush/synced
同步刷写将索引刷到磁盘,并在各分片上写入相同的 sync_id。节点重启后如果 sync_id 匹配,可以跳过昂贵的 segment 文件复制阶段,大幅加快恢复速度。
注意:如果集群中有正在写入的索引,同步刷新可能失败(这是正常的,不影响升级)。
Step 3:停止目标节点
# 方式一:systemd
sudo systemctl stop elasticsearch
# 方式二:直接 kill(SIGTERM 使 ES 优雅退出)
kill -SIGTERM <pid>
Step 4:升级 ES
# 小版本升级(同大版本,如 7.16.2 → 7.17.0)
# 直接替换二进制文件即可
# 大版本升级需要通过包管理器或手动替换
# RPM:
sudo rpm -Uvh elasticsearch-7.17.0-x86_64.rpm
# 或解压 tar.gz 到新目录,保留旧目录用于快速回滚
Step 5:启动升级后的节点
sudo systemctl start elasticsearch
# 确认节点加入集群
GET /_cat/nodes?v
Step 6:恢复分片分配
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.enable": null
}
}
设置为 null 会恢复默认值("all")。
Step 7:等待集群变 Green
GET /_cluster/health?wait_for_status=green&timeout=5m
wait_for_status=green 会使请求阻塞直到集群恢复 Green,或超时 5 分钟。
Step 8:重复 Step 1-7 升级下一个节点
三、完整脚本化流程
以下是一个升级单个节点的脚本模板(适合自动化):
#!/bin/bash
ES_HOST="localhost:9200"
NODE_NAME="node-2"
echo "=== Step 1: Disable shard allocation ==="
curl -s -X PUT "${ES_HOST}/_cluster/settings" -H 'Content-Type: application/json' -d '{
"persistent": { "cluster.routing.allocation.enable": "primaries" }
}'
echo "=== Step 2: Synced flush ==="
curl -s -X POST "${ES_HOST}/_flush/synced"
echo "=== Step 3: Stop node ==="
ssh ${NODE_NAME} "sudo systemctl stop elasticsearch"
sleep 10
echo "=== Step 4: Upgrade ==="
ssh ${NODE_NAME} "sudo rpm -Uvh /path/to/elasticsearch-7.17.0.rpm"
echo "=== Step 5: Start node ==="
ssh ${NODE_NAME} "sudo systemctl start elasticsearch"
sleep 30
echo "=== Step 6: Re-enable allocation ==="
curl -s -X PUT "${ES_HOST}/_cluster/settings" -H 'Content-Type: application/json' -d '{
"persistent": { "cluster.routing.allocation.enable": null }
}'
echo "=== Step 7: Wait for green ==="
curl -s "${ES_HOST}/_cluster/health?wait_for_status=green&timeout=10m"
echo "=== Node ${NODE_NAME} upgrade complete ==="
四、主版本升级的特殊注意事项
4.1 不能跳版本升级
| 当前版本 | 目标版本 | 可行? | 升级路径 |
|---|---|---|---|
| 5.6.x | 7.17.x | 否 | 5.6 → 6.8 → 7.17 |
| 6.8.x | 7.17.x | 是(从最后一个 6.x) | 6.8 → 7.17 |
| 7.17.x | 8.x | 是 | 7.17 → 8.x |
原因:ES 每个大版本只确保能从上一个主要版本的最后一个小版本升级。跳版本会跳过必要的数据格式迁移步骤。
4.2 跨版本升级需要 reindex
从 6.x 升级到 7.x 后,在 6.x 中创建的索引仍然可以读写,但无法使用 7.x 的新特性(如新的 mapper 行为)。建议在升级完成后对关键索引进行 reindex:
POST _reindex
{
"source": { "index": "orders_6x" },
"dest": { "index": "orders_7x" }
}
4.3 REST API 兼容性变化
不同大版本的 REST API 可能有 breaking change。升级前务必阅读对应版本的 Breaking Changes 文档。常见变化包括:
- 类型(
_type)在 7.x 逐步废弃,8.x 完全移除。 - mapping 参数名称变化(如
string→text/keyword)。 - 聚合和查询语法的参数调整。
五、回滚策略
5.1 小版本升级的回滚
小版本(同一个大版本内)通常可以降级:停止升级后的节点,替换回旧版本二进制,重启即可。
5.2 大版本升级的回滚
大版本升级后不能直接降级,因为磁盘上的数据结构可能已被新版本修改。回滚的唯一途径是从快照恢复:
# 恢复升级前的快照到一个新的集群
POST /_snapshot/s3_backup_repo/pre_upgrade_snapshot/_restore
# 或者:关闭所有索引,从快照恢复
POST /my_index/_close
POST /_snapshot/s3_backup_repo/pre_upgrade_snapshot/_restore
{
"indices": "my_index",
"include_global_state": true
}
升级前一定要做快照! 这是回滚的最后保障。
六、小版本升级的安全窗口
| 版本类型 | 举例 | 风险 | 建议 |
|---|---|---|---|
| Patch 版本 | 7.17.1 → 7.17.3 | 极低——仅修复 bug,不改数据结构 | 看到更新就升,快速跟随 |
| Minor 版本 | 7.16 → 7.17 | 低——可能有新功能,但向后兼容 | 跟踪 release note,确认无 breaking change 再升 |
| Major 版本 | 7.17 → 8.x | 高——有 breaking change,需充分测试 | 先在测试环境验证,准备快照和回滚方案 |
七、踩坑案例
案例 1:升级后 shard 无法分配
某团队将 3 节点集群从 6.8 升级到 7.16,升级后发现部分副本分片原因为 existing store is corrupt 无法分配。原因是第一个节点升级后,旧版本的节点无法识别新版本写入的分片元数据。正确做法:必须在 Step 1 禁用副本分配(设置为 primaries),等所有节点升级完再一次性恢复。
案例 2:跳版本升级导致无法启动
某团队试图从 5.4 直接升级到 7.10,节点启动失败,报 cannot upgrade from 5.4 to 7.10。最后只能先把数据通过 snapshot 导出,在新集群重建索引。
教训:
- 一定要先做快照。
- 遵守官方升级路径,不跳版本。
- 滚动升级过程中必须暂停副本分片分配。
- 先在非生产环境走一遍完整流程。