3. 日志采集
日志采集是整个可观测性链条的第一公里。说白了,你后面分析能力再强,数据进不来或者进来的是脏数据,全都白搭。这一章我带你过三个主流工具:Filebeat、Logstash、Fluentd。它们各有各的脾气,也各有各的适用场景。
核心观点:日志采集不是简单的“读文件→发出去”。你要考虑采集性能、断点续传、数据清洗、背压处理。这三个工具,我建议你至少熟练掌握两个。
3.1 Filebeat:轻量级采集器配置
Filebeat 是我个人最常用的采集器。为什么?因为它够轻。一个二进制文件扔上去就能跑,内存占用通常不到 20MB。你想想看,在容器环境里,每个 Pod 塞一个 Filebeat 边车,几乎感觉不到资源开销。
3.1.1 核心配置结构
Filebeat 的配置核心就两块:输入(input)和输出(output)。中间可以加一些简单的处理模块,但别指望它做复杂的数据清洗——那不是它的活。
# filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/api-gateway/*.log
- /var/log/nginx/access.log
fields:
service: api-gateway
env: production
fields_under_root: true
multiline:
pattern: '^\d{4}-\d{2}-\d{2}'
negate: true
match: after
output.elasticsearch:
hosts: ["http://elasticsearch:9200"]
index: "api-logs-%{+yyyy.MM.dd}"
username: "filebeat"
password: "${ES_PWD}"
# 别忘了加这个
logging.level: info
logging.to_files: true
logging.files:
path: /var/log/filebeat
name: filebeat.log
keepfiles: 7
我的经验:fields_under_root: true 这个配置很多人会漏掉。不加的话,你自定义的字段会被嵌套在 fields 对象里,查询时多一层路径,很烦人。
3.1.2 多行日志处理
API 调用审计日志经常跨多行——比如 Java 的堆栈跟踪。Filebeat 的 multiline 配置就是干这个的。
我曾经遇到过一个坑:某个微服务的日志时间戳格式是 yyyy-MM-dd HH:mm:ss,但有个别日志行开头是空格。结果 Filebeat 把堆栈跟踪的每一行都当成独立事件发了出去。后来我加了个 negate: true,问题解决。
3.1.3 断点续传与注册表
Filebeat 用 registry 文件记录每个文件的读取位置。就算进程重启,它也能从断点处继续读,不会丢数据也不会重复发。
# 查看注册表状态
filebeat -e -d "publish"
注意:千万别手贱删了 data/registry 目录。我有个同事在生产环境上干过这事,结果 Filebeat 把过去一周的日志重新发了一遍,ES 集群直接被打满。
3.2 Logstash:管道处理
Logstash 是 ELK 栈里的“老大哥”。它不负责采集——那是 Filebeat 的事——它负责管道处理。说白了,数据进来之后怎么洗、怎么拆、怎么补字段,都是 Logstash 的活。
3.2.1 管道生命周期
Logstash 的管道分三个阶段:输入 → 过滤 → 输出。每个阶段都可以有多个插件串起来。
| 阶段 | 常用插件 | 作用 |
|---|---|---|
| 输入 | beats, http, tcp, file | 接收数据源 |
| 过滤 | grok, mutate, date, json | 解析、清洗、转换 |
| 输出 | elasticsearch, stdout, kafka | 发送到目标 |
3.2.2 Grok 解析实战
API 审计日志最常见的格式是 JSON。但有时候你会遇到非结构化的文本日志,比如 Nginx 的 access log。这时候 Grok 就派上用场了。
# logstash.conf
input {
beats {
port => 5044
codec => json
}
}
filter {
if [service] == "api-gateway" {
grok {
match => {
"message" => "%{IP:client_ip} - %{USER:ident} \[%{HTTPDATE:timestamp}\] \"%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}\" %{NUMBER:status_code} %{NUMBER:bytes}"
}
}
date {
match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
target => "@timestamp"
}
mutate {
remove_field => ["message", "ident"]
}
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "nginx-access-%{+yyyy.MM.dd}"
}
}
关键点:Grok 的正则表达式很吃性能。我建议你在 if 条件里先做分流,只对需要的日志做 Grok 解析,别一股脑全塞进去。
3.2.3 性能调优
Logstash 是 Java 写的,内存消耗不小。我见过有人一台 4G 的机器跑 Logstash,结果 OOM 了。几个调优参数你得记住:
pipeline.workers:默认是 CPU 核数,可以适当调大pipeline.batch.size:默认 125,处理大日志时可以调到 500pipeline.batch.delay:默认 50ms,适当调小可以降低延迟
# jvm.options
-Xms2g
-Xmx2g
# logstash.yml
pipeline.workers: 4
pipeline.batch.size: 500
pipeline.batch.delay: 10
3.3 Fluentd:部署实战
Fluentd 是 CNCF 毕业项目,在 Kubernetes 生态里用得特别多。它的设计哲学和 Logstash 不太一样——Fluentd 强调统一日志层,所有日志先到 Fluentd,再由它路由到不同后端。
3.3.1 架构与部署
Fluentd 的部署方式有两种:DaemonSet(每个节点一个)和边车模式(每个 Pod 一个)。我个人更推荐 DaemonSet,管理成本低。
# fluentd-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: logging
spec:
selector:
matchLabels:
name: fluentd
template:
metadata:
labels:
name: fluentd
spec:
containers:
- name: fluentd
image: fluent/fluentd:v1.16
volumeMounts:
- name: varlog
mountPath: /var/log
- name: config
mountPath: /fluentd/etc
volumes:
- name: varlog
hostPath:
path: /var/log
- name: config
configMap:
name: fluentd-config
3.3.2 配置文件结构
Fluentd 的配置用 <source>、<filter>、<match> 三个指令来组织。和 Logstash 的 input/filter/output 是对应的。
# fluentd.conf
<source>
@type tail
path /var/log/containers/*.log
pos_file /var/log/fluentd-containers.log.pos
tag kubernetes.*
format json
read_from_head true
</source>
<filter kubernetes.**>
@type kubernetes_metadata
annotation_match ["prometheus.io/scrape"]
</filter>
<match kubernetes.**>
@type elasticsearch
host elasticsearch.logging.svc.cluster.local
port 9200
logstash_format true
logstash_prefix api-audit
flush_interval 5s
</match>
避坑指南:pos_file 一定要配置。我曾经在测试环境忘了加,结果每次 Fluentd 重启都重新读一遍所有日志,ES 里全是重复数据。
3.3.3 插件生态
Fluentd 的插件超过 1000 个。常用的几个:
- fluent-plugin-elasticsearch:输出到 ES
- fluent-plugin-kafka:输出到 Kafka
- fluent-plugin-rewrite-tag-filter:动态修改标签
- fluent-plugin-grok-parser:类似 Logstash 的 Grok
3.4 工具选型对比
三个工具怎么选?我列个表给你参考:
| 维度 | Filebeat | Logstash | Fluentd |
|---|---|---|---|
| 资源消耗 | 极低(~20MB) | 较高(~1GB+) | 中等(~200MB) |
| 处理能力 | 简单过滤 | 复杂管道 | 中等复杂度 |
| 插件生态 | 有限 | 丰富 | 非常丰富 |
| K8s 集成 | 好(Operator) | 一般 | 优秀(CNCF) |
| 适用场景 | 边车采集 | 集中清洗 | 统一日志层 |
我的建议:小团队、轻量场景用 Filebeat + ES 就够了。日志量大、清洗逻辑复杂,加一层 Logstash。如果你们已经在用 K8s,Fluentd 是更自然的选择。
3.5 实战:搭建一个完整的采集管道
最后,我带你走一遍完整的搭建流程。假设我们要采集 API 网关的审计日志,经过 Logstash 清洗后存入 ES。
- 部署 Filebeat:在每个 API 网关节点上部署 Filebeat,配置输入为日志文件,输出指向 Logstash 的 5044 端口。
- 配置 Logstash:接收 Beats 输入,用 Grok 解析日志,用 Mutate 清理字段,输出到 ES。
- 验证管道:用
stdout { codec => rubydebug }先输出到控制台,确认解析正确后再切到 ES。 - 监控采集状态:Filebeat 的
filebeat status命令可以查看采集进度,Logstash 的监控 API 在http://localhost:9600。
# 验证 Logstash 管道
curl http://localhost:9600/_node/stats/pipeline?pretty
重要:生产环境一定要加 pipeline.ecs_compatibility: disabled(如果你用旧版 ES)。否则 Logstash 默认的 ECS 模式会把字段名改掉,你的 Kibana 仪表盘可能全崩。