Observability · Logs · Elastic Stack

可观测性 ELK 三件套:从入门到精通
Elasticsearch / Logstash / Kibana 的机制、链路、选型与排障,配 10 张 Mermaid 图

版本基线 Elastic Stack 9.5.5(2026-10-06 发布) 事实来源 Elastic 官方文档 / 官方博客 / endoflife.date 图 10 张 Mermaid(源码随图折叠) 生成 2026-10-14
目录
  1. 结论先行
  2. 30 秒心智模型(对照你的世界)
  3. 全景架构:数据从哪来、到哪去
  4. 一条日志的完整旅程(时序)
  5. Elasticsearch:检索内核
  6. Logstash:加工流水线
  7. Kibana:可视化与管控
  8. 采集层选型:Beats / Elastic Agent / OTel
  9. 存储与生命周期:data stream + ILM + logsdb
  10. 部署形态与容量规划
  11. 安全、权限与订阅边界
  12. 排障决策树
  13. 从入门到精通:能力阶梯与学习路径
  14. 判断

1 · 结论先行

ELK 不是三个软件的合集,而是一条「采集 → 加工 → 存储 → 检索 → 展现/告警」的流水线: Logstash 负责把杂乱日志变成带结构的 JSON,Elasticsearch 负责把它存下来并在秒级内全文检索, Kibana 负责把人能从里面看出问题。三者通过 HTTP/JSON 解耦,任何一段都能被替换或旁路。

2026 年必须知道的三件事:

2 · 30 秒心智模型(对照你的世界)

如果你做过 CI/CD 和容器运维,下面这张对照表就是整篇文档的地图。左边是你已经会的东西,右边是 ELK 里的同构概念——执行 = f(配置, 状态),ELK 的全部复杂度都来自「配置」如何决定「状态」的分布。

你熟悉的世界ELK 里的对应物关键差异(坑在这里)
Jenkins Pipeline / GitLab CI JobLogstash pipeline(input → filter → output)一次跑一条流水线是「一次性」的;Logstash 是常驻进程,7×24 消费事件流,失败要能重放
node_exporter / TelegrafFilebeat / Metricbeat / Elastic Agent只采集、几乎不加工,资源占用极小;加工放后面(Logstash 或 Elasticsearch ingest pipeline)
Grafana 面板Kibana Dashboard(Lens 可视化)Kibana 同时是「前端 + 管理后台」,权限/索引模板/ILM 都在同一个 UI 里
MySQL 表 / 行 / DDLindex / document / mappingmapping 一旦建好基本不可改,改字段类型只能新建索引再 reindex——像不可变 schema
分库分表的分片shard(主分片)+ replica(副本分片)分片数在索引创建时定死(_shrink/_split 可调整但代价大);分片过多本身就会拖慢集群
MySQL 分区表 / logrotate 归档data stream + ILM(hot→warm→cold→frozen→delete)data stream 是「只追加」的:写入自动进最新 backing index,不能随意 update/delete 单条
SQLQuery DSL / ES|QL(管道式新查询语言)ES|QL 是 9.x 的主力查询语言:类 SQL 但对日志/时序更自然,Kibana 的 Discover、仪表盘、告警都能用
日志文件本身_source(文档原文)+ 倒排索引 + doc_values_source 可被合成(synthetic)以省空间,代价是字段顺序等细节不再保真

3 · 全景架构:数据从哪来、到哪去

