ES 集群变 Red,搜索服务怎么就不可用了?

问题背景

公司电商平台使用 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 策略上线时未经过实际验证(引用了开发环境的快照仓库名称),且日志索引的磁盘占用缺乏独立监控。

预防措施

  1. 磁盘监控:每个 ES 节点磁盘使用率独立监控,>80% 告警
  2. ILM 策略验证:变更 ILM 策略后通过 GET _ilm/explain 验证策略实际执行状态
  3. 日志索引独立存储:将日志数据和业务数据放在不同的磁盘/节点上
  4. 分片分配感知:确保关键索引的主分片分散在不同节点上(使用 index.routing.allocation 规则)
  5. 定期巡检:每月检查 ILM 策略执行情况和索引生命周期状态

总结

Elasticsearch 的磁盘水位线保护是合理的——磁盘满了集群直接不可用。问题是当保护机制被触发后,主分片不可分配会导致索引直接 Red。这次事故的教训是:ILM 策略不是"配置了就万事大吉",必须定期验证它确实在删除过期数据。同时,核心业务索引应该分散主分片节点分布,避免单点灾难。

使用 Hugo 构建
主题 StackJimmy 设计