跳到主要内容

运维与监控

巡检从哪看起​

ES 的运维接口非常全,但日常真正高频用的就那几组。先把「看一眼」的工具列出来,再讲指标怎么读。

集群健康​

# 基础健康
curl -s "http://localhost:9200/_cluster/health?pretty"

# 带上索引级明细,看是哪个索引在拖后腿
curl -s "http://localhost:9200/_cluster/health?level=indices&pretty"

# 等集群恢复到指定状态,最多等 30 秒(适合重启后确认)
curl -s "http://localhost:9200/_cluster/health?wait_for_status=yellow&timeout=30s&pretty"

_cluster/health 返回的字段里,运维要盯的是这几个:status(三色)、unassigned_shards(未分配分片数)、initializing_shards(恢复中的分片数)、relocating_shards(迁移中的分片数)、number_of_pending_tasks(待处理的集群状态变更,持续大于 0 说明 master 忙不过来)。

_cat 系列​

_cat 接口返回纯文本表格,适合终端里快速扫。常用的几条:

命令看什么
_cat/health?v集群三色、节点数、分片数
_cat/nodes?v节点角色、堆使用率、CPU、负载、master 标记
_cat/indices?v&s=store.size:desc索引文档数、大小、健康状态,按大小排序
_cat/shards?v每个分片在哪个节点、状态、大小
_cat/allocation?v每个节点的分片数、磁盘占用、剩余空间
_cat/thread_pool?v各线程池的活跃数、队列、拒绝数
_cat/pending_tasks?v积压的集群任务
_cat/segments?v段数量,判断是否需要 forcemerge
_cat/recovery?v&active_only=true正在恢复的分片及进度
_cat/aliases?v别名指向哪些索引
# 只挑关心的列,输出好读很多
curl -s "http://localhost:9200/_cat/nodes?v&h=name,node.role,heap.percent,ram.percent,cpu,load_1m,disk.avail,master"
curl -s "http://localhost:9200/_cat/indices?v&h=index,health,pri,rep,docs.count,store.size&s=store.size:desc"
把 _cat 当仪表盘用

_cat/nodes 里的 heap.percent 超过 75%、disk.used_percent 超过 85%、load_1m 持续高于 CPU 核数,都是该介入的信号。每天花一分钟扫这三列,比等告警响了再查要主动得多。

关键指标​

JVM 堆使用率​

堆是 ES 最脆弱也最关键的资源。对象(分片元数据、聚合桶、批量请求缓冲、fielddata)都堆在堆里,堆一满就是漫长的 GC,GC 一停就是查询超时。

# 节点级 JVM 指标
curl -s "http://localhost:9200/_nodes/stats/jvm?pretty"

# 只取堆使用率和 GC 次数
curl -s "http://localhost:9200/_nodes/stats/jvm?filter_path=nodes.*.jvm.mem.heap_used_percent,nodes.*.jvm.gc.collectors"

判断标准:

堆使用率状态动作
持续低于 60%健康有余量,可承接更多分片或更大聚合
60%-75%正常偏高关注趋势,检查是否有大聚合、分片过多
75%-85%警戒排查大查询、fielddata、分片数;考虑扩容
持续高于 85%危险随时可能 OOM 或触发熔断,优先降负载
老年代 GC 后仍高于 80%临界说明对象回收不掉,扩容或减少分片,别硬扛

ES 还有一层保护叫熔断器(circuit breaker):堆用到 95% 左右会拒绝新的聚合/批量请求,返回 CircuitBreakingException。它不是故障,是最后的保险——看到这个异常,说明负载已经超了,该扩容或优化查询,而不是调大熔断阈值。

# 看熔断器触发次数
curl -s "http://localhost:9200/_nodes/stats/breaker?pretty"

段合并与段数量​

每个 refresh 都会生成新段,段太多会拖慢查询、占用文件句柄。索引处于「只追加」状态时(比如日志),段数量会在几十到几百之间波动,这是正常的;但一个索引几百个段且不再下降,就该处理了:

# 单个索引的段信息
curl -s "http://localhost:9200/logs-2025.10.25/_segments?pretty"

# 合并统计:当前合并数、累计合并耗时
curl -s "http://localhost:9200/_stats/merge?pretty"

对只读的历史索引,可以做一次 forcemerge 把段合并到 1 个,查询会明显变快、磁盘占用也会下降:

POST /logs-2025.09.01/_forcemerge?max_num_segments=1
forcemerge 别对写入中的索引做

forcemerge 会重写整个索引,消耗大量磁盘 IO 和 CPU,几 TB 的索引能跑几个小时。只对确定不再写入的历史索引做,并且安排在低峰期。对活跃索引做 forcemerge,等于和业务写入抢资源,还会马上被新的 refresh 打回原形。

搜索与索引延迟​

# 索引级:查询耗时、写入耗时、refresh 次数
curl -s "http://localhost:9200/_stats/search,indexing,refresh?pretty"

# 线程池:队列积压和拒绝数,最能反映「扛不扛得住」
curl -s "http://localhost:9200/_cat/thread_pool?v&h=node_name,name,active,queue,rejected,completed"

