问题背景
公司电商平台使用 Elasticsearch 7.17 集群支撑商品搜索、订单检索和日志分析。集群由 5 个节点组成:3 个 Master 节点(兼 Data)和 2 个纯 Data 节点。索引策略为 3 主分片 + 1 副本,日均写入约 80GB 日志和 5 万条商品变更,总数据量约 2TB。
故障现象
下午 2:00,业务方反馈商品搜索返回空结果,App 端搜索页白屏。运维紧急登录 Kibana 检查:
1
2
3
4
5
6
7
8
9
|
GET _cluster/health
{
"cluster_name": "es-cluster-prod",
"status": "red",
"timed_out": false,
"number_of_nodes": 5,
"unassigned_shards": 17,
"active_shards": 43
}
|
集群状态 Red,17 个分片未分配。查看未分配分片详情:
1
|
GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason,node
|
大量主分片状态为 UNASSIGNED,原因都是 ALLOCATION_FAILED。
排查过程
第一步:检查节点状态
1
|
GET _cat/nodes?v&h=name,disk.used_percent,heap.percent,ram.percent,master
|
1
2
3
4
5
|
node-01 94.2% 78% 62% *
node-02 96.1% 72% 55% -
node-03 67.3% 65% 48% -
node-04 91.8% 70% 52% -
node-05 95.5% 75% 58% -
|
node-02 磁盘使用率 96.1%,node-05 达 95.5%,已触发 ES 的磁盘水位线保护。
第二步:理解 ES 磁盘保护机制
ES 的磁盘分配策略:
1
2
3
|
cluster.routing.allocation.disk.watermark.low: 85% # 不往该节点分配新分片
cluster.routing.allocation.disk.watermark.high: 90% # 从该节点迁出已有分片
cluster.routing.allocation.disk.watermark.flood_stage: 95% # 强制只读索引
|
node-02 和 node-05 超过 95% flood_stage,ES 自动将这两个节点上的索引设为 read_only_allow_delete,并拒绝在这些节点上分配分片。
1
|
GET _cat/indices?v&h=index,health,status,pri,rep
|
products 索引的 3 个主分片恰好全部落在 node-02 和 node-05 上——副本分片在其他节点上但因为主分片不可分配,副本无法提升为主。
第三步:磁盘空间占用分析
1
2
3
4
5
|
$ du -sh /data/elasticsearch/nodes/0/indices/* | sort -rh | head -10
480G /data/elasticsearch/nodes/0/indices/logs-2022.12
320G /data/elasticsearch/nodes/0/indices/logs-2022.11
195G /data/elasticsearch/nodes/0/indices/logs-2022.10
...
|
日志索引占用了绝大部分磁盘空间。ILM(Index Lifecycle Management)策略配置为保留 90 天日志,但实际检查发现策略的 delete 阶段未生效——原因是指定了不存在的快照仓库名称,导致 ILM 在 delete 阶段卡住,日志无限堆积。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
GET _ilm/policy/logs-policy
{
"logs-policy": {
"phases": {
"delete": {
"actions": {
"delete": {
"delete_searchable_snapshot": true // 引用了不存在的快照仓库
}
}
}
}
}
}
|
解决方案
紧急恢复
1
2
3
4
5
6
7
8
9
10
11
|
# 1. 临时提升磁盘水位线,允许分片分配
PUT _cluster/settings
{
"transient": {
"cluster.routing.allocation.disk.watermark.flood_stage": "98%"
}
}
# 2. 手动删除过期日志索引
DELETE logs-2022.09*
DELETE logs-2022.10*
|
释放出约 600GB 空间后,分片自动开始重新分配,集群在 15 分钟内从 Red → Yellow,30 分钟后恢复 Green。
修复 ILM 策略
1
2
3
4
5
6
7
8
9
|
PUT _ilm/policy/logs-policy
{
"policy": {
"phases": {
"hot": { "min_age": "0ms", "actions": { "rollover": { "max_size": "50GB" } } },
"delete": { "min_age": "30d", "actions": { "delete": {} } }
}
}
}
|
去掉对不存在快照仓库的引用,改用纯 delete 操作。
调整磁盘水位线
1
2
3
4
5
6
7
8
|
PUT _cluster/settings
{
"persistent": {
"cluster.routing.allocation.disk.watermark.low": "80%",
"cluster.routing.allocation.disk.watermark.high": "85%",
"cluster.routing.allocation.disk.watermark.flood_stage": "90%"
}
}
|
将默认的 85/90/95 收紧至 80/85/90,提早触发分片迁移。
根因分析
直接原因:日志索引因 ILM 策略配置错误无限堆积,导致两台 Data 节点磁盘使用率超 95%,触发 ES flood_stage 保护。恰巧关键索引 products 的主分片全落在这两台节点上,主分片被锁定无法分配,集群变 Red。
深层原因:ILM 策略上线时未经过实际验证(引用了开发环境的快照仓库名称),且日志索引的磁盘占用缺乏独立监控。
预防措施
- 磁盘监控:每个 ES 节点磁盘使用率独立监控,>80% 告警
- ILM 策略验证:变更 ILM 策略后通过
GET _ilm/explain 验证策略实际执行状态
- 日志索引独立存储:将日志数据和业务数据放在不同的磁盘/节点上
- 分片分配感知:确保关键索引的主分片分散在不同节点上(使用
index.routing.allocation 规则)
- 定期巡检:每月检查 ILM 策略执行情况和索引生命周期状态
总结
Elasticsearch 的磁盘水位线保护是合理的——磁盘满了集群直接不可用。问题是当保护机制被触发后,主分片不可分配会导致索引直接 Red。这次事故的教训是:ILM 策略不是"配置了就万事大吉",必须定期验证它确实在删除过期数据。同时,核心业务索引应该分散主分片节点分布,避免单点灾难。