Categraf SNMP 表格采集进阶:table、field、标签继承、过滤和 partial 策略

本文深入介绍 Categraf SNMP 插件的表格采集模型,讲清 table、field、is_tag、inherit_tags、index_as_tag、filters、filters_expression、filters_mode、partial 错误策略、依赖缓存和运行时采集统计。

作者 快猫星云

上一篇文章用 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

如果这些指标上有 ifNameifDescrifAlias 标签,说明标签采集正常。

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 个都能成功,只有 ifAliasifOutDiscards 失败。

历史行为是:

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 字段,例如 ifNameifDescrifAlias
  • 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_ifHCInOctetssnmp_interface_ifOperStatus,再看标签里有没有 ifName

为什么开启 partial 后还是没数据?

如果失败的是 tag、filter、inherit_tags、secondary-index 这类依赖,Categraf 会保守跳过,避免标签错乱。partial 主要保护普通数值字段的部分失败。

filters 写了但没有任何端口?

先去掉 filters,用 --test 看设备真实返回的 ifDescrifNameifAlias。不同厂商接口命名差异很大,正则不能照搬。

dependency_cache_ttl 越大越好吗?

不是。TTL 太大可能让旧标签保留过久。端口描述经常变更的环境,TTL 应更短;设备配置稳定的环境可以适当延长。

是否可以直接配置 oid = “IF-MIB::ifTable” 自动采全表?

可以,但不建议作为生产起点。自动采全表容易带来过多字段和标签,后端压力不可控。更推荐显式列出要采的字段。

14. 生产建议

SNMP 表格配置要先保证“行身份正确”,再追求“字段丰富”。标签错乱比少采几个字段更危险,因为错误标签会误导 Dashboard 和告警。

建议按下面顺序推进:

  1. 先采顶层 sysNamesysUpTimesnmp_up
  2. 再采接口表核心字段:状态、速率、流量、错包;
  3. 确认标签稳定后开启端口过滤;
  4. 开启 partial 和依赖缓存,观察运行时统计;
  5. 最后再扩展硬件、传感器和厂商私有 OID。

不要把所有设备、所有厂商、所有 OID 都塞进一份配置里。SNMP 的可维护性来自分层模板,而不是一份巨大的万能配置。

延伸路径

继续看解决方案和产品对比

如果你正在做监控、可观测性或故障定位相关选型,建议从解决方案和产品对比继续往下看。

快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云