几个判断口径:

  • search.query_time_in_millis 除以查询次数得到平均查询耗时,但它会把所有查询平均掉,长尾问题看不出来。真正有用的是看趋势:同一业务在数据量增长后平均耗时是否同步上涨。
  • indexing.index_time_in_millis 反映写入开销。写入慢通常不是 ES 慢,而是 refresh 太频繁、文档太大、或者 merge 在抢 IO。
  • thread_pool.write.queue 持续大于 0,说明写入速度超过了处理能力;rejected 开始增长,就是明确的过载信号,客户端会收到 429。
  • thread_pool.search.queue 积压,通常是某个大聚合或深分页把协调节点拖住了。

磁盘水位​

磁盘是 ES 最容易出硬故障的地方:写满不仅写不进,还会让分片无法分配、集群变红。ES 用三级水位线自动保护:

水位默认阈值行为
low(低水位)85%不再向该节点分配新分片
high(高水位)90%更积极地迁走分片,仍可写入
flood_stage(洪水位)95%该节点上所有索引被设为只读(read_only_allow_delete),写入直接失败
# 每个节点磁盘剩余
curl -s "http://localhost:9200/_cat/allocation?v"

# 看当前水位配置
curl -s "http://localhost:9200/_cluster/settings?include_defaults=true&flat_settings=true&pretty" | grep -i watermark

水位线可以按集群调(比如大磁盘机器上适当放宽),但不要为了消警把阈值调到 99%——那只是把「写不进去」的时间点往后推,代价是恢复时更被动。正确做法是清理旧索引、扩容磁盘、或者用 ILM 自动删除过期数据。磁盘只读的解除方法在第 6 篇讲。

其他值得盯的指标​

指标位置异常含义
number_of_pending_tasks_cluster/health持续大于 0,master 处理不过来
unassigned_shards_cluster/health大于 0 且不下降,分片分配有问题
jvm.gc.collectors.old.collection_time_nodes/stats/jvm老年代 GC 时间占比高,堆不够
fs.total.available_in_bytes_nodes/stats/fs剩余空间,配合水位线看
process.cpu.percent_nodes/stats/process长期接近 100%,CPU 瓶颈
indices.fielddata.memory_size_in_bytes_nodes/stats/indices异常增长,通常是给 text 字段开了 fielddata
thread_pool.write.rejected_cat/thread_pool写入过载,客户端收到 429

ILM:索引生命周期管理​

日志、监控这类数据有明确的生命周期:刚写入时被频繁查询(热),几天后只做偶尔排查(温),一个月后基本不看但合规要求留着(冷),三个月后删除。手工管理这套流程既繁琐又容易出错,ILM 就是干这个的。

ILM 把索引的一生分成四个阶段:

阶段典型动作目的
hotrollover(按大小/时间滚动)、设置高优先级承接写入,保证写入性能
warmforcemerge、降低优先级索引不再写入,合并段提升查询效率
coldsearchable_snapshot(转可搜索快照)大幅降低存储成本,仍可查询
deletedelete到期清理,释放空间

一份完整的热温冷归档策略​

PUT /_ilm/policy/logs-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "50gb",
"max_age": "30d"
},
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "7d",
"actions": {
"forcemerge": { "max_num_segments": 1 },
"set_priority": { "priority": 50 }
}
},
"cold": {
"min_age": "30d",
"actions": {
"searchable_snapshot": {
"snapshot_repository": "fs_backup"
},
"set_priority": { "priority": 0 }
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}

含义逐段读:

  • hot:索引一创建就进入 hot 阶段。单个主分片达到 50GB、或索引满 30 天就 rollover 出新索引,老索引留在 hot 继续服务查询。set_priority 让 hot 索引在恢复时优先分配。
  • warm:rollover 后 7 天进入 warm。此时索引已不再写入,forcemerge 成 1 个段,查询更快、段更少。如果你的节点分了 data_warm 角色,还需要在索引设置里指定 _tier_preference,让分片迁到 warm 层。
  • cold:rollover 后 30 天进入 cold。searchable_snapshot 把索引转成可搜索快照,数据搬到快照仓库、本地只留少量元数据,存储成本能降一个数量级,查询仍然可用(只是慢一些)。这要求先配好快照仓库,也就是第 4 篇建的那个。
  • delete:rollover 后 90 天删除。注意 min_age 是从 rollover 的时间点开始算的(没配 rollover 才从索引创建时间算),不是从「上一阶段完成」算,别把时间线搞混。

把策略挂到索引上​

策略本身不会自动生效,索引必须带上 index.lifecycle.name。通常通过索引模板统一设置:

PUT /_index_template/logs-template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 4,
"number_of_replicas": 1,
"index.lifecycle.name": "logs-policy",
"index.lifecycle.rollover_alias": "logs"
}
}
}

rollover_alias 指定 rollover 用的写别名。然后手工引导第一个索引,并把别名设为写索引:

PUT /logs-000001
{
"aliases": {
"logs": { "is_write_index": true }
}
}

