上一篇文章用 IF-MIB 跑通了交换机接口监控:设备连通性、端口状态、流量、带宽利用率、错包和丢包。这篇文章继续往下讲 SNMP 插件里最容易配置错的部分:表格采集。
SNMP 的难点在于很多关键数据不是一个独立 OID,而是一张表。接口、风扇、电源、温度传感器、CPU、内存、磁盘、会话数,都可能以 table 的形式暴露。Categraf 需要把这些表格行转换成 Prometheus 风格指标,这就涉及行索引、字段、标签、过滤、标签继承和部分失败处理。
核心要点
[[instances.field]]用于采集标量 OID,输出为snmp_<name>;[[instances.table]]用于采集表格 OID,输出为snmp_<table.name>_<field.name>。is_tag = true的字段不会输出数值指标,而是成为同一行其他指标的标签。inherit_tags用于把顶层字段采到的标签继承到表格行里,常见做法是把sysName作为source继承给接口表。index_as_tag = true会把 SNMP 行索引写入index标签,便于 PromQL 做向量匹配。filters应用于表格行过滤,适合减少空口、虚拟口、无意义实体带来的时序数量。partial策略能在普通数值字段失败时保留已成功字段,但对 tag、filter、secondary-index、inherited-tag 等依赖保持保守。dependency_cache_ttl只缓存依赖值,不缓存普通指标值;它用于提高短暂依赖读取失败时的稳定性。
1. field 和 table 的边界
SNMP 插件里有两类采集块。
第一类是顶层字段:
[[instances.field]]
oid = "1.3.6.1.2.1.1.3.0"
name = "uptime"
它适合采集标量 OID,例如:
sysUpTime.0;sysName.0;- 设备全局 CPU 使用率;
- 设备全局内存使用率;
- 集群状态、会话数等只有一个值的指标。
如果 name = "uptime",默认输出指标名是:
snmp_uptime
第二类是表格:
[[instances.table]]
name = "interface"
[[instances.table.field]]
oid = "1.3.6.1.2.1.31.1.1.1.6"
name = "ifHCInOctets"
它适合采集每一行都有多个字段的数据,例如接口表。table.name = "interface",字段名为 ifHCInOctets,输出指标名就是:
snmp_interface_ifHCInOctets
这条命名规则很重要。Dashboard、PromQL、告警规则都要以最终输出的指标名为准。
2. is_tag 决定字段是标签还是指标
表格中的字段有两种用途:一类是数值,一类是标签。
例如接口流量是数值:
[[instances.table.field]]
oid = "1.3.6.1.2.1.31.1.1.1.6"
name = "ifHCInOctets"
它会输出:
snmp_interface_ifHCInOctets{ident="10.10.10.11",ifName="GE1/0/1"} 123456
接口名称更适合作为标签:
[[instances.table.field]]
oid = "1.3.6.1.2.1.31.1.1.1.1"
name = "ifName"
is_tag = true
is_tag = true 之后,不会产生 snmp_interface_ifName 指标。它会作为标签挂到同一行其他数值指标上。
这也是很多 SNMP Dashboard 没数据的原因:用户以为 ifName 是指标,实际上它是标签。排查时应该查数值指标,再看标签:
snmp_interface_ifHCInOctets
snmp_interface_ifOperStatus
如果这些指标上有 ifName、ifDescr、ifAlias 标签,说明标签采集正常。
3. 用 inherit_tags 继承设备身份
很多设备地址不是稳定业务名称,告警里只有 10.10.10.11 不够直观。常见做法是采 sysName.0 作为顶层标签:
[[instances.field]]
oid = "1.3.6.1.2.1.1.5.0"
name = "source"
is_tag = true
然后让接口表继承这个标签:
[[instances.table]]
name = "interface"
inherit_tags = ["source"]
这样接口指标会带上:
source="sw-core-01"
inherit_tags 的关键点是:被继承的标签必须来自顶层字段,且顶层字段必须采集成功。开启 partial 后,如果继承标签未知,Categraf 会跳过相关表格行,避免输出缺少设备身份的指标。
4. 为什么建议开启 index_as_tag
SNMP 表格行都有索引。接口表里常见索引就是 ifIndex,但不是所有表都把索引字段作为普通列暴露得足够稳定。
开启:
index_as_tag = true
Categraf 会把行索引写成标签:
index="49"
这对 PromQL 很有用。例如判断“管理状态 up 但运行状态 down”:
snmp_interface_ifAdminStatus == 1
and on (ident, index)
snmp_interface_ifOperStatus != 1
如果不开 index_as_tag,也可以用 ifIndex 标签匹配,前提是你把 ifIndex 配成 is_tag = true:
[[instances.table.field]]
oid = "1.3.6.1.2.1.2.2.1.1"
name = "ifIndex"
is_tag = true
生产环境建议两者都保留:index 用于稳定匹配,ifIndex 便于和设备侧命令、snmpwalk 输出对应。
5. filters 怎么写
SNMP 表格往往很大。如果不做过滤,接口表可能包含大量空口、VLAN、Loopback、Tunnel、Stack、虚拟口和管理口。硬件表也可能包含机框、槽位、模块、传感器、逻辑对象等多种行。
Categraf 支持在 table 下配置 filters:
filters = [
"A:ifOperStatus:^(1|2)$",
"B:ifDescr:^(GigabitEthernet|Ten-GigabitEthernet|HundredGigE|Eth|ge-|xe-|te-)",
"C:ifAlias:.+"
]
filters_expression = "A && (B || C)"
filters_mode = "strict"
每条过滤规则由三段组成:
变量名:字段名:正则表达式
上面的含义是:
A:端口运行状态是 up 或 down;B:接口描述看起来像物理口;C:接口别名非空;- 最终表达式:满足 A,并且满足 B 或 C。
如果不写变量名,也可以使用简化格式:
filters = [
"ifOperStatus:^(1|2)$",
"ifAlias:.+"
]
简化格式默认是 OR 关系。对于生产配置,更推荐完整格式加 filters_expression,可读性更高。
6. filters_mode = strict 的意义
filters_mode = "strict" 表示过滤字段缺失或无法判断时,不要默认放行。对于 SNMP 来说,这是更稳妥的行为。
例如你用 ifAlias 判断“只采配置了别名的业务端口”,但某台设备读取 ifAlias 失败。如果默认放行,就可能把所有端口都采上来,造成时序数量暴涨。strict 模式下,无法判断的行会被跳过。
建议:
- 接口表、硬件表、传感器表都优先使用 strict;
- 首次接入时可以先不加过滤,确认设备返回内容;
- 过滤表达式稳定后再启用 strict;
- 对关键端口不要只依赖
ifAlias,最好结合接口名称或 CMDB 标签。
7. oid 写数字还是 MIB 名称
Categraf 支持数字 OID,也支持通过 MIB 名称翻译,例如:
oid = "IF-MIB::ifHCInOctets"
或:
oid = "1.3.6.1.2.1.31.1.1.1.6"
生产文章和模板里建议优先写数字 OID,原因很直接:
- 不依赖 Categraf 运行环境里是否安装了对应 MIB;
- 不受不同系统 net-snmp MIB 路径影响;
- 复制到容器或最小化系统里更容易工作;
- 排障时可以直接用
snmpwalk验证。
MIB 名称适合用于阅读和维护。可以在注释里保留:
oid = "1.3.6.1.2.1.31.1.1.1.6" # IF-MIB::ifHCInOctets
8. conversion 和 convert_rule
SNMP 返回值不总是规整数字。厂商私有 OID 里经常出现:
34%
fan: 12000 rpm
offline
normal(1)
普通转换可以用 conversion:
[[instances.table.field]]
oid = "1.3.6.1.4.1.XXX.1.1"
name = "fan_speed"
conversion = "float"
如果同一个 OID 正常时返回 34%,异常时返回 offline,可以用 convert_rule:
[[instances.table.field]]
oid = "1.3.6.1.4.1.XXX.1.1"
name = "fan_speed"
conversion = "float"
[[instances.table.field.convert_rule]]
match = "offline"
value = -1
规则按顺序匹配,第一条命中生效。没有命中时回退到字段原有 conversion。这适合硬件状态、风扇转速、温度、功率等厂商私有指标。
9. legacy 和 partial 的区别
表格采集最麻烦的是“部分字段失败”。例如一张接口表配置了 10 个字段,其中 9 个都能成功,只有 ifAlias 或 ifOutDiscards 失败。
历史行为是:
default_table_error_policy = "legacy"
legacy 更接近“整张表强一致”:字段失败可能导致当前表采集失败。优点是行为保守,缺点是网络设备上某个非关键字段抖动时,整张表都可能没有数据。
新版支持:
default_table_error_policy = "partial"
partial 的原则是:普通数值字段失败时,保留其他已经成功采集的字段;但影响标签、过滤、索引、继承标签的依赖未知时,仍然跳过相关行。
适合 partial 的场景:
- 设备型号多,部分字段不是所有设备都支持;
- 某些厂商私有 OID 偶发超时;
- 希望流量、状态这类核心指标不要被低优先级字段拖垮。
不适合盲目 partial 的场景:
- 还没搞清楚每个字段用途;
- 标签字段本身经常失败;
- 过滤条件依赖不稳定;
- Dashboard 和告警对字段完整性要求很高。
推荐做法是全局开 partial,再对非常敏感的表单独使用 legacy:
default_table_error_policy = "partial"
[[instances.table]]
name = "interface"
error_policy = "partial"
[[instances.table]]
name = "critical_state"
error_policy = "legacy"
10. 依赖缓存缓存什么
partial 模式下可以开启依赖缓存:
dependency_cache_ttl = "10m"
dependency_cache_max_entries = 10000
它缓存的是依赖值,不是普通指标值。
会缓存的内容包括:
- tag 字段,例如
ifName、ifDescr、ifAlias; - filter 字段,例如
ifOperStatus; - secondary-index 映射;
- 顶层继承标签,例如
source。
不会缓存的内容包括:
ifHCInOctets这类流量计数器;ifInErrors这类错误计数器;- 温度、风扇转速、电源功率等普通数值。
换句话说,依赖缓存可以帮你在短暂读取不到 ifName 时继续正确标识端口,但不会用旧流量值冒充新流量值。
TTL 不建议过长。接口改名、端口迁移、设备替换后,如果依赖缓存时间太长,旧标签会保留更久。一般从 5 到 10 分钟开始,结合设备变更频率调整。
11. 运行时采集统计怎么看
新版 SNMP 插件会输出一些自身采集状态指标,便于判断 partial 是否在发生、哪些表失败、依赖缓存是否命中。
常见指标包括:
snmp_health_state
snmp_gather_duration_seconds
snmp_partial_table_total
snmp_fatal_table_total
snmp_field_error_total
snmp_dependency_cache_total
snmp_dependency_skipped_rows_total
snmp_dependency_cache_entries
snmp_transport_failure_total
建议至少关注:
snmp_health_state == 0
表示某个 agent 当前处于不健康状态。
表格部分采集次数:
increase(snmp_partial_table_total[10m])
字段错误:
increase(snmp_field_error_total[10m])
依赖缓存事件:
increase(snmp_dependency_cache_total[10m])
如果这些指标持续增长,不代表一定故障,但说明该设备或表格存在不稳定采集点,需要结合 Categraf 日志和具体 OID 排查。
12. 一个推荐的接口表模板
下面是一份更适合作为生产起点的接口表配置:
[[instances.table]]
name = "interface"
inherit_tags = ["source"]
index_as_tag = true
error_policy = "partial"
filters = [
"A:ifOperStatus:^(1|2)$",
"B:ifDescr:^(GigabitEthernet|Ten-GigabitEthernet|TwentyFiveGigE|FortyGigE|HundredGigE|Eth|ge-|xe-|te-|Eth-Trunk)",
"C:ifAlias:.+"
]
filters_expression = "A && (B || C)"
filters_mode = "strict"
[[instances.table.field]]
oid = "1.3.6.1.2.1.2.2.1.1" # ifIndex
name = "ifIndex"
is_tag = true
[[instances.table.field]]
oid = "1.3.6.1.2.1.2.2.1.2" # ifDescr
name = "ifDescr"
is_tag = true
[[instances.table.field]]
oid = "1.3.6.1.2.1.31.1.1.1.1" # ifName
name = "ifName"
is_tag = true
[[instances.table.field]]
oid = "1.3.6.1.2.1.31.1.1.1.18" # ifAlias
name = "ifAlias"
is_tag = true
[[instances.table.field]]
oid = "1.3.6.1.2.1.2.2.1.7" # ifAdminStatus
name = "ifAdminStatus"
[[instances.table.field]]
oid = "1.3.6.1.2.1.2.2.1.8" # ifOperStatus
name = "ifOperStatus"
[[instances.table.field]]
oid = "1.3.6.1.2.1.31.1.1.1.15" # ifHighSpeed
name = "ifHighSpeed"
[[instances.table.field]]
oid = "1.3.6.1.2.1.31.1.1.1.6" # ifHCInOctets
name = "ifHCInOctets"
[[instances.table.field]]
oid = "1.3.6.1.2.1.31.1.1.1.10" # ifHCOutOctets
name = "ifHCOutOctets"
[[instances.table.field]]
oid = "1.3.6.1.2.1.2.2.1.14" # ifInErrors
name = "ifInErrors"
[[instances.table.field]]
oid = "1.3.6.1.2.1.2.2.1.20" # ifOutErrors
name = "ifOutErrors"
首次接入时可以先注释 filters,确认返回行之后再逐步加过滤。
13. 常见问题
为什么查不到 snmp_interface_ifName?
因为 ifName 通常配置成 is_tag = true,它是标签,不是指标。应该查 snmp_interface_ifHCInOctets 或 snmp_interface_ifOperStatus,再看标签里有没有 ifName。
为什么开启 partial 后还是没数据?
如果失败的是 tag、filter、inherit_tags、secondary-index 这类依赖,Categraf 会保守跳过,避免标签错乱。partial 主要保护普通数值字段的部分失败。
filters 写了但没有任何端口?
先去掉 filters,用 --test 看设备真实返回的 ifDescr、ifName、ifAlias。不同厂商接口命名差异很大,正则不能照搬。
dependency_cache_ttl 越大越好吗?
不是。TTL 太大可能让旧标签保留过久。端口描述经常变更的环境,TTL 应更短;设备配置稳定的环境可以适当延长。
是否可以直接配置 oid = “IF-MIB::ifTable” 自动采全表?
可以,但不建议作为生产起点。自动采全表容易带来过多字段和标签,后端压力不可控。更推荐显式列出要采的字段。
14. 生产建议
SNMP 表格配置要先保证“行身份正确”,再追求“字段丰富”。标签错乱比少采几个字段更危险,因为错误标签会误导 Dashboard 和告警。
建议按下面顺序推进:
- 先采顶层
sysName、sysUpTime和snmp_up; - 再采接口表核心字段:状态、速率、流量、错包;
- 确认标签稳定后开启端口过滤;
- 开启
partial和依赖缓存,观察运行时统计; - 最后再扩展硬件、传感器和厂商私有 OID。
不要把所有设备、所有厂商、所有 OID 都塞进一份配置里。SNMP 的可维护性来自分层模板,而不是一份巨大的万能配置。