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带来的便利了。

下一章,咱们聊聊告警规则怎么设计——什么时候该告警,什么时候不该告警,这里面的门道可不少。