「ELK 日志分析体系:从采集到告警」

讲解 ELK 日志分析体系:Filebeat 与 Logstash 采集解析、索引模板与 ILM 生命周期、日志检索最佳实践、集群监控与告警,并给出 Nginx 访问日志的完整落地案例。

ELK(Elasticsearch + Logstash + Kibana,外加 Beats 采集器)是日志分析的事实标准组合:Filebeat 负责轻量采集,Logstash 负责解析加工,Elasticsearch 负责存储检索,Kibana 负责可视化与告警。本文从采集链路讲到索引生命周期,再落到检索实践与监控告警,帮助你搭起一套生产可用的日志平台。

1. ELK 架构概览

一句话总结: ELK 用 Beats 采集、Logstash 加工、ES 存储、Kibana 消费,各层职责单一、可独立扩展。

1.1 组件分工

组件角色特点
Filebeat轻量采集器内存占用小,部署在业务机
Logstash数据加工管道grok/date/mutate 处理
Elasticsearch存储与检索索引模板、ILM、检索
Kibana可视化与告警Discover、Dashboard、Alerting

日志量小的场景可以只用 Filebeat 直连 Elasticsearch(ES 8 自带 Ingest Pipeline 也能做解析),量大或格式复杂时才引入 Logstash 做缓冲与加工。

1.2 链路拓扑

业务应用/系统 → Filebeat → Kafka(可选缓冲)→ Logstash → Elasticsearch → Kibana

中小规模直接 Filebeat → Logstash → ES 即可;写入峰值高时在中间加 Kafka 削峰,让 Logstash 消费速率与 ES 写入能力解耦。ES 写入跟不上时优先调 bulk 批量大小与刷新间隔,而不是无限堆 Logstash worker。

2. Filebeat 日志采集

一句话总结: Filebeat 以最轻量的方式把日志文件变成事件流,支持多行合并与字段增强。

2.1 基础配置