之后的写入都打到 logs 别名上,rollover 时 ILM 自动创建 logs-000002 并把写别名切过去。查询同样查 logs 别名,业务代码完全不用感知索引名在变。

看 ILM 执行到哪了​

# 策略列表
curl -s "http://localhost:9200/_ilm/policy?pretty"

# 单个索引的生命周期状态:当前阶段、动作、是否出错
curl -s "http://localhost:9200/logs-000001/_ilm/explain?pretty"

_ilm/explain 是最常用的排障入口,重点看三个字段:phase(当前阶段)、action(当前动作)、step(执行到哪一步),以及 error(如果非空,就是卡住的原因)。常见卡点:仓库没配导致 cold 阶段失败、rollover 别名没设置、索引被手工设置了 index.lifecycle.indexing_complete。

监控接入:elasticsearch_exporter + Grafana​

_cat 和 _nodes/stats 适合人肉巡检,长期趋势和告警要靠 Prometheus。ES 本身不暴露 Prometheus 格式的指标,中间需要一个 exporter——社区最常用的是 elasticsearch_exporter(prometheus-community 维护)。

启动 exporter,指向 ES 的地址:

docker run -d --name es-exporter \
-p 9114:9114 \
--add-host=host.docker.internal:host-gateway \
prometheuscommunity/elasticsearch-exporter:v1.8.0 \
--es.uri=http://host.docker.internal:9200

验证指标已经出来:

curl -s http://localhost:9114/metrics | grep '^elasticsearch_cluster_health'

然后在 Prometheus 里加一个抓取任务:

scrape_configs:
- job_name: "elasticsearch"
metrics_path: /metrics
static_configs:
- targets: ["10.0.0.20:9114"]

exporter 暴露的指标名大多和 ES 的 stats 接口对应,常用的几个:

指标含义
elasticsearch_cluster_health_status集群状态,按 color 标签区分三色
elasticsearch_cluster_health_number_of_nodes节点数
elasticsearch_jvm_memory_used_bytes堆使用量,area="heap"
elasticsearch_indices_store_size_bytes索引占用磁盘
elasticsearch_indices_search_query_time_seconds累计查询耗时
elasticsearch_thread_pool_rejected_count线程池拒绝数,按 type 区分读写
elasticsearch_process_cpu_percent进程 CPU 使用率

几条能直接用的告警表达式:

# 集群非 green
elasticsearch_cluster_health_status{color="green"} == 0

# 堆使用率超过 85%
elasticsearch_jvm_memory_used_bytes{area="heap"}
/ elasticsearch_jvm_memory_max_bytes{area="heap"} > 0.85

# 写入线程池开始拒绝请求
rate(elasticsearch_thread_pool_rejected_count{type="write"}[5m]) > 0

# 有分片未分配
elasticsearch_cluster_health_unassigned_shards > 0

指标接进 Prometheus 之后,仪表盘和告警规则交给 Grafana。Grafana 的数据源和面板配置见 Grafana 核心概念与架构,社区也有现成的 Elasticsearch exporter 面板可以直接导入,省掉从零画图的时间。

别只监控 ES,也监控 exporter 自己

exporter 挂了,Prometheus 里看到的是「指标突然消失」而不是「集群异常」,很容易误判。给 exporter 加一条 up == 0 的告警,和 ES 本身的告警分开,故障定位会快很多。

日常巡检清单​

频率检查项命令/位置
每天集群三色、未分配分片_cluster/health
每天堆使用率、磁盘剩余_cat/nodes、_cat/allocation
每天写入/查询拒绝数_cat/thread_pool
每周索引大小增长趋势_cat/indices&s=store.size:desc
每周段数量异常的索引_cat/segments
每周ILM 是否有卡住的索引_ilm/explain
每月快照是否正常、做一次恢复演练_snapshot
每月版本与安全补丁官方 Release Notes

小结​

  • 巡检入口就三组:_cluster/health 看三色、_cat 系列看明细、_nodes/stats 看资源。_cat/nodes 的堆、磁盘、负载三列最值得天天扫。
  • 核心指标:堆使用率(85% 红线)、段数量与合并、搜索/索引延迟、线程池队列与拒绝、磁盘水位(85% / 90% / 95% 三级)。
  • 95% 洪水位会把索引打成只读,写请求直接失败;调高阈值只是拖延,正解是清数据、扩磁盘、上 ILM。
  • ILM 用 hot → warm → cold → delete 四阶段管理时间序列数据:rollover 控制单索引大小、forcemerge 优化只读索引、searchable_snapshot 降存储成本、delete 到期清理。
  • 策略要挂到索引上才生效:模板里写 index.lifecycle.name 和 rollover_alias,再手工引导第一个索引并设 is_write_index。
  • Prometheus 侧用 elasticsearch_exporter 抓指标,Grafana 出图;告警先配「非 green」「堆超 85%」「写入拒绝」「分片未分配」这四条。

指标和策略都有了,最后一篇处理「已经出问题了怎么办」:集群变红、分片分配失败、内存打满、写入被拒,逐条给排查路径。