ELK 日志平台实战——Logstash、Filebeat、Elasticsearch 索引管理
ELK 日志平台实战——Logstash、Filebeat、Elasticsearch 索引管理
适读人群:需要构建日志平台的工程师 | 阅读时长:约20分钟 | 核心价值:从日志采集到 ES 索引管理,搭建一套生产可用的 ELK 平台
说到 ELK,我有个不太愉快的回忆。
那是我第一次搭 ELK 平台,用了一周时间把 Elasticsearch + Logstash + Kibana 都跑起来了,把公司的日志接入进去,在 Kibana 里可以搜索了,当时觉得很自豪。
但两个月之后,Elasticsearch 集群开始频繁报警。磁盘使用率先到了 85%,然后 90%,然后某天凌晨直接写满了,Elasticsearch 进入只读模式,所有日志采集停止。
排查原因:两个月里日志量增长了 4 倍(业务增长),而且我们没有配置任何索引生命周期管理,所有日志都堆在那里,没有任何自动清理机制。
那天凌晨两点,我在紧急手动删索引,删了 3 个小时,才让集群恢复正常。
那之后,"ILM(Index Lifecycle Management)"这四个字母就刻在我心里了。
ELK 架构设计
轻量级采集:用 Filebeat 而不是 Logstash
常见的误解是把 Logstash 部署到每台业务服务器上做日志采集。这有两个问题:
- Logstash 是 JVM 应用,每个实例至少消耗 512MB 内存,100 台服务器就要浪费 50GB 内存
- Logstash 依赖配置重启才能生效,维护成本高
正确的架构:
业务服务器: Filebeat(轻量,Go 实现,~50MB 内存)
↓
Kafka(可选,用于缓冲,防止 ES 写入压力过大)
↓
Logstash(集中部署,做解析转换)
↓
Elasticsearch
↓
KibanaFilebeat 只负责收集文件、发送数据,不做任何解析——这样它非常轻量,对业务服务器几乎没有性能影响。
Filebeat 配置
# /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/myapp/*.log
- /var/log/myapp/*.json
# 多行日志合并(Java 异常栈跨多行)
multiline.type: pattern
multiline.pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}' # 以日期开头才是新的一行日志
multiline.negate: true
multiline.match: after
# 添加主机信息
fields:
service_name: payment-service
environment: production
fields_under_root: true # 字段放在根层级,不要嵌套在 fields 下
# 容器日志
- type: container
enabled: true
paths:
- '/var/lib/docker/containers/*/*.log'
processors:
- add_host_metadata:
when.not.contains.tags: forwarded
- add_cloud_metadata: ~
- add_docker_metadata: ~
# 发送到 Kafka(推荐)
output.kafka:
hosts: ["kafka-01:9092", "kafka-02:9092", "kafka-03:9092"]
topic: 'logs-%{[fields.service_name]}' # 按服务分 topic
partition.round_robin:
reachable_only: false
required_acks: 1
compression: gzip
max_message_bytes: 1000000
# 如果不用 Kafka,直接发给 Logstash
# output.logstash:
# hosts: ["logstash-01:5044", "logstash-02:5044"]
# loadbalance: true
logging.level: warningLogstash 配置:解析 JSON 日志
# /etc/logstash/conf.d/java-app-logs.conf
input {
kafka {
bootstrap_servers => "kafka-01:9092,kafka-02:9092"
topics_pattern => "logs-.*"
group_id => "logstash-consumers"
consumer_threads => 4
codec => "json"
auto_offset_reset => "latest"
}
}
filter {
# 解析 JSON 格式的结构化日志
if [log][flags][multiline] {
# 多行日志,先合并再解析
mutate {
gsub => ["message", "\n", " "]
}
}
json {
source => "message"
target => "parsed"
skip_on_invalid_json => true
}
if "_jsonparsefailure" not in [tags] {
# JSON 解析成功,提取字段
mutate {
rename => {
"[parsed][timestamp]" => "@timestamp"
"[parsed][level]" => "log_level"
"[parsed][message]" => "log_message"
"[parsed][traceId]" => "trace_id"
"[parsed][userId]" => "user_id"
"[parsed][service]" => "service"
}
remove_field => ["parsed", "message"]
}
date {
match => ["@timestamp", "ISO8601", "yyyy-MM-dd'T'HH:mm:ss.SSSZ"]
target => "@timestamp"
}
} else {
# 非 JSON 日志,用正则解析
grok {
match => {
"message" => "%{TIMESTAMP_ISO8601:@timestamp} %{LOGLEVEL:log_level}\s+%{GREEDYDATA:log_message}"
}
tag_on_failure => ["_grokparsefailure"]
}
}
# 根据日志级别设置字段
if [log_level] in ["ERROR", "FATAL"] {
mutate {
add_field => { "is_error" => true }
}
}
# 脱敏:删除可能的手机号、身份证等敏感信息
mutate {
gsub => [
"log_message", "\b1[3-9]\d{9}\b", "***PHONE***",
"log_message", "\b\d{17}[\dXx]\b", "***ID_CARD***"
]
}
}
output {
elasticsearch {
hosts => ["es-01:9200", "es-02:9200", "es-03:9200"]
index => "logs-%{[service]}-%{+YYYY.MM.dd}"
user => "logstash_writer"
password => "${LOGSTASH_ES_PASSWORD}"
# 使用 ILM
ilm_enabled => true
ilm_rollover_alias => "logs-%{[service]}"
ilm_pattern => "{now/d}-000001"
ilm_policy => "logs-policy"
}
}Elasticsearch 索引管理(重点!)
这是我当年踩坑的核心部分,必须认真做好。
ILM Policy 配置
ILM(Index Lifecycle Management)定义索引在整个生命周期里的行为:
PUT _ilm/policy/logs-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "50GB",
"max_age": "1d"
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "7d",
"actions": {
"forcemerge": {
"max_num_segments": 1
},
"shrink": {
"number_of_shards": 1
},
"set_priority": {
"priority": 50
},
"allocate": {
"number_of_replicas": 1
}
}
},
"cold": {
"min_age": "30d",
"actions": {
"set_priority": {
"priority": 0
},
"freeze": {}
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}这个 ILM Policy 的生命周期:
- Hot(活跃):每天或每 50GB 滚动一次新索引,正常读写
- Warm(7天后):合并分片,缩减副本,迁移到性价比更高的存储
- Cold(30天后):冻结索引,只读,进一步降低内存占用
- Delete(90天后):自动删除
Index Template 配置
Index Template 为新创建的索引提供默认配置:
PUT _index_template/logs-template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 2,
"number_of_replicas": 1,
"index.lifecycle.name": "logs-policy",
"index.lifecycle.rollover_alias": "logs-alias",
"index.refresh_interval": "5s",
"index.codec": "best_compression"
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"log_level": { "type": "keyword" },
"service": { "type": "keyword" },
"trace_id": { "type": "keyword" },
"user_id": { "type": "keyword" },
"log_message": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 512
}
}
},
"host": {
"properties": {
"name": { "type": "keyword" },
"ip": { "type": "ip" }
}
}
},
"dynamic": "false" // 禁止动态 mapping,防止 mapping 爆炸
}
}
}dynamic: false 是防止 ES "mapping 爆炸"的关键——如果允许动态 mapping,应用代码里每增加一个新字段,ES 就会自动添加 mapping,字段越来越多,索引越来越臃肿,查询越来越慢。
踩坑实录
踩坑一:Logstash 成为性能瓶颈
日志量峰值时,Logstash 来不及消费 Kafka 里的消息,消息积压越来越多,最终告警"Kafka consumer lag over 1,000,000"。
排查原因:Logstash pipeline 里的 grok 解析是单线程的,CPU 跑满了。
解决方案:
- 增加 Logstash pipeline workers:
# logstash.yml
pipeline.workers: 8 # 根据 CPU 核数设置
pipeline.batch.size: 500 # 每批处理 500 条避免复杂的 grok 正则:grok 里的正则表达式效率很低,对于 JSON 日志直接用
jsonfilter,而不是用 grok 解析。如果日志已经是结构化 JSON,Logstash 里不做解析:让 Filebeat 直接发给 ES,Logstash 只处理需要特殊处理的日志源。
踩坑二:ES 的 field data 导致 OOM
某次我在 Kibana 里对一个 text 类型的字段做了聚合分析(因为字段类型设错了)。ES 开始加载该字段的所有文档值到 JVM 堆(field data),几分钟后 ES 节点 OOM 崩溃。
Text 类型的字段不应该用于聚合/排序。用于聚合的字段应该是 keyword 类型。
防护措施:设置 field data 的内存上限:
PUT _cluster/settings
{
"persistent": {
"indices.breaker.fielddata.limit": "40%"
}
}以及在 index mapping 里关闭不需要聚合的 text 字段的 fielddata:
"log_message": {
"type": "text",
"fielddata": false // 禁止这个字段加载 field data
}踩坑三:日志里有 PII 数据被写入 ES
一次安全审计发现,我们的日志里有用户手机号、部分信用卡号被写入了 Elasticsearch,而 ES 没有做访问控制,公司内任何人都能在 Kibana 里搜到这些数据。
这是一个非常严重的合规问题。
解决方案(上面的 Logstash 配置里已经包含了):
- 在 Logstash 的 filter 里加脱敏处理,用正则替换敏感数据
- 给 Elasticsearch 开启 X-Pack Security,按团队分配读写权限
- 定期审计 Kibana 的访问日志
深度解析:ELK vs Grafana Loki 的选型
在日志平台选型上,近几年 Grafana Loki 越来越受欢迎,很多人在问"ELK 和 Loki 怎么选"。这里做一个实际的对比。
Elasticsearch(ELK 的核心)的特点
Elasticsearch 对日志做全文索引——日志里的每个词都被索引,可以用任意关键词高效搜索。这是它的最大优势:搜索灵活,能支持复杂的查询(模糊搜索、范围查询、聚合分析)。
代价是资源消耗:存储日志需要额外存储索引,通常日志的索引大小是原始数据的 2-5 倍。内存消耗也很大(ES 依赖 JVM 堆内存做缓存)。
Grafana Loki 的特点
Loki 走的是另一条路:不对日志内容做全文索引,只对标签(label)做索引。查询日志时,先用标签过滤出目标日志流,再用正则表达式在结果里搜索关键词。
优势:存储成本低(没有全文索引,压缩后的日志体积非常小),和 Prometheus 的查询语法(LogQL)相似,与 Grafana 集成无缝。
劣势:不支持任意字段的高效搜索,如果标签设计不好,某些查询会非常慢。
我的选型建议
如果你的团队已经有 ES 集群,并且用于全文搜索或其他业务,顺手接日志是合理的,但要做好 ILM 和索引管理。
如果是新建日志平台,并且团队已经用 Grafana + Prometheus 栈,Loki 是更轻量的选择,运维成本低,与已有监控体系集成自然。
如果对日志的搜索需求复杂(需要按任意字段搜索,需要复杂聚合),ES 更合适。
深度解析:日志的采样与存储成本控制
日志平台最常见的运维问题是存储成本失控——日志量增长速度超出预期,磁盘越来越快,存储账单越来越高。
解决存储成本问题,有几个层次:
第一层:日志级别控制
生产环境只保留 INFO 以上的日志,DEBUG 日志只在需要时临时开启。一个服务如果每秒记录 1000 条 DEBUG 日志,和每秒记录 100 条 INFO 日志,存储成本差 10 倍。
这个优化最简单,收益最大,应该是第一步。
第二层:日志采样
对于高频但价值较低的日志(比如健康检查接口的访问日志、定时任务的成功日志),可以做采样——只记录其中 10%,但保留所有 ERROR 和 WARN 日志。
在 Logstash 里实现采样:
filter {
if [uri] == "/actuator/health" and [status] != "500" {
# 健康检查成功的日志只保留 1/10
ruby {
code => "event.cancel if rand > 0.1"
}
}
}第三层:分级存储
热数据(最近 7 天)放 SSD,访问速度快;温数据(7-30 天)放 HDD;冷数据(30-90 天)放对象存储(S3/OSS)。配合 ES ILM 自动迁移,存储成本可以降低 60-80%。
第四层:日志压缩
ES 的 index.codec: best_compression 配置,使用 DEFLATE 压缩算法,比默认的 LZ4 压缩率更高,但 CPU 消耗略多。对于写入频率不是极高的日志,这个配置是值得的。
Kibana 实用配置
保存好用的 KQL 查询:
# 查找指定服务的 ERROR 日志
service: "payment-service" AND log_level: "ERROR"
# 查找某个 trace 的所有日志
trace_id: "abc123def456"
# 查找包含异常栈的日志
log_message: "Exception" AND NOT log_message: "Test"
# 最近 5 分钟的所有 ERROR
log_level: "ERROR" AND @timestamp > now-5mDashboard 配置告警(Kibana Alerting):
Kibana 7.7+ 支持基于 ES 查询的告警,可以当 ES 里 ERROR 日志数量突然增多时发 Slack 通知,这是日志平台的最后一道保障。
深度解析:ELK 与 Grafana Loki 的选型
ELK Stack(Elasticsearch + Logstash + Kibana)是日志平台的主流选择,但近年来 Grafana Loki 作为一个新的选项越来越受到关注,特别是在云原生场景下。理解它们的差异,有助于做出合适的选型。
ELK 的设计理念
ELK 的核心是 Elasticsearch 的全文索引能力。日志被采集后,Elasticsearch 会对每个字段建立倒排索引,使得任意字段的精确搜索和聚合都非常快。你可以写复杂的 Elasticsearch Query DSL,做多字段过滤、统计分析、Top-N 查询,Kibana 提供了友好的界面来可视化这些查询。
这个设计的代价是存储开销高。Elasticsearch 的索引结构会让实际存储占用是原始日志的 2-5 倍,加上副本(生产环境通常至少 1 副本),实际存储成本相当高。对于日志量大(每天 TB 级别)的团队,存储成本是不可忽视的。
Loki 的设计理念
Grafana Loki 的设计理念是"像 Prometheus 一样做日志"。它不对日志内容做全文索引,只索引标签(Labels),日志内容原文存储(通常压缩后放 S3 或 GCS)。查询时,先用标签过滤出相关的日志流,再在内容里做全文搜索。
这个设计的好处是存储成本极低——不建全文索引,压缩率高,存储成本可以是 ELK 的十分之一甚至更低。在云上用 S3 存日志,基本上可以"免费"保留几年的历史数据。
代价是查询能力受限。Loki 的聚合分析能力远不如 Elasticsearch,复杂的统计查询需要 LogQL(Loki 的查询语言)处理,学习曲线有一定陡度,而且对日志内容的全文搜索性能在数据量极大时会比 Elasticsearch 慢。
如何选
判断核心需求:如果你最重要的用例是"出了问题,快速找到特定请求的日志",两者都能满足。如果你有大量基于日志的业务分析需求(每天有多少用户触发了某类事件、某类错误的趋势),ELK 更合适。如果你的主要需求是"低成本存储海量日志,偶尔查一下",Loki 更经济。
已经有 Grafana + Prometheus 的团队,加 Loki 是很自然的延伸——所有监控在一个界面,运维统一。没有 Grafana 生态的团队,ELK 是更成熟的选择,Kibana 的功能比 Grafana 对日志的支持更完善。
深度解析:日志平台的扩展性设计
日志平台一旦建立,往往就成了团队的基础设施,持续运行几年。因此扩展性设计很重要——不只是"当前能用",而是"随着业务增长,能支撑未来两三年的日志量增长"。
Elasticsearch 集群的横向扩展
Elasticsearch 是天然分布式的,通过增加节点来扩容。一个成熟的 ELK 平台应该从一开始就考虑节点分工:Master 节点(负责集群协调,不存数据),Data 节点(存储数据,处理查询),Ingest 节点(数据预处理)。
对于中型团队(日志量每天 100GB-1TB),建议 3 个 Master 节点(奇数,保障选主)、3-5 个 Data 节点,Ingest 节点按 Logstash 的压力决定。这种分工能避免 Master 节点因为查询压力过大而出现问题,也让 Data 节点可以独立扩容。
日志的分级存储策略
不是所有日志都需要放在高速存储里。热数据(最近 3-7 天,高频查询)放在 SSD Data 节点;温数据(7-30 天,偶尔查询)可以放在 HDD 节点(成本低);冷数据(30 天以上)压缩后归档到 S3 或 MinIO,用 Elasticsearch 的 Frozen Index 功能在需要时解冻查询。
这种分级存储策略可以把日志存储成本降低 60-80%,同时保留了对历史数据的查询能力(解冻速度较慢,但合规审计用不需要实时响应)。ILM 策略里的 cold 阶段和 delete 阶段就是为这个场景设计的。
Logstash vs Filebeat 的角色分工
一个常见的误解是把 Logstash 和 Filebeat 当作可以互换的选择。实际上它们定位不同:Filebeat 是轻量级的日志采集 agent,资源消耗极小,适合部署在每台服务器上;Logstash 是重量级的数据处理管道,有丰富的 filter 插件,适合做复杂的日志解析和转换,但资源消耗大,不适合每台服务器都部署一个。
生产环境的常见架构是:每台服务器部署 Filebeat 采集日志,发到中央的 Logstash 集群(或者 Kafka 作为缓冲),Logstash 做解析和过滤,最终写入 Elasticsearch。Filebeat 负责采集,Logstash 负责处理,各司其职。
这种架构的好处是:服务器上的 Filebeat 资源占用可以控制在 50MB 内存以下,不影响业务进程;Logstash 集群可以独立扩缩容,应对高峰期的日志洪峰;即使 Logstash 出了问题,Filebeat 会缓存日志,等 Logstash 恢复后继续发送,不会丢数据。
总结
ELK 平台的成功,30% 在技术实现,70% 在运营管理。
ILM 和索引管理是运营的核心——没有 ILM 的 ELK 平台,迟早会遇到磁盘撑爆的问题。日志的价值在于被查询,查询的前提是数据被正确采集、解析和存储。把这三个环节做好,ELK 平台才真正发挥价值,而不只是一台昂贵的"日志仓库"。
另外,日志平台是数据平台,数据安全要认真对待:脱敏、访问控制、审计日志,这些不是可选项。日志里往往包含用户的敏感行为数据,泄露的后果不只是技术问题,也是合规和法律问题。做好日志平台的安全设计,是对用户负责,也是对公司负责。把技术实现和运营管理同等重视,才是日志平台从"能用"到"好用"再到"长期稳定运行"的完整路径。选型是起点,运营才是终点,不要在建设阶段投入大量资源,却在运营阶段随意敷衍。