filebeat.inputs:
  - type: filestream
    id: app-error
    enabled: true
    paths:
      - /var/log/app/*.log
    fields:
      log_type: app-error
    fields_under_root: true
output.logstash:
  hosts: ["logstash01:5044"]

filestream 是 Filebeat 8 推荐的类型,支持跨重启的游标续读(registry 记录读取位置)。fields 给事件附加业务标签,fields_under_root 让标签直接进顶层字段,便于后续按 log_type 分组。

2.2 多行日志合并

filebeat.inputs:
  - type: filestream
    id: java-stacktrace
    paths:
      - /var/log/app/java.log
    parsers:
      - multiline:
          type: pattern
          pattern: "^\\d{4}-\\d{2}-\\d{2}"
          negate: true
          match: after

Java 异常堆栈是多行事件,multiline parser 用「匹配时间戳开头的行」区分事件边界:不匹配的行合并到上一事件末尾(negate + after)。多行合并发生在采集端,比在 Logstash 里合并更省带宽。

2.3 输出缓冲与重试

output.logstash:
  hosts: ["logstash01:5044", "logstash02:5044"]
  loadbalance: true
  bulk_max_size: 1024
  worker: 2

多个 Logstash 主机配 loadbalance 实现客户端负载均衡,bulk_max_size 控制批量大小。Filebeat 自带确认与重试机制,Logstash 不可达时事件留在内存与注册表,恢复后继续发送,业务机无需常驻大缓冲。

3. Logstash 解析与转换

一句话总结: Logstash 用 grok 把非结构化文本切分成结构化字段,再统一时间与类型。

3.1 管道结构

input { beats { port => 5044 } }
filter { grok { ... } date { ... } mutate { ... } }
output { elasticsearch { ... } }

Logstash 管道分 input、filter、output 三段。filter 是核心,grok 负责正则切分,date 把日志字符串时间解析成 ES 的 date 字段,mutate 负责改类型、加字段、去字段。

3.2 grok 切分访问日志

filter {
  grok {
    match => {
      "message" => "%{IPORHOST:client_ip} - - \[%{HTTPDATE:ts}\] \"%{WORD:method} %{URIPATH:uri} HTTP/%{NUMBER:http_version}\" %{NUMBER:status} %{NUMBER:bytes}"
    }
  }
  date {
    match => [ "ts", "dd/MMM/yyyy:HH:mm:ss Z" ]
    target => "@timestamp"
  }
  mutate {
    convert => { "status" => "integer" }
    remove_field => [ "ts" ]
  }
}

grok 用预置模式(IPORHOST、HTTPDATE、WORD、NUMBER)加自定义匹配把整行日志切成分字段。解析失败的行会落进 _grokparsefailure 标签,监控该标签是衡量日志解析质量的常用手段。

3.3 输出到 ES

output {
  elasticsearch {
    hosts => ["http://es01:9200", "http://es02:9200"]
    index => "app-logs-%{+YYYY.MM.dd}"
    pipeline => "app-logs-ingest"
  }
}

index 按天滚动生成索引名,配合第 4 节的 ILM 策略自动管理生命周期。pipeline 参数可以把部分轻量加工(如字段重命名)下沉到 ES Ingest Pipeline,减轻 Logstash 压力。

4. 索引模板与 ILM

一句话总结: 索引模板统一新索引的分片、副本与 mapping,ILM 自动执行滚动、压缩与删除。

4.1 索引模板

{
  "index_patterns": ["app-logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "index.lifecycle.name": "logs-lifecycle",
      "index.routing.allocation.require.box_type": "warm"
    },
    "mappings": {
      "properties": {
        "@timestamp": { "type": "date" },
        "level":  { "type": "keyword" },
        "message": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } },
        "client_ip": { "type": "ip" }
      }
    }
  }
}

模板命中 app-logs-* 的所有新索引,统一分片数与 mapping。日志字段按语义选择类型:IP 用 ip 类型、枚举用 keyword、正文用 text 加 keyword 子字段。模板中的 allocation 把索引引导到 warm 节点,配合冷热架构降成本。

4.2 ILM 生命周期策略

{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": { "max_size": "30gb", "max_age": "1d" }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": { "force_merge": { "max_num_segments": 1 } }
      },
      "delete": {
        "min_age": "30d",
        "actions": { "delete": {} }
      }
    }
  }
}

ILM 把索引生命周期切成 hot/warm/cold/delete 阶段:hot 阶段达到 30GB 或 1 天即 rollover 滚动新索引;7 天后进入 warm 做 force_merge 合并段;30 天后删除。rollover 需配合别名使用,模板中设置 index.lifecycle.name 后策略自动接管。

4.3 滚动别名

{
  "actions": [
    { "add": { "index": "app-logs-000001", "alias": "app-logs-write" } }
  ]
}

写入方只对准别名 app-logs-write,ILM 滚动后新索引自动顶替别名,应用无感知。检索方按时间范围跨多个具体索引,或使用通配 app-logs-* 配合时间过滤,避免别名只包含当前活跃索引的局限。

5. 日志检索最佳实践

一句话总结: 检索效率来自字段设计、过滤器优先与时间裁剪,而非依赖全文扫全库。

5.1 高效检索 DSL

{
  "query": {
    "bool": {
      "filter": [
        { "term":  { "level": "ERROR" } },
        { "range": { "@timestamp": { "gte": "now-1h", "lt": "now" } } }
      ],
      "must": [
        { "match_phrase": { "message": "connection refused" } }
      ]
    }
  },
  "sort": [{ "@timestamp": "desc" }],
  "size": 50
}

时间范围与级别过滤放进 filter 走位图缓存,全文匹配进 must。日志检索大部分是「最近一小时某关键字」,时间裁剪是最大的性能杠杆,跨月全扫只该出现在离线分析。

5.2 关键字选择

日志正文建议字段
服务名/环境keyword 精确匹配
级别/状态码keyword 或 integer
IP 地址ip 类型范围查询
错误消息text 全文 + phrase
调用耗时数值范围过滤

日志正文不要对所有字段都建 text 全文索引:message 建 text,trace_id 建 keyword,耗时建数值。Kibana Discover 默认按全文匹配全文,误把 keyword 字段也当 text 查会拖慢检索。

5.3 常见检索坑

  • 不设时间范围:默认 match_all 全库扫描,检索量被索引总规模拖死。
  • 对 text 做 term 精确匹配:term 打不到词,必须用 match 或 keyword 子字段。
  • 索引名含大写或特殊字符:Kibana 数据视图按模式匹配,索引命名保持小写与连字符。
  • 过度分词导致短语查不到:中文日志要配 IK 分词,否则 match_phrase 命中率低。

6. 监控与告警

一句话总结: 平台可用性监控集群健康与磁盘水位,业务告警基于日志内容触发。

6.1 集群健康与磁盘水位

{
  "cluster": {
    "indices": {
      "data_streams": { "name": "app-logs", "expand_wildcards": "all" }
    }
  },
  "nodes": {
    "summary": { "filter_path": "nodes.*.*,nodes.*.roles" }
  }
}

_cluster/health 看红黄绿状态与未分配分片,_cat/allocation 看磁盘水位。磁盘是日志集群最常见的失联元凶:触发 flood_stage 后只读保护会自动把索引置为只读,预警必须早于水印,磁盘使用率 75% 就要告警。

6.2 基于内容的业务告警

Kibana Alerting 的「Elasticsearch query」规则定时执行一条查询,命中条数超过阈值即触发告警。例如 ERROR 级别日志在过去 5 分钟超过 50 条就告警,或某服务心跳字段超过 10 分钟未出现就告警。

{
  "query": {
    "bool": {
      "filter": [
        { "term": { "level": "ERROR" } },
        { "range": { "@timestamp": { "gte": "now-5m", "lt": "now" } } }
      ]
    }
  }
}

告警规则要带时间窗与节流(Throttle),同一故障 5 分钟内只发一条通知,避免告警风暴。告警通道接钉钉/企业微信/邮件均可,规则越贴近业务特征,值班同学越容易定位。

6.3 自监控

Logstash 的 _node/stats 暴露事件处理速率与积压,Filebeat 的 _cat/health 看采集心跳。ES 的 /_cluster/pending_tasks 与线程池拒绝指标反映写入是否过载;一个健康的日志平台自身也要进监控大盘,而不是等业务反馈「日志断了」。

7. Nginx 访问日志落地案例

一句话总结: 从 Filebeat 采集到 Kibana 看板,一条访问日志走完 ELK 全链路。

7.1 采集端

/var/log/nginx/access.log

Nginx 日志格式先改成 JSON 或带 key 的分隔格式,能省掉大部分 grok 工作量。推荐 Nginx 输出 JSON:status、request_time、remote_addr 等字段天然结构化,Logstash 只做类型转换。

7.2 Logstash 过滤端

filter {
  if [fields][log_type] == "nginx-access" {
    json {
      source => "message"
      target => "nginx"
    }
    date {
      match => [ "[nginx][time_local]", "dd/MMM/yyyy:HH:mm:ss Z" ]
      target => "@timestamp"
    }
    mutate {
      convert => { "[nginx][status]" => "integer" }
      convert => { "[nginx][request_time]" => "float" }
      remove_field => [ "message" ]
    }
  }
}

json filter 直接解析 Nginx 的 JSON 日志行,date 统一时间,mutate 做类型转换并移除原始 message 减小存储。结构化采集把 grok 正则的脆弱性降到最低。

7.3 Kibana 看板

Kibana 上基于 app-logs-* 数据视图建三张核心图表:按小时的请求量折线(date_histogram + count)、按状态码的占比(terms)、按 request_time 的 P90 分位数(percentiles)。再配一张按客户端 IP 的访问排行表,即构成访问日志总览看板。告警规则盯 5xx 比例超过 1% 与 P90 超过阈值,接入值班通知。

8. 总结

一句话总结: ELK 以 Filebeat 采集、Logstash 加工、ES 存储、Kibana 消费,用索引模板与 ILM 管住生命周期,用检索与告警把日志变成可运维的数据资产。

环节要点
架构Beats 采集、Logstash 加工、ES 存储、Kibana 消费
采集Filebeat filestream 续读,multiline 合并堆栈
解析grok/json 结构化,date 统一时间
生命周期模板统一分片与 mapping,ILM 滚动/合并/删除
检索filter 优先、时间裁剪、字段按语义建类型
告警集群健康预警先于水印,内容告警带时间窗
落地JSON 化日志省 grok,看板配 P90 与 5xx
红线不设时间范围全扫、对 text 做 term、磁盘到 90% 才处理

ELK 的价值在把「日志」从排除故障的黑盒变成可检索、可统计、可告警的数据资产。先统一采集与字段规范,再用模板与 ILM 管住索引生命周期,最后用检索与告警把数据用起来。存储与集群相关可阅读《部署运维与备份恢复》《集群分片与高可用架构》,检索语法参考《Query DSL 与相关性打分》。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「elasticsearch」更多文章

  1. 「搜索服务架构:从索引到容错」
  2. 「安全加固与访问控制:从角色到审计」
  3. 「地理空间搜索:从坐标到地图」