图 1 · ELK 全景架构
这张图回答:一个企业里 ELK 的五个环节各自在哪、谁可以省略。
flowchart LR subgraph SRC["数据源"] A1["应用日志文件"] A2["容器 stdout/stderr"] A3["系统与中间件指标"] A4["应用链路 trace"] end subgraph SHIP["采集层 轻量 常驻"] B1["Filebeat"] B2["Metricbeat"] B3["Elastic Agent
内建 OTel Collector"] end subgraph PROC["加工层 可选 可放大"] C1["Logstash
grok / dissect / enrich"] C2["Elasticsearch
ingest pipeline"] C3["消息队列
Kafka / Redis 缓冲"] end subgraph STORE["存储与检索"] D1["Elasticsearch 集群
data stream + ILM"] D2["对象存储
searchable snapshot"] end subgraph VIEW["展现与告警"] E1["Kibana
Discover / Lens / 仪表盘"] E2["告警与连接器
邮件 / Webhook / Slack"] end A1 --> B1 A2 --> B3 A3 --> B2 A4 --> B3 B1 --> C3 B2 --> C2 B3 --> C3 C3 --> C1 C1 --> D1 B1 -.->|"轻量场景直连"| D1 B3 -.->|"Agent 直连"| D1 C2 --> D1 D1 --> D2 D1 --> E1 E1 --> E2 E2 -.->|"人到现场处理"| SRC
Mermaid 源码
flowchart LR
  subgraph SRC["数据源"]
    A1["应用日志文件"]
    A2["容器 stdout/stderr"]
    A3["系统与中间件指标"]
    A4["应用链路 trace"]
  end
  subgraph SHIP["采集层 轻量 常驻"]
    B1["Filebeat"]
    B2["Metricbeat"]
    B3["Elastic Agent<br/>内建 OTel Collector"]
  end
  subgraph PROC["加工层 可选 可放大"]
    C1["Logstash<br/>grok / dissect / enrich"]
    C2["Elasticsearch<br/>ingest pipeline"]
    C3["消息队列<br/>Kafka / Redis 缓冲"]
  end
  subgraph STORE["存储与检索"]
    D1["Elasticsearch 集群<br/>data stream + ILM"]
    D2["对象存储<br/>searchable snapshot"]
  end
  subgraph VIEW["展现与告警"]
    E1["Kibana<br/>Discover / Lens / 仪表盘"]
    E2["告警与连接器<br/>邮件 / Webhook / Slack"]
  end
  A1 --> B1
  A2 --> B3
  A3 --> B2
  A4 --> B3
  B1 --> C3
  B2 --> C2
  B3 --> C3
  C3 --> C1
  C1 --> D1
  B1 -.->|"轻量场景直连"| D1
  B3 -.->|"Agent 直连"| D1
  C2 --> D1
  D1 --> D2
  D1 --> E1
  E1 --> E2
  E2 -.->|"人到现场处理"| SRC

三点工程判据:

4 · 一条日志的完整旅程(时序)

图 2 · 一条日志从产生到可被检索(含可观测性三大支柱汇合)
这张图回答:为什么我「刚打的日志」在 Kibana 里搜不到——延迟到底花在哪几段。
sequenceDiagram autonumber participant APP as 业务应用 participant SHIP as Filebeat / Elastic Agent participant MQ as Kafka(可选) participant LS as Logstash participant ES as Elasticsearch participant KB as Kibana APP->>SHIP: 追加写日志文件或 stdout Note over SHIP: 记忆位点 断点续传 SHIP->>SHIP: 多行合并 + 加 host 元数据 SHIP->>MQ: 投递事件(或直连 ES 的 bulk 接口) MQ->>LS: 消费者拉取 LS->>LS: grok 解析 + date 归一化 + mutate 清洗 LS->>ES: bulk 批量写 data stream Note over ES: ingest pipeline 再加工
按时间戳路由到 backing index ES->>ES: 写入内存 buffer 与 translog ES-->>KB: refresh 后可见 默认约 1 秒 Note over LS,ES: 写入失败 400/404 → DLQ 死信队列 KB->>ES: ES|QL 或 Query DSL 查询 ES-->>KB: 命中结果 + 聚合 KB->>KB: Lens 渲染仪表盘并触发告警规则
Mermaid 源码
sequenceDiagram
  autonumber
  participant APP as 业务应用
  participant SHIP as Filebeat / Elastic Agent
  participant MQ as Kafka(可选)
  participant LS as Logstash
  participant ES as Elasticsearch
  participant KB as Kibana
  APP->>SHIP: 追加写日志文件或 stdout
  Note over SHIP: 记忆位点 断点续传
  SHIP->>SHIP: 多行合并 + 加 host 元数据
  SHIP->>MQ: 投递事件(或直连 ES 的 bulk 接口)
  MQ->>LS: 消费者拉取
  LS->>LS: grok 解析 + date 归一化 + mutate 清洗
  LS->>ES: bulk 批量写 data stream
  Note over ES: ingest pipeline 再加工<br/>按时间戳路由到 backing index
  ES->>ES: 写入内存 buffer 与 translog
  ES-->>KB: refresh 后可见 默认约 1 秒
  Note over LS,ES: 写入失败 400/404 → DLQ 死信队列
  KB->>ES: ES|QL 或 Query DSL 查询
  ES-->>KB: 命中结果 + 聚合
  KB->>KB: Lens 渲染仪表盘并触发告警规则
官方原文(near real-time search):「By default, Elasticsearch periodically refreshes indices every second, but only on indices that have received one search request or more in the last 30 seconds.」 —— 两个常被忽略的推论:(1) 没人查的索引根本不会刷新,日志延迟看指标前先确认有人在查;(2) 以为「写入立即可见」而在测试里断言,必然偶发失败。

5 · Elasticsearch:检索内核

5.1四个必须背下来的对象

对象是什么工程含义
document一条 JSON 记录(一条日志 = 一个文档)字段名决定一切,字段名一乱,Kibana 面板就废
index文档的集合,背后是若干 Lucene 段真的「表」,但 schema(mapping)不可变
shardLucene 索引实例,分布在节点上并行度的单位,也是故障恢复的单位;创建后数量基本固定
replica主分片的副本,跨节点高可用 + 提升查询吞吐;副本数可随时改

5.2写入路径与「秒级可见」的真相

图 3 · 单分片内的写入与可见性
这张图回答:refresh 和 flush 有什么区别,为什么数据「已经写成功」却「还搜不到」、断电为什么不一定丢。
flowchart TD W["index / bulk 请求"] --> IB["内存索引缓冲
in-memory buffer"] W --> TL["translog 事务日志
每次写都落盘"] IB -->|"refresh 默认约 1 秒"| SEG["新 segment
先写文件系统缓存"] SEG --> SEARCH["可被搜索
近实时生效"] SEG -->|"flush / commit"| DISK["落盘 fsync"] TL -->|"节点重启恢复"| IB SEG -->|"后台 merge"| M["合并成大 segment
删除已标记文档"] M --> SEARCH
Mermaid 源码
flowchart TD
  W["index / bulk 请求"] --> IB["内存索引缓冲<br/>in-memory buffer"]
  W --> TL["translog 事务日志<br/>每次写都落盘"]
  IB -->|"refresh 默认约 1 秒"| SEG["新 segment<br/>先写文件系统缓存"]
  SEG --> SEARCH["可被搜索<br/>近实时生效"]
  SEG -->|"flush / commit"| DISK["落盘 fsync"]
  TL -->|"节点重启恢复"| IB
  SEG -->|"后台 merge"| M["合并成大 segment<br/>删除已标记文档"]
  M --> SEARCH

5.3读路径:一次查询在集群里怎么走

图 4 · 查询的扇出与归并
这张图回答:为什么「分片越多查询越慢」,以及查不到/超时该往哪一层看。
flowchart LR C["Kibana 发起查询"] --> CO["协调节点
coordinating"] CO -->|"并发扇出"| P1["主分片 1
本地查询"] CO -->|"并发扇出"| P2["主分片 2
本地查询"] CO -->|"并发扇出"| P3["主分片 N
本地查询"] P1 --> MERGE["归并排序 / 聚合合并"] P2 --> MERGE P3 --> MERGE MERGE --> TOP["返回 top-N 与聚合结果"] CO -.->|"默认读取路径"| R["副本分片
共同承担读流量"] R --> MERGE
Mermaid 源码
flowchart LR
  C["Kibana 发起查询"] --> CO["协调节点<br/>coordinating"]
  CO -->|"并发扇出"| P1["主分片 1<br/>本地查询"]
  CO -->|"并发扇出"| P2["主分片 2<br/>本地查询"]
  CO -->|"并发扇出"| P3["主分片 N<br/>本地查询"]
  P1 --> MERGE["归并排序 / 聚合合并"]
  P2 --> MERGE
  P3 --> MERGE
  MERGE --> TOP["返回 top-N 与聚合结果"]
  CO -.->|"默认读取路径"| R["副本分片<br/>共同承担读流量"]
  R --> MERGE

结论:查询成本 ≈ 分片数 × 单分片成本。每个分片都要被打开、查、归并,因此「分片太多」(oversharding)是自建集群性能劣化的头号原因。官方给出的优化标尺:

5.49.x 的断代变更(照抄 8.x 文档会踩)

变更影响
mapping 元数据字段不再支持 type / fields / copy_to / boost老模板里带这些参数的自定义元数据字段,需要删掉后再建
client.type 节点设置被移除节点客户端早在 8.0 就删了;elasticsearch.yml 里留着的这行会让节点起不来,删掉
旧 _knn_search 技术预览 API 移除向量检索请用 knn 查询(_search 内)
单节点磁盘水位设置被移除磁盘水位相关设置只保留 multi-node / single-node 两种取值,升级前清理旧值
elser 推理服务弃用用 elasticsearch 服务替代(稀疏向量/推理场景)

来源:Elasticsearch 9.0.0 release notes(官方 9.0 安装与升级指南)。

5.5ingest pipeline:不写 Logstash 也能加工

Elasticsearch 自身带一个「写入前加工」环节,请求里带 ?pipeline=xxx 即触发;索引可声明 default pipeline 和 final pipeline。这层适合做轻量归一化(补字段、改类型、geoip),官方还提供 simulate API 在不写入的前提下验证 pipeline。

# 定义一个最简单的 ingest pipeline
PUT _ingest/pipeline/add_env
{
  "description": "补齐环境字段",
  "processors": [
    { "set": { "field": "service.environment", "value": "prod" } }
  ]
}

# 不写入、只验证:拿到处理后的文档
POST _ingest/pipeline/add_env/_simulate
{ "docs": [ { "_source": { "message": "hello" } } ] }

6 · Logstash:加工流水线

6.1结构:三段 + 两条队列

图 5 · Logstash 事件处理流水线与失败分支
这张图回答:Logstash 什么时候会丢日志,PQ 与 DLQ 分别在哪个位置兜底。
flowchart LR IN["input 插件
beats / kafka / file / tcp"] --> QB{"队列"} QB -->|"默认 memory
快 但重启丢"| F1["filter worker 1"] QB -->|"persisted
慢 但可恢复"| F2["filter worker 2"] F1 --> OUT["output 插件
elasticsearch / kafka"] F2 --> OUT OUT -->|"成功"| ES["Elasticsearch"] OUT -->|"400 / 404 类失败"| DLQ["DLQ 死信队列
落盘待重放"] OUT -->|"ES 不可用"| RETRY["重试并阻塞
上游压力回传"] DLQ -->|"专用 pipeline 读取"| OUT
Mermaid 源码
flowchart LR
  IN["input 插件<br/>beats / kafka / file / tcp"] --> QB{"队列"}
  QB -->|"默认 memory 快 但重启丢"| F1["filter worker 1"]
  QB -->|"persisted 慢 但可恢复"| F2["filter worker 2"]
  F1 --> OUT["output 插件<br/>elasticsearch / kafka"]
  F2 --> OUT
  OUT -->|"成功"| ES["Elasticsearch"]
  OUT -->|"400 / 404 类失败"| DLQ["DLQ 死信队列<br/>落盘待重放"]
  OUT -->|"ES 不可用"| RETRY["重试并阻塞<br/>上游压力回传"]
  DLQ -->|"专用 pipeline 读取"| OUT
机制官方口径实务建议
内存队列(默认)快,但进程重启未处理事件会丢可接受的丢日志场景(调试环境、可重放源)用默认
持久化队列 PQ磁盘队列,重启可恢复;官方提供 pqrepair 工具修复损坏队列金融/审计类日志必开;注意磁盘占用与吞吐下降
死信队列 DLQ专收 Elasticsearch 返回 400 / 404 这类「重试也没用」的事件不打开 DLQ 时,这些事件在日志里刷屏然后消失;打开后用专有 pipeline 消费它们定位 mapping 冲突

6.2Filter 三件套:grok / dissect / mutate

input {
  beats { port => 5044 }
}

filter {
  # 1) 结构化解析:优先 dissect(快),格式不稳再用 grok(慢但灵活)
  grok {
    match => {
      "message" => "%{IPORHOST:client.ip} %{WORD:http.request.method} %{URIPATHPARAM:url.path} %{NUMBER:http.response.status_code:int}"
    }
  }
  # 2) 时间归一化:把日志里的时间变成 @timestamp,否则按采集时间算
  date { match => [ "log_time", "ISO8601", "UNIX_MS" ] }

  # 3) 清洗与降噪
  mutate {
    remove_field => [ "agent.ephemeral_id", "host.os" ]
    rename       => { "log_level" => "log.level" }   # 对齐 ECS 命名
  }
  if [log][level] == "DEBUG" { drop { } }              # 源头降量,比事后删索引便宜
}

output {
  elasticsearch {
    hosts            => ["https://es-node-1:9200", "https://es-node-2:9200"]
    data_stream      => "true"          # 走 data stream 路由
    data_stream_type    => "logs"
    data_stream_dataset => "myapp.access"
    data_stream_namespace => "prod"
    # 目标 data stream: logs-myapp.access-prod
  }
}
插件文档要点:Elasticsearch output 插件的 data_stream 默认即为 true;必须启用 ECS 兼容模式(ecs_compatibility 设为 v1 或 v8)data stream 才能正常工作。TLS 场景可用 ca_trusted_fingerprint 传 CA 的 SHA-256 指纹(需 Logstash 8.3+),省掉证书文件分发。

6.3资源与版本红线

项官方口径
堆内存典型摄入场景建议 不低于 4 GB、不高于 8 GB(堆之外还有 off-heap:线程栈、direct memory)
线程模型每个 pipeline 的 filter 线程池约 2 × CPU 核数;output 插件还会起自己的辅助线程
Java 版本Logstash 9.4.0 起要求 Java 21(升级 JRuby 10 所致),Java 17 及以下不再支持
Elasticsearch 堆默认由角色与内存自动推导,官方推荐用默认值;手工覆盖时 Xms=Xmx 且 ≤ 总内存 50%,同时不超过压缩指针(compressed oops)阈值(多数系统 26 GB 安全,个别可到 30 GB)
Elasticsearch 进程内存用 heap size [...], compressed ordinary object pointers [true] 日志行或 jvm.using_compressed_ordinary_object_pointers 自证没超阈值

7 · Kibana:可视化与管控

图 6 · 从查询到仪表盘再到告警
这张图回答:Kibana 里一次「面板刷新」背后发生了什么,告警规则挂在哪一环。
flowchart LR Q["查询入口
Discover / 仪表盘筛选器"] --> LANG{"查询语言"} LANG -->|"ESQL 推荐"| P1["管道式查询
FROM / WHERE / STATS"] LANG -->|"Query DSL"| P2["JSON 查询体"] P1 --> KBS["Kibana 服务端
注入数据视图与权限"] P2 --> KBS KBS --> ESL["Elasticsearch 集群"] ESL --> RES["结果集"] RES --> LENS["Lens 可视化
多图层 可折叠为标签页"] LENS --> DASH["Dashboard
可设置 owner 与只写限制"] RES --> ALERT["告警规则
含基于 ES|QL 面板创建规则"] ALERT --> CONN["连接器
邮件 / Slack / Webhook"]
Mermaid 源码
flowchart LR
  Q["查询入口<br/>Discover / 仪表盘筛选器"] --> LANG{"查询语言"}
  LANG -->|"ESQL 推荐"| P1["管道式查询<br/>FROM / WHERE / STATS"]
  LANG -->|"Query DSL"| P2["JSON 查询体"]
  P1 --> KBS["Kibana 服务端<br/>注入数据视图与权限"]
  P2 --> KBS
  KBS --> ESL["Elasticsearch 集群"]
  ESL --> RES["结果集"]
  RES --> LENS["Lens 可视化<br/>多图层 可折叠为标签页"]
  LENS --> DASH["Dashboard<br/>可设置 owner 与只写限制"]
  RES --> ALERT["告警规则<br/>含基于 ES|QL 面板创建规则"]
  ALERT --> CONN["连接器<br/>邮件 / Slack / Webhook"]

9.x 的 Kibana 演进(近几个次版本)

版本能力对运维的意义
9.1可从 ES|QL 仪表盘面板直接「创建告警规则」从「看图」到「挂告警」不用再切到告警模块重写查询
9.2Discover 里的 ES|QL 查询支持变量控件;Kibana 新增 Streams 页统一管理 data stream数据流管理从「模板 + ILM + 索引」三处配置收敛到一个页面
9.3仪表盘支持 owner 与 write_restricted(写成受限期);Lens 图层改为标签页布局仪表盘可以「先内部编辑、再发布」,避免半成品被业务方看到并误信
9.4ES|QL 支持多 terms 图、Vega 可视化;ES|QL 图表支持仪表盘/URL 下钻复杂分析不用再退回 Query DSL + 脚本聚合

可观测性三大支柱在 Kibana 的落点

支柱数据形态Kibana 入口
Logslogs-*-* data stream(默认 logsdb)Discover(日志视图)/ 对数速率异常检测
Metricsmetrics-*-*(TSDS 时序流更省空间)基础设施视图 / Lens 时序图
TracesAPM 数据(原生 agent 或 OTel/EDOT 汇入)APM 应用视图(服务地图、事务、依赖)

ECS(Elastic Common Schema)是把三者粘起来的关键:@timestamp、log.level、message、service.name、event.dataset(这个字段能开启日志速率异常检测)、error.* 等字段名统一了,面板与告警才能复用。官方特别区分三个时间戳的先后关系:@timestamp(事件发生) < event.created(采集器看到) < event.ingested(入库)。排查「日志延迟」时用 event.ingested - @timestamp 量化,别靠感觉。

8 · 采集层选型:Beats / Elastic Agent / OpenTelemetry

图 7 · 采集层选型判定树
这张图回答:我这个场景到底该装 Filebeat、装 Elastic Agent,还是直接上 OTel Collector。
flowchart TD START["要采集什么"] --> T1{"需要同时采
日志 + 指标 + trace 吗"} T1 -->|"只要日志 且环境受限"| FB["Filebeat
最省资源"] T1 -->|"只要指标"| MB["Metricbeat"] T1 -->|"都要 或要统一管理"| EA["Elastic Agent
单一代理 集中策略"] T1 -->|"要跨厂商 标准 OTel"| OT["OpenTelemetry Collector
或 EDOT 发行版"] EA --> O1{"9.2 起内建 OTel Collector"} O1 -->|"agent 模式"| O2["主机本地采集
贴近应用"] O1 -->|"gateway 模式"| O3["集中式入口
自管栈中替代 APM Server"] OT --> O4["统一 OTLP 出口
弹性对接多后端"] FB --> DONE["写入 Elasticsearch
或 Logstash"] MB --> DONE EA --> DONE O2 --> DONE O3 --> DONE O4 --> DONE
Mermaid 源码
flowchart TD
  START["要采集什么"] --> T1{"需要同时采<br/>日志 + 指标 + trace 吗"}
  T1 -->|"只要日志 且环境受限"| FB["Filebeat<br/>最省资源"]
  T1 -->|"只要指标"| MB["Metricbeat"]
  T1 -->|"都要 或要统一管理"| EA["Elastic Agent<br/>单一代理 集中策略"]
  T1 -->|"要跨厂商 标准 OTel"| OT["OpenTelemetry Collector<br/>或 EDOT 发行版"]
  EA --> O1{"9.2 起内建 OTel Collector"}
  O1 -->|"agent 模式"| O2["主机本地采集<br/>贴近应用"]
  O1 -->|"gateway 模式"| O3["集中式入口<br/>自管栈中替代 APM Server"]
  OT --> O4["统一 OTLP 出口<br/>弹性对接多后端"]
  FB --> DONE["写入 Elasticsearch<br/>或 Logstash"]
  MB --> DONE
  EA --> DONE
  O2 --> DONE
  O3 --> DONE
  O4 --> DONE

事实(官方文档):从 9.2 起,Elastic Agent 运行在内建的 OpenTelemetry Collector 之上(该能力来自 EDOT Collector,即「Elastic Distributions of OpenTelemetry」);官方同时提供 Beats → Elastic Agent 的迁移指南。EDOT 家族还包括各语言 SDK(Java / Python / .NET / Node.js / PHP / Android / iOS 已 GA,Browser 处于技术预览),以及面向云厂商的 Forwarder。

实务推论:新建集群就上 Elastic Agent;存量 Filebeat 不必连夜迁(Beats 仍在发布,9.3.8 于 2026-08-04 发布),但新项目的默认选择应该是 Agent;如果公司有「可观测性数据标准必须 OTel」的技术债要求,直接在 Agent 的 OTel 模式下用 OTLP 接入,避免以后再迁一次。

9 · 存储与生命周期:data stream + ILM + logsdb

9.1data stream 是什么

官方定义:data stream 是对一组针对「只追加时序数据」优化的索引的抽象层——你只对一个逻辑名字(如 logs-myapp-prod)读写,它自动路由到多个 backing index。适合它的三个判据:

反例:需要频繁按 _id 覆盖写(后写入者胜)的场景,官方建议改用 index alias + write index。TSDS(时序数据流)与 data stream lifecycle 这类特性必须以 data stream 为前提。

9.2ILM:五个阶段与可用动作

图 8 · 索引生命周期状态机
这张图回答:一条日志从热到被删除,中间会经历哪些动作,哪些动作只能在特定阶段做。
flowchart LR HOT["hot 热
持续写入 可搜索"] -->|"rollover 达 50GB 或 30 天"| WARM["warm 温
只读 可 shrink 合并段"] WARM -->|"7 天后"| COLD["cold 冷
查询少 迁到便宜硬件"] COLD -->|"到期"| FROZEN["frozen 冻结
可搜索快照 极慢但便宜"] FROZEN -->|"保留期到达"| DEL["delete 删除"] HOT -.->|"set_priority 提高优先级"| HOT WARM -.->|"allocate 指定节点属性"| WARM COLD -.->|"searchable snapshot"| COLD DEL -.->|"wait_for_snapshot 先确保快照"| DEL
Mermaid 源码
flowchart LR
  HOT["hot 热<br/>持续写入 可搜索"] -->|"rollover 达 50GB 或 30 天"| WARM["warm 温<br/>只读 可 shrink 合并段"]
  WARM -->|"7 天后"| COLD["cold 冷<br/>查询少 迁到便宜硬件"]
  COLD -->|"到期"| FROZEN["frozen 冻结<br/>可搜索快照 极慢但便宜"]
  FROZEN -->|"保留期到达"| DEL["delete 删除"]
  HOT -.->|"set_priority 提高优先级"| HOT
  WARM -.->|"allocate 指定节点属性"| WARM
  COLD -.->|"searchable snapshot"| COLD
  DEL -.->|"wait_for_snapshot 先确保快照"| DEL
动作hotwarmcoldfrozendelete
rollover✅————
shrink / force merge✅✅———
read-only / set priority / downsample✅✅✅——
allocate / migrate—✅✅——
searchable snapshot✅—✅✅—
unfollow✅✅✅✅—
wait for snapshot / delete————✅

来源:官方「Index lifecycle management phases and actions」——这是一手表格,照它配策略就不会出现「在 delete 阶段配 rollover」这种无效配置。

官方给的典型策略示例:主分片到 50 GB 或 30 天 rollover → warm 阶段设为只读并 shrink 到单个分片 → 7 天后进 cold 迁到便宜硬件 → 保留期满删除。注意两条硬规则:(1) 后续阶段的 min_age 必须不小于前一阶段;(2) 手工挂在索引上的生命周期策略,在 rollover 产生新索引后不会自动继承——必须写在索引模板里。

9.3logsdb:9.x 最实在的成本变化

事实(官方文档 + 官方实测):

场景要不要开 logsdb理由
应用日志、访问日志(海量、低价值密度)要(新集群默认已开)省磁盘是第一优先级,写入慢几个百分点可以接受
需要打满写入吞吐的极限场景可评估关闭索引吞吐有可测量开销;用真实流量压测后再定
依赖字段顺序/数组重复值保真的场景谨慎synthetic _source 恢复出的字段顺序可能与原文不同;logsdb 默认 synthetic_source_keep: arrays 保留重复值与顺序,但数组元素/对象结构不保证保留
# 检查现有 data stream 是否已落进 logsdb 模式
GET /.ds-logs-*/_settings?filter_path=*.settings.index.mode

# 手工建一个 logsdb 索引(自测用)
PUT /my-logsdb
{ "settings": { "index.mode": "logsdb" } }

10 · 部署形态与容量规划

图 9 · 典型生产拓扑(角色分离 + 冷热分层)
这张图回答:一个自管 ELK 集群的节点该怎么分工,冷热数据落在哪。
flowchart TB LB["客户端 / Kibana / Beats"] --> CO["协调层
coordinating 查询聚合"] CO --> HL["hot 层
SSD 高配
写入与近期查询"] CO --> WL["warm 层
中等配置
只读 已 merge"] CO --> CL["cold / frozen 层
大容量 HDD
可搜索快照"] HL --> ML["专用 master 节点
3 台 奇数 只做集群决策"] WL --> ML CL --> ML HL --> SNAP["快照仓库
对象存储 S3/OSS/COS"] ML --> SNAP HL --> KBN["Kibana 实例"]
Mermaid 源码
flowchart TB
  LB["客户端 / Kibana / Beats"] --> CO["协调层<br/>coordinating 查询聚合"]
  CO --> HL["hot 层<br/>SSD 高配<br/>写入与近期查询"]
  CO --> WL["warm 层<br/>中等配置<br/>只读 已 merge"]
  CO --> CL["cold / frozen 层<br/>大容量 HDD<br/>可搜索快照"]
  HL --> ML["专用 master 节点<br/>3 台 奇数 只做集群决策"]
  WL --> ML
  CL --> ML
  HL --> SNAP["快照仓库<br/>对象存储 S3/OSS/COS"]
  ML --> SNAP
  HL --> KBN["Kibana 实例"]

容量规划的五条硬口径

维度官方标尺落地做法
分片体积10 GB – 50 GBILM rollover 设 max_primary_shard_size: 50GB,让分片随数据自然增长
分片文档数低于 2 亿 / 分片rollover 再加 max_docs 兜底(高基数小文档场景尤其重要)
ES 堆默认自动推导(推荐);手工覆盖时 ≤ 50% 内存且 Xms=Xmx别抄「固定 16 GB」的老博客;用默认,然后看 GC 日志
压缩指针阈值≤ 26 GB(多数系统安全,最多 30 GB)堆超过阈值后指针从 4 字节变 8 字节,内存效率反而下降
master 节点奇数台、专用角色3 台专用 master,避免脑裂与数据节点抢占选主资源

部署形态三选一:

11 · 安全、权限与订阅边界

图 10 · 一次请求到底被谁拦下来(权限判定顺序)
这张图回答:我给用户配了角色为什么还是看不到某个应用/某个字段。
flowchart TD REQ["用户请求 Kibana 某项功能"] --> S1{"该角色在此空间被授予
功能权限吗"} S1 -->|"没授予"| DENY["功能不可见"] S1 -->|"授予"| S2{"该功能在当前空间
被显示出来了吗"} S2 -->|"被隐藏"| DENY2["功能不可见"] S2 -->|"可见"| S3{"索引权限匹配吗
read + view_index_metadata"} S3 -->|"不匹配"| DENY3["查不到数据"] S3 -->|"匹配"| S4{"有 DLS / FLS 限制吗
付费功能"} S4 -->|"有 文档或字段被过滤"| PART["只看到部分文档或字段"] S4 -->|"没有"| OK["看到全部"]
Mermaid 源码
flowchart TD
  REQ["用户请求 Kibana 某项功能"] --> S1{"该角色在此空间被授予<br/>功能权限吗"}
  S1 -->|"没授予"| DENY["功能不可见"]
  S1 -->|"授予"| S2{"该功能在当前空间<br/>被显示出来了吗"}
  S2 -->|"被隐藏"| DENY2["功能不可见"]
  S2 -->|"可见"| S3{"索引权限匹配吗<br/>read + view_index_metadata"}
  S3 -->|"不匹配"| DENY3["查不到数据"]
  S3 -->|"匹配"| S4{"有 DLS / FLS 限制吗<br/>付费功能"}
  S4 -->|"有 文档或字段被过滤"| PART["只看到部分文档或字段"]
  S4 -->|"没有"| OK["看到全部"]

官方权限矩阵的原文结论:功能可用 = 角色授予了该功能 AND 该功能在当前空间可见;两个条件缺一不可。给出的读取建议是索引权限至少给 read + view_index_metadata。另外一条容易误解的规则:多个角色叠加是「并集」——再加一个角色不能减少权限,想收权只能改/删原有角色。

能力订阅档位说明
加密通信、RBAC、Kibana 空间、功能控制、File/native 认证、API key、流量加密Basic 免费基础安全在免费版就有,自建集群不要裸奔
文档级安全 DLS / 字段级安全 FLS、SSO(SAML/OIDC/Kerberos/JWT)、ABAC、IP 过滤、审计日志「同一个索引里不同团队只能看自己那部分日志」属于这一档
synthetic _sourcelogsdb 最狠的省空间能力,免费版拿不到
基于机器学习异常检测 / SLO 的告警规则普通阈值告警不受此限
跨集群操作官方脚注:跨集群操作要求涉及的所有集群订阅档位一致
Gold 档已停售新客户不可购;Platinum 也仅存量客户可续

注:档位以官方订阅页为准(本文按 2026 年 10 月页面对照);Elastic Cloud 的功能矩阵与自管版略有差异(如部分 frozen 层/ILM 能力在托管侧按档位划分)。

12 · 排障决策树

图 11 · ELK 三类高频故障的定位路径
这张图回答:日志不见了、集群变黄变红、查询变慢,分别先看哪一层。
flowchart TD P["问题现象"] --> Q1{"现象是哪一类"} Q1 -->|"日志根本没出现"| A1{"源头有这条日志吗"} A1 -->|"文件里就没有"| A2["不是 ELK 的问题
回到应用侧"] A1 -->|"文件里有"| A3{"采集器位点是否推进
Filebeat registry / Agent 状态"} A3 -->|"没推进"| A4["采集器挂了或权限不足
看采集器自身日志"] A3 -->|"推进了"| A5{"中间有 MQ 或 Logstash 吗"} A5 -->|"有"| A6["查 DLQ 与 PQ
400/404 类失败会堆在 DLQ"] A5 -->|"没有"| A7["查 ES bulk 拒绝
线程池或磁盘水位"] Q1 -->|"集群状态异常"| B1{"颜色"} B1 -->|"yellow"| B2["有副本未分配
单节点集群属正常现象"] B1 -->|"red"| B3["有主分片未分配
查 allocation explain"] B3 --> B4["常见原因
磁盘水位 节点掉线 分片损坏"] Q1 -->|"查询变慢"| C1{"是写入高峰还是长期"} C1 -->|"写入高峰"| C2["检查 refresh 频率与 bulk 大小
考虑加大 refresh_interval"] C1 -->|"长期变慢"| C3{"分片是否过多"} C3 -->|"是"| C4["强制合并或按 50GB 重新规划
_forcemerge 只清墓碑文档"] C3 -->|"否"| C5["看字段类型与查询写法
keyword 与 text 用错会扫全量"]
Mermaid 源码
flowchart TD
  P["问题现象"] --> Q1{"现象是哪一类"}
  Q1 -->|"日志根本没出现"| A1{"源头有这条日志吗"}
  A1 -->|"文件里就没有"| A2["不是 ELK 的问题<br/>回到应用侧"]
  A1 -->|"文件里有"| A3{"采集器位点是否推进<br/>Filebeat registry / Agent 状态"}
  A3 -->|"没推进"| A4["采集器挂了或权限不足<br/>看采集器自身日志"]
  A3 -->|"推进了"| A5{"中间有 MQ 或 Logstash 吗"}
  A5 -->|"有"| A6["查 DLQ 与 PQ<br/>400/404 类失败会堆在 DLQ"]
  A5 -->|"没有"| A7["查 ES bulk 拒绝<br/>线程池或磁盘水位"]
  Q1 -->|"集群状态异常"| B1{"颜色"}
  B1 -->|"yellow"| B2["有副本未分配<br/>单节点集群属正常现象"]
  B1 -->|"red"| B3["有主分片未分配<br/>查 allocation explain"]
  B3 --> B4["常见原因<br/>磁盘水位 节点掉线 分片损坏"]
  Q1 -->|"查询变慢"| C1{"是写入高峰还是长期"}
  C1 -->|"写入高峰"| C2["检查 refresh 频率与 bulk 大小<br/>考虑加大 refresh_interval"]
  C1 -->|"长期变慢"| C3{"分片是否过多"}
  C3 -->|"是"| C4["强制合并或按 50GB 重新规划<br/>_forcemerge 只清墓碑文档"]
  C3 -->|"否"| C5["看字段类型与查询写法<br/>keyword 与 text 用错会扫全量"]
症状最可能的根因先跑哪条命令
Kibana 搜不到刚发生的日志refresh 未到(默认约 1 秒)/ 没人查该索引就不刷新 / 采集器位点滞后看 event.ingested - @timestamp 的差值,确认延迟在哪一段
集群 yellow副本分片未分配(单节点集群常见)GET _cluster/allocation/explain
集群 red主分片未分配:节点掉线、磁盘水位、分片损坏GET _cluster/health?level=indices + allocation explain
Logstash 日志里刷 400 / 404mapping 冲突(字段类型不符)或目标索引不存在查 DLQ;用 ingest simulate 校验后再放量
写入被拒 es_rejected_executionbulk 线程池饱和 / 磁盘水位触顶看线程池队列与磁盘水位,加节点或降级写入
查询长期慢分片过多(oversharding)/ 字段类型选错(text 当 keyword 用)按 10–50 GB 重新规划,检查 mapping

13 · 从入门到精通:能力阶梯与学习路径

图 12 · 五级能力阶梯
这张图回答:我现在的水平在哪一级,下一级要补什么。
flowchart LR L1["L1 跑起来
docker compose 起三件套
灌进一条日志并在 Discover 看到"] --> L2 L2["L2 采集与解析
Filebeat 采 Nginx 日志
Logstash grok 出字段
画出第一张 Lens 图"] --> L3 L3["L3 规范与生命周期
ECS 字段命名
索引模板 + ILM 冷热
data stream 与 rollover"] --> L4 L4["L4 性能与容量
分片规划 10-50GB
堆与 GC 观察
bulk 与 refresh 调优
logdsb 取舍"] --> L5 L5["L5 生产级运维
RBAC 与空间隔离
快照与恢复演练
版本升级路线
告警闭环与容量预估"]
Mermaid 源码
flowchart LR
  L1["L1 跑起来<br/>docker compose 起三件套<br/>灌进一条日志并在 Discover 看到"] --> L2
  L2["L2 采集与解析<br/>Filebeat 采 Nginx 日志<br/>Logstash grok 出字段<br/>画出第一张 Lens 图"] --> L3
  L3["L3 规范与生命周期<br/>ECS 字段命名<br/>索引模板 + ILM 冷热<br/>data stream 与 rollover"] --> L4
  L4["L4 性能与容量<br/>分片规划 10-50GB<br/>堆与 GC 观察<br/>bulk 与 refresh 调优<br/>logsdb 取舍"] --> L5
  L5["L5 生产级运维<br/>RBAC 与空间隔离<br/>快照与恢复演练<br/>版本升级路线<br/>告警闭环与容量预估"]
级别能做什么必须搞懂的机制自测题
L1 跑起来本地起三件套,写入并检索到一条日志HTTP/JSON 接口、index 与 document、Discover 基本查询为什么刚写入的文档可能查不到?(refresh)
L2 采集与解析用 Filebeat + Logstash 采真实日志并结构化grok vs dissect、@timestamp 的来源、ECS 字段日志时间显示成采集时间,是哪个 filter 漏了?(date)
L3 规范与生命周期索引模板 + ILM + data stream 全自动滚动归档rollover 触发条件、min_age 单调性、mapping 不可变改了字段类型,为什么必须 reindex?
L4 性能与容量按 10–50 GB 规划分片、判断要不要 logsdb、定位变慢原因segment/merge、refresh 代价、查询扇出、堆与压缩指针堆设 40 GB 会更快还是更慢?(更慢)
L5 生产级运维多租户权限、快照恢复演练、版本升级、告警闭环RBAC 判定顺序、订阅边界、跨版本破坏性变更9.0 升级前要删掉哪些历史设置?

建议的动手顺序(每一步都有可见产出)

  1. 单机三件套:跑起来 → 灌一条日志 → 在 Discover 看到它 → 用 ?refresh=wait_for 复现「立刻可见」。
  2. Nginx 日志端到端:Filebeat 采 → Logstash grok 出 client.ip / url.path / http.response.status_code → 在 Kibana 画「状态码 Top 10」Lens 图。
  3. 生命周期:建索引模板 + ILM(50 GB / 30 天 rollover,7 天转 cold,30 天删除)→ 用一个小的 max_age: 1m 策略观察 backing index 真实滚动。
  4. 压测与观察:灌入百万级文档 → 观察 segment 数量、force merge 前后的查询耗时差异。
  5. 故障演练:手动停一个节点看 yellow/red;打开 DLQ 后故意写一个类型冲突的文档,再从 DLQ 里把它捞出来。
  6. 安全与隔离:两个空间 + 两个角色,验证「功能可见性 × 索引权限」两个条件都必须满足。

版本与升级路线(记住这条时间线)

版本状态(2026-10-14)备注
9.5.x当前主线(9.5.5,2026-10-06)新部署选它
9.4.x仍受支持(9.4.8,2026-10-06 同日发布)9.0–9.3 已陆续终止维护
9.3 / 9.2 / 9.1 / 9.0已 EOL(分别止于 2026-08-04 / 2026-05-05 / 2026-02-03 / 2025-10-23)还跑在这些版本上的集群应尽早升级
8.19.x最后的 8.x 线,维护至 2027-07-158.x 的「长期落脚点」,跨大版本升级的起点
Beats 9.2已 EOL(2026-05-05)Beats 仍在发布(9.3.8 @ 2026-08-04),但官方路径已指向 Elastic Agent
升级纪律:次版本支持窗口大约只有半年多。把「能升就升」写进运维日历,比一次性跨四五个次版本升级便宜得多。跨 8→9 之前,先按 9.0 release notes 逐条清理被删除的设置与 mapping 参数。

14 · 判断

✔ 该用 ELK 的场景
  • 日志量大且需要全文检索与聚合分析(排障要「搜关键词 + 按维度分组」),这是它不可替代的能力。
  • 需要日志/指标/链路在一个界面里关联(同一时间轴、同一服务名),省掉 N 个系统来回跳的成本。
  • 需要平台级治理能力:多租户空间、字段级/文档级隔离(付费)、ILM 冷热分层、可搜索快照归档。
  • 团队已经吃透 ECS 与 data stream:规范一旦立住,后续所有面板/告警都能复用。
⚠ 保留意见与替代选项
  • 成本不低。省空间的三板斧(logsdb、synthetic _source、frozen/searchable snapshot)里,后两项与实测最狠的省法都涉及付费档或额外调优,预算评估要按实际情况算,别按「免费版 = 全部能力」估算。
  • 版本节奏快。次版本半年左右就终止维护,自管集群的升级是长期负担而不是一次性任务。
  • 纯指标场景不必上 ELK。Prometheus + Grafana 在指标侧的性价比通常更高;ELK 的甜区是「日志 + 检索 + 聚合」。
  • 采集层正处在迁移期。Beats 与 Elastic Agent 双轨并存,团队要明确「新项目只上 Agent」,否则两年后手里会有两套采集配置。

判断

学 ELK 的正确顺序是「链路优先、组件其次」:先搞清一条日志从文件到 Kibana 的每一段(图 2),再逐个补组件细节——因为 90% 的线上问题都能靠这张链路图定位到具体那一段。三件套里 Elasticsearch 是唯一不可替换的(数据在这里、检索在这里),Logstash 在结构化日志时代已可被 ingest pipeline 或采集器自带解析替代,Kibana 则是「你做得好不好别人看不见、做不好别人立刻看得见」的那一层——面板与字段规范(ECS)的实际价值高于任何调优参数。

给自建集群的三条优先动作:(1) 先确认版本是否还在支持窗口,不在就排升级计划;(2) 把 ILM 与索引模板配全,别让索引无限增长(这是最容易拖垮集群、也最容易被忽略的一条);(3) 快照 + 恢复演练,只做快照不做恢复演练等于没有备份。

观察窗口:任何一个自建 ELK 集群,若连续 30 天出现「磁盘水位告警」或「分片数增长超过日志量增长」,说明生命周期策略或分片规划已经失控,此时调参无用,需要重做模板层。

15 · 来源清单

主题一手来源
版本与生命周期elastic.co/blog(Elastic Stack 9.5.5,2026-10-06)· endoflife.date/elasticsearch · endoflife.date/beats
9.0 破坏性变更elastic.co/guide/en/elastic-stack/9.0/release-notes-elasticsearch-9.0.0.html
近实时搜索与 refreshelastic.co/docs/manage-data/data-store/near-real-time-search
data streamelastic.co/docs/manage-data/data-store/data-streams
logsdb 与默认设置elastic.co/docs/manage-data/data-store/data-streams/logs-data-stream-configure · elastic.co/observability-labs(LogsDB 实测)· elastic.co/search-labs(8.19/9.1 性能对比)
分片规划elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards
ILM 阶段与动作elastic.co/docs/manage-data/lifecycle/index-lifecycle-management/index-lifecycle
JVM/堆设置elastic.co/docs/reference/elasticsearch/jvm-settings · elastic.co/docs/deploy-manage/deploy/self-managed/important-settings-configuration
Logstash JVM 与 Java 版本elastic.co/guide/en/logstash/8.19/jvm-settings.html · elastic.co/docs/release-notes/logstash(9.4.0 要求 Java 21)
Logstash PQ / DLQelastic.co/docs/reference/logstash/persistent-queues · elastic.co/guide/en/logstash/8.19/dead-letter-queues.html
Elasticsearch output 插件 data_stream 选项elastic.co/docs/reference/logstash/plugins/plugins-outputs-elasticsearch(v12.1.6,2026-07-06)
ingest pipeline / simulateelastic.co/docs/api/doc/elasticsearch/operation/operation-ingest-simulate
Kibana 版本特性elastic.co/docs/release-notes/kibana(9.1–9.5)
RBAC / 空间与角色elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/kibana-role-management
Elastic Agent 内建 OTel Collector、Beats 迁移elastic.co/docs/reference/opentelemetry · elastic.co/what-is/edot · elastic.co/docs/reference/fleet/migrate-from-beats-to-elastic-agent
订阅档位功能矩阵elastic.co/subscriptions(含 synthetic _source 自 8.17 起为 Enterprise 的脚注)
ECS 字段elastic.co/docs/reference/ecs · elastic.co/guide/en/ecs-logging(log.level / event.dataset / 三个时间戳)