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 与相关性打分》。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。