3. 数据采集层:Agent部署、日志采集、API接口对接、SNMP Trap接收
数据采集层是整个监控系统的「眼睛」和「耳朵」。说白了,这一层要是没做好,后面再牛的告警算法、再炫的仪表盘,都是空中楼阁。我做了这么多年运维,见过太多团队在采集层偷懒,结果线上出问题时数据对不上,那叫一个抓瞎。
今天咱们就把这四块东西掰开揉碎讲清楚:Agent怎么部署、日志怎么采、API怎么对接、SNMP Trap怎么收。
3.1 Agent部署:轻量、安全、可管理
Agent是部署在被监控机器上的采集程序。我个人习惯把它分成两类:侵入式Agent和非侵入式Agent。
| 类型 | 典型代表 | 优点 | 缺点 |
|---|---|---|---|
| 侵入式Agent | Telegraf、Prometheus Node Exporter | 采集指标丰富,可自定义插件 | 占用系统资源,需维护版本 |
| 非侵入式Agent | SNMP、IPMI、SSH远程采集 | 无需安装,对业务零影响 | 采集粒度粗,性能开销在管理端 |
我在项目中遇到过一种情况:某团队给2000台服务器全装了侵入式Agent,结果每台Agent默认开启了一堆不必要的插件,CPU占用直接飙了5%。嗯,这里要注意——Agent部署的第一原则:只采集你需要的指标。
- 使用容器化部署(Docker + systemd),方便版本回滚
- Agent配置统一由配置中心下发,不要手动改每台机器的配置文件
- 开启Agent自身的健康检查接口,监控系统要能监控自己
下面这张图是我常用的Agent部署架构,你可以参考一下:
3.2 日志采集:从混乱到有序
日志采集比指标采集麻烦得多。指标是结构化的,日志是非结构化的。你想想看,同样是「连接超时」,不同开发写的日志格式可能完全不一样。
我常用的日志采集方案是 Filebeat + Logstash + Elasticsearch 这条链路。不过现在也有不少人直接用 Vector 或 Fluentd,性能更好。
日志采集的核心原则:
- 所有日志必须包含时间戳、日志级别、服务名、TraceID
- 日志文件必须按天或按小时滚动,避免单文件过大
- 采集端必须做限流,防止日志洪峰打挂采集管道
我曾经遇到过一件事:某次大促,业务日志量突然暴涨10倍,Filebeat没做限流,直接把Logstash打OOM了。从那以后,我所有日志采集配置里都会加这么一段:
# Filebeat 限流配置示例
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/app/*.log
# 每秒最多采集1000行
harvester_limit: 1000
# 背压机制:当输出阻塞时暂停采集
backoff.init: 1s
backoff.max: 60s
output.logstash:
hosts: ["logstash:5044"]
# 启用压缩,减少网络带宽
compression_level: 6
# 批量发送,提高吞吐
bulk_max_size: 2048
嗯,这里有个小技巧:日志采集一定要做「采样」和「降级」。比如DEBUG日志可以全量采集,但ERROR日志必须全量保留。我习惯在Logstash里做过滤,把WARN以下的日志直接丢弃,只保留ERROR和业务关键日志。
3.3 API接口对接:标准化是王道
API对接这块,说白了就是「别人把数据准备好了,你去拉」。最常见的场景是对接云厂商的监控API、对接内部CMDB、对接第三方SaaS服务。
我个人习惯把API对接分成三步走:
- 认证与鉴权:、OAuth2、JWT,选一种就行。别搞太复杂,但也不能裸奔。
- 数据拉取:全量拉取还是增量拉取?全量适合数据量小的场景,增量适合大数据量。我一般先用全量做一次基线,后面全走增量。
- 数据转换:把第三方API返回的数据格式,转成内部统一的指标模型。这一步最容易踩坑。
- 第三方API可能有调用频率限制,一定要做重试和退避
- API返回的数据可能包含空值或异常值,采集层要做数据清洗
- 接口变更时要有版本管理,我见过API升级后字段名变了,采集直接崩了
下面是一个简单的API对接代码示例,我用Python写的,你可以参考:
import requests
import time
from typing import Dict, List
class APICollector:
def __init__(self, base_url: str, : str):
self.base_url = base_url
self.headers = {"Authorization": f"Bearer {}"}
self.session = requests.Session()
self.session.headers.update(self.headers)
def fetch_metrics(self, endpoint: str, params: Dict = None) -> List[Dict]:
"""拉取指标数据,带重试和退避"""
max_retries = 3
for attempt in range(max_retries):
try:
resp = self.session.get(
f"{self.base_url}/{endpoint}",
params=params,
timeout=10
)
resp.raise_for_status()
return resp.json().get("data", [])
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt) # 指数退避
return []
def transform(self, raw_data: List[Dict]) -> List[Dict]:
"""转换为内部统一格式"""
transformed = []
for item in raw_data:
transformed.append({
"metric": item.get("name"),
"value": float(item.get("value", 0)),
"timestamp": int(time.time()),
"tags": {
"host": item.get("host"),
"region": item.get("region", "unknown")
}
})
return transformed
3.4 SNMP Trap接收:被动采集的艺术
SNMP Trap跟前面几种方式不太一样。Agent和API都是主动去拉数据,而Trap是设备主动推数据给你。说白了,它是「被动采集」。
我最早接触SNMP Trap是在管网络设备的时候。交换机、路由器、防火墙出了故障,会主动发一个Trap报文到你的Trap Receiver。你想想看,如果靠轮询去查设备状态,等发现故障的时候黄花菜都凉了。
SNMP Trap的接收流程大概是这样的:
- 设备配置Trap目标地址(你的Receiver IP)
- 设备发生事件时,发送UDP报文到Receiver的162端口
- Receiver解析报文,提取OID、Value、时间戳
- 将解析结果转成内部事件,写入消息队列
我的SNMP Trap实践建议:
- 使用 snmptrapd 作为Receiver,配合 snmpTT 做事件格式化
- 一定要配置Trap的过滤规则,否则设备的风扇告警能把你刷屏
- Trap报文是UDP的,可能会丢包。建议在发送端做重传,或者在Receiver端做去重
我曾经遇到过一个问题:某厂商的防火墙每5分钟发一次「设备正常运行」的Trap,结果Receiver没做去重,告警系统每5分钟收到一条「设备正常」的告警。嗯,这其实是个设计缺陷——Trap只应该发异常事件,不要发心跳。
下面是一个简单的snmptrapd配置示例:
# /etc/snmp/snmptrapd.conf
# 允许所有来源的Trap
authCommunity log,execute,net public
# 格式化输出
format2 %V\n%v\n%.4y-%.2m-%.2l %.2h:%.2j:%.2k %A [%B] %N\n\t%W Trap: %q\n\t%v\n
# 执行外部脚本处理Trap
traphandle default /usr/local/bin/trap_handler.sh
# 忽略某些OID(比如设备心跳)
ignoreOID .1.3.6.1.4.1.9.9.43.1.1.1.0
最后说一句:数据采集层是整个监控系统的地基。Agent部署要轻量,日志采集要限流,API对接要标准化,SNMP Trap要去重。这四块做好了,后面的数据处理和告警才能站得住脚。