3、日志采集与聚合:使用Filebeat + ELK采集设备端与云端日志
日志这东西,平时没人搭理它,一出问题它就是救命稻草。我在做OTA升级系统运维那几年,最怕的就是设备升级到一半挂了,然后啥日志都没有。你想想看,几十万台设备分布在各地,每台都去现场连串口?不现实。
所以这一章,咱们就聊聊怎么把设备端和云端的日志,统一采集、聚合起来。说白了,就是用Filebeat + ELK这套组合拳,把散落在各地的日志,全部拉到一块儿,方便查、方便看、方便报警。
3.1 为什么选Filebeat + ELK?
市面上日志采集工具不少,Logstash、Fluentd、Filebeat……我个人习惯是:设备端用Filebeat,云端用Logstash。为什么这么搭?
- Filebeat轻量:它就是个Go写的二进制文件,资源占用极低。在嵌入式设备上跑,内存占用也就十几MB,CPU几乎没影响。我在项目里遇到过,有些设备只有64MB内存,跑Logstash直接OOM,换成Filebeat稳得很。
- ELK生态成熟:Elasticsearch做存储和搜索,Kibana做可视化,Logstash做过滤和转发。这套组合在业界用了很多年,坑基本都被踩平了。
- 断点续传:Filebeat内部有个叫"harvester"的机制,它会记录每个日志文件的读取位置。设备断电重启?没事,它接着上次的位置继续读,不会丢日志。
核心思路:设备端只负责"推"日志,云端负责"收"和"处理"。设备端越简单越好,云端越灵活越好。
3.2 整体架构长什么样?
嗯,先画个图(文字版):
设备端(嵌入式Linux)
└─ Filebeat(采集日志文件)
└─ 发送到 → Kafka(消息队列)
└─ Logstash(消费Kafka,过滤、解析)
└─ Elasticsearch(存储)
└─ Kibana(可视化、告警)
为什么中间要加个Kafka?我吃过亏。早期架构是Filebeat直接推给Logstash,结果有一次云端Logstash挂了,日志全丢了。加了Kafka之后,Logstash挂了也不怕,Kafka里存着,恢复后继续消费。说白了,Kafka就是个缓冲层,扛得住流量洪峰。
3.3 设备端:Filebeat配置实战
设备端通常是嵌入式Linux,比如OpenWrt、Yocto、Buildroot之类的。Filebeat的配置很简单,核心就两个部分:输入(input)和输出(output)。
3.3.1 输入配置
设备端的日志文件,一般放在/var/log/下。比如OTA升级日志叫ota_update.log,系统日志叫syslog。配置如下:
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/ota_update.log
- /var/log/syslog
fields:
device_id: "${DEVICE_ID}"
env: "production"
fields_under_root: true
multiline:
pattern: '^\d{4}-\d{2}-\d{2}'
negate: true
match: after
这里有个细节:multiline。OTA升级日志经常是多行的,比如一个异常栈跟踪。如果不做多行合并,一行一个事件,那栈信息就被拆得七零八落。我配置的是"以日期开头"的行作为新事件开始,其他行都合并到上一行。嗯,这个坑我踩过,不配的话查问题能查到崩溃。
3.3.2 输出配置
输出到Kafka:
output.kafka:
hosts: ["kafka1:9092", "kafka2:9092"]
topic: "ota-device-logs"
partition.round_robin:
reachable_only: true
compression: gzip
max_message_bytes: 1048576
为什么要压缩?设备端上行带宽通常很有限,有些4G模块流量还贵。gzip压缩后,日志体积能减少70%以上。我在一个项目里测过,不压缩一天走500MB流量,压缩后不到150MB。
小技巧:设备ID一定要带上。我习惯用fields字段把device_id注入到每条日志里,这样在Kibana里就能按设备ID搜索,定位问题快得多。
3.4 云端:Logstash消费与解析
Logstash这边,主要做三件事:消费Kafka、解析日志、写入ES。
3.4.1 消费Kafka配置
input {
kafka {
bootstrap_servers => "kafka1:9092,kafka2:9092"
topics => ["ota-device-logs"]
group_id => "logstash-ota-group"
codec => "json"
consumer_threads => 4
}
}
这里consumer_threads我设了4,因为Kafka的分区数一般是3~6个,线程数跟分区数匹配,消费效率最高。设多了反而浪费资源。
3.4.2 日志解析
设备端日志格式五花八门。有的用JSON,有的用纯文本。我建议设备端统一输出JSON格式,解析起来最省事。但现实是,很多老设备就是纯文本。那就用grok来解析:
filter {
if [fields][env] == "production" {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} \[%{LOGLEVEL:level}\] %{GREEDYDATA:message}" }
}
date {
match => ["timestamp", "ISO8601"]
target => "@timestamp"
}
}
}
我曾经遇到过一个坑:设备端的时区没统一,有的用UTC,有的用CST。结果在Kibana上看时间线,日志顺序全是乱的。后来我在Logstash里强制把时间字段转成UTC,再在Kibana里按用户时区显示,才解决。
3.5 Elasticsearch:索引与生命周期
日志数据量很大,一天可能几十GB。如果不做管理,ES磁盘很快爆掉。我建议按天建索引,并配生命周期策略:
PUT _ilm/policy/ota_logs_policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_size": "50GB",
"max_age": "1d"
}
}
},
"delete": {
"min_age": "30d",
"actions": {
"delete": {}
}
}
}
}
}
这个策略的意思是:索引超过50GB或1天就滚动,30天后自动删除。嗯,保留30天基本够用了。OTA升级的问题,一般7天内就能定位完,留30天是为了复盘和审计。
3.6 Kibana:可视化与告警
日志进了ES,Kibana就是咱们的"作战指挥室"。我一般会建这么几个视图:
- 升级成功率趋势图:按小时统计成功/失败次数,一眼看出有没有异常波动。
- 错误日志分布:按设备型号、固件版本聚合,看看哪个版本问题最多。
- 设备离线告警:如果某台设备超过30分钟没上报日志,触发告警。
告警这块,Kibana自带的Alerting功能就够用。比如:
规则:错误日志数量 > 100次/5分钟
动作:发送企业微信通知 + 创建Jira工单
我曾经有一次,凌晨3点被告警吵醒——某个新固件版本导致大批设备升级失败。要不是有这套告警,等到早上上班才发现,那损失就大了。
3.7 避坑指南
我曾经踩过的几个坑,你一定要注意:
- 日志轮转问题:设备端的logrotate可能会在Filebeat还没读完时就切文件。解决办法:Filebeat配置
close_inactive为5分钟,确保文件被切之前已经读完。 - 网络抖动:设备端网络不稳定,Kafka连接经常断。Filebeat有内置的重试机制,默认等1秒、2秒、4秒……指数退避。别改太激进,否则会把Kafka打爆。
- 磁盘写满:Filebeat有个"registry"文件,记录读取位置。如果磁盘满了,registry写不进去,重启后会重复读日志。建议给
/var/lib/filebeat单独分区,或者监控磁盘使用率。
3.8 总结
日志采集与聚合,说白了就是三个字:收、存、查。Filebeat负责"收",Kafka负责"缓冲",ELK负责"存和查"。这套架构我用了好几年,稳定可靠。你只要把设备端的Filebeat配好,云端的Logstash解析规则写对,剩下的就是享受Kibana带来的便利了。
下一章,咱们聊聊告警规则怎么设计——什么时候该告警,什么时候不该告警,这里面的门道可不少。