ES集群分片打满导致Red与日志链路中断排障

ES集群分片打满导致Red与日志链路中断排障

记录时间:2026-10-17(2026-10-19 更新 4.4 告警表达式)
环境:dyck 生产 ELK(RKE1,3 节点 Elasticsearch 7.17.9,tools 命名空间,NFS 数据盘 nfs-dyck,Logstash → Kafka 日志链路)

一、问题现象

2026-10-17 上午连续收到两条告警,外加 Logstash 日志刷屏:

  • 08:02:30 ESClusterRed(critical):dyck-elk-cluster 存在未分配主分片,读写不可用
  • 09:17:00 Kafka 消费者组无成员(warning):消费者组 logstash 无活跃成员,Topic 积压
  • Logstash 持续重试写入,报 unavailable_shards_exception:
[kafka-gzeport-openapi-cms-2026.10.17][0] primary shard is not active Timeout: [1m],
request: [BulkShardRequest ... containing [53] requests]

我看时间线就对上了:ES Red 后 18 秒 Logstash 开始报错,Kafka 告警滞后约 75 分钟。这两条告警是一条因果链。

二、排查过程

2.1 先排节点与磁盘

我的第一反应是节点掉了。查下来:

kubectl -n tools get sts dyck-elasticsearch   # 3/3
kubectl -n tools get pods -o wide | grep elastic

三个 Pod 全是 Running,183 天零重启,events 也没有异常。接着怀疑磁盘水位触发 flood stage:

kubectl -n tools exec dyck-elasticsearch-0 -- df -h /usr/share/elasticsearch/data

NFS 卷 6.9T 只用了 814G(12%),这条也排除。节点和磁盘都不是问题。

2.2 用 allocation explain 定位

带上 elastic 账号查集群健康:3 节点、active_shards 6000、unassigned_shards 6,状态 red。然后查分配失败原因:

kubectl -n tools exec dyck-elasticsearch-0 -- curl -s -u elastic:<password> \
  'http://localhost:9200/_cluster/allocation/explain?pretty'

三个节点的 decider 输出完全一样:

"decider": "shards_limit",
"explanation": "too many shards [2000] allocated to this node,
                cluster setting [cluster.routing.allocation.total_shards_per_node=2000]"

每个节点都顶满了 2000 片,新索引的主分片没有地方可放。

2.3 顺带排掉的两个假设

  • 我一开始按仓库文档用了 logging 命名空间,命令全部返回 404,实际生产在 tools。这个差异已记入仓库文档的待更正项。
  • 日志里同时段有 lettuce Reconnecting to 10.202.17.52:6379(Redis 重连),我怀疑过是不是基础设施级故障。后来确认 Redis 侧没有对应告警,ES 节点也没有掉线,两者没有关联。

三、根因分析

日志链路是 31 个 Kafka topic,每个服务每天建一个日索引(kafka-<service>-YYYY.MM.DD),1 主 1 副。日索引只建不删,攒了大约 3 到 4 个月:

约 3000 个日索引 × 2 分片(主+副)= 6000 分片
3 节点 × 2000 片/节点上限 = 6000

数字严丝合缝。ES 的日索引按 UTC 日期命名,北京 08:00 正好是 UTC 00:00 滚动点,新索引在这个时刻创建,主分片分配失败,集群转 Red,Logstash 写不进去,输出端反压导致消费停摆。Kafka 的积压是次生现象,Kafka 本身没有问题。

total_shards_per_node=2000 是人为设置的(默认 -1 无限制),说明当初有防分片爆炸的意识,但没有配套的清理策略,缺一个长期清理机制。

四、解决方案

4.1 临时抬高上限

先把上限从 2000 提到 3000,让 6 个未分配分片能落地,恢复写入。注意用 persistent 固化;如果之前用过 transient,要先清掉(transient 优先级更高,会覆盖 persistent)。

参数 值 说明
cluster.routing.allocation.total_shards_per_node 2000 → 3000 临时生效,清理完成后可调回 2000
kubectl -n tools exec dyck-elasticsearch-0 -- curl -s -u elastic:<password> -X PUT \
  'http://localhost:9200/_cluster/settings' -H 'Content-Type: application/json' \
  -d '{"persistent":{"cluster.routing.allocation.total_shards_per_node":3000}}'

4.2 定时清理过期索引

新建 CronJob delete-kafka-es-index-job(tools),每天 08:08 删除 90 天前的日索引。参考了仓库里 v1 的 es-delete-job.yaml,主要改了两处:服务地址换成 v3 集群的 dyck-elasticsearch:9200;删除范围从”正好 N 天前那一天”改成”所有早于截止日的”,否则历史存量永远清不掉。

镜像用 reg-hub.gzeport.com/library/curl-jq:latest;截止日用 date -u 按 UTC 计算,和索引命名对齐。tools 命名空间有 ResourceQuota,Pod 必须显式写 cpu/memory 的 requests 与 limits。

4.3 索引规划收敛

Logstash 输出从”每服务一个索引”改成多服务合并的日索引:

# 改前
index => "kafka-%{[service]}-%{+YYYY.MM.dd}"
# 改后
index => "kafka-logs-%{+YYYY.MM.dd}"

service 字段还在文档里,Kibana 用字段过滤。合并后分片数从约 3420 降到约 180。

4.4 分片数预警

在 vmrule-elasticsearch.yaml 的 elasticsearch.shards 组加了一条规则。第一版用的是 elasticsearch_cluster_health_number_of_shards,上线后发现 elasticsearch_exporter 根本没有这个指标(规则能加载但永远查不到数据),改成 active_shards 口径,同时把”每节点多少片”改成”分片使用率”表达,以后调上限只改一个百分比:

参数 值 说明
表达式 (elasticsearch_cluster_health_active_shards / elasticsearch_cluster_health_number_of_data_nodes) / 2000 * 100 > 95 分片使用率 > 95%(2000 片/数据节点为上限);exporter 无 number_of_shards 指标
for 15m 与同组规则一致
severity warning 提前预警,不是故障告警

分母里的 2000 是 total_shards_per_node 的集群配置,exporter 不暴露配置值,只能写成常量,调整上限时同步改。告警 summary 显示使用率百分比,不再显示每节点片数。

五、参考资料

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