Prometheus snmp_exporter: Monitor Switches and Routers - 夜莺博客

Prometheus snmp_exporter: Monitor Switches and Routers

Routers and switches cannot expose a Prometheus /metrics endpoint, but they all speak SNMP - which is exactly what snmp_exporter bridges: it walks the device over SNMP and re-exposes the results as Prometheus metrics. Together with Grafana this gives you a fully open-source network observability stack: interface utilization, error rates, up/down states and alerting, all in the same platform as your server metrics. This guide covers the exporter setup, the snmp.yml auth/module model, the Prometheus relabel boilerplate, and the PromQL queries that matter.

How It Fits Together

Prometheus --GET /snmp?target=192.0.2.1&module=if_mib--> snmp_exporter
snmp_exporter --SNMP GET/GETBULK--> router / switch

The exporter is stateless: Prometheus pulls http://snmp-exporter:9116/snmp?target=<device>&module=<module>, the exporter polls the device and returns metrics. auth and module are defined in snmp.yml; modules are pre-built walk definitions (the default if_mib covers interface counters for most switches and routers).

The statelessness is the key architectural property, and it drives everything else. Because the exporter holds no configuration about which devices exist, target discovery lives entirely in Prometheus. Add a switch to a file-based service-discovery file and Prometheus starts scraping it — no exporter reload, no restart, no per-device state to get out of sync. Scale the exporter horizontally and Prometheus load-balances across its targets as it would for any other scrape job.

Installing and Running the Exporter

The exporter ships as a single static Go binary, and a container image is published on the GitHub registry. Both work; the systemd approach is common on-premise because it sidesteps a container runtime on the monitoring host.

# Binary install
wget https://github.com/prometheus/snmp_exporter/releases/download/v0.26.0/snmp_exporter-0.26.0.linux-amd64.tar.gz
tar xzf snmp_exporter-0.26.0.linux-amd64.tar.gz
sudo mv snmp_exporter-0.26.0.linux-amd64/snmp_exporter /usr/local/bin/
sudo mkdir -p /etc/snmp_exporter

# Container
docker run -d --name snmp-exporter -p 9116:9116 \
  -v /etc/snmp_exporter/snmp.yml:/etc/snmp_exporter/snmp.yml \
  prom/snmp-exporter:latest
# /etc/systemd/system/snmp_exporter.service
[Unit]
Description=Prometheus SNMP Exporter
After=network-online.target
Wants=network-online.target

[Service]
User=snmp-exporter
Restart=always
ExecStart=/usr/local/bin/snmp_exporter --config.file=/etc/snmp_exporter/snmp.yml
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

The exporter listens on port 9116 by default. Note that it binds SNMP to UDP/161 outbound and requires no inbound SNMP access — a detail that matters when a security review asks why the monitoring host needs to reach every switch.

Configuration

# prometheus.yml
scrape_configs:
- job_name: "snmp-network"
  static_configs:
  - targets:
    - 192.0.2.1      # core-sw-01
    - 192.0.2.2      # core-sw-02
  metrics_path: /snmp
  params:
    module: [if_mib]
  relabel_configs:
  - source_labels: [__address__]
    target_label: __param_target
  - source_labels: [__param_target]
    target_label: instance
  - target_label: __address__
    replacement: snmp-exporter:9116

The relabel block is boilerplate for every SNMP job: it passes the real device address as the target parameter, labels the instance with it, and points the scrape at the exporter. For SNMPv3 devices, define an auth section in snmp.yml (username, auth/protocol+password, priv protocol+password) and pass auth: [<name>] in params - see the exporter README for the generator-based snmp.yml workflow.

Reading the relabel chain line by line is worth doing once, because every SNMP problem you debug later traces back to it:

  • Rule 1 copies the scrape target's address (__address__, currently the device IP) into the URL parameter __param_target, so the exporter knows which device to walk.
  • Rule 2 copies that same address into the instance label, so series are labelled with the device IP rather than with the exporter's address. Without it, every metric would appear to come from the exporter and the data would be useless.
  • Rule 3 overwrites __address__ with the exporter's host and port, redirecting the actual HTTP scrape to the exporter.

If you forget rule 3 the scrape goes straight to the switch on port 80 and fails; if you forget rule 2 the series collide. Both mistakes look like "exporter is broken" when they are actually configuration errors.

Understanding snmp.yml: Auth, Modules and Walks

snmp.yml has two top-level maps. auth entries describe how to talk to a device (community, or SNMPv3 credentials). modules describe what to ask for — the OID walks and how to map them to metric names.

auths:
  public_v2:
    version: 2
    community: public
  snmpv3_authpriv:
    version: 3
    username: prometheus
    security_level: authPriv
    password: authpassword
    auth_protocol: SHA
    priv_password: privpassword
    priv_protocol: AES

modules:
  if_mib:
    walk:
      - 1.3.6.1.2.1.2           # IF-MIB::interfaces
      - 1.3.6.1.2.1.31.1.1       # IF-MIB::ifXTable (64-bit counters)
    metrics:
      - name: ifHCInOctets
        oid: 1.3.6.1.2.1.31.1.1.1.6
        type: counter
        help: The total number of octets received on the interface
        indexes:
          - labelname: ifIndex
            type: gauge
        lookups:
          - labels: [ifIndex]
            labelname: ifDescr
            oid: 1.3.6.1.2.1.2.2.1.2

Two things in that snippet do most of the work. First, the ifXTable walk (1.3.6.1.2.1.31.1.1) provides the 64-bit counters ifHCInOctets and ifHCOutOctets, which you should always prefer on fast links. Second, the lookups block enriches each series with a human-readable ifDescr label sourced from a different table — this is what lets you write alerts and dashboards against ifDescr="GigabitEthernet0/1" instead of memorising ifIndex numbers.

Generating snmp.yml from MIBs

Hand-editing snmp.yml does not scale past a handful of OIDs. The exporter ships a generator that consumes MIB files plus a small YAML selector file and emits the full config.

go install github.com/prometheus/snmp_exporter/generator@latest

# Keep MIB sources and the generator config together
mkdir -p /opt/snmp-generator/mibs
cd /opt/snmp-generator

# Download the MIBs your devices need (IF-MIB, plus vendor MIBs)
curl -O http://www.circitor.fr/Mibs/Mib/I/IF-MIB.mib

# Selector file: module name -> list of MIB walk roots
cat > generator.yml <<'EOF'
auths:
  public_v2:
    version: 2
    community: public
modules:
  if_mib:
    walk:
      - ifName
      - ifHCInOctets
      - ifHCOutOctets
      - ifOperStatus
      - ifAdminStatus
      - ifInErrors
      - ifOutErrors
EOF

# Generate
MIBDIRS=./mibs ./generator generate
cp snmp.yml /etc/snmp_exporter/snmp.yml
systemctl reload-or-restart snmp_exporter

Vendor MIBs (Cisco, Juniper, Arista) are where the interesting metrics live — CPU and memory utilisation, temperature, PSU and fan state, BGP session counts. Crucially, the generator parses MIB files correctly and produces the right OID type and index metadata; hand-written entries frequently get counter-versus-gauge wrong, which produces meaningless rates.

PromQL for Network Monitoring

# interface utilization (use 64-bit counters when available)
rate(ifHCOutOctets{instance="192.0.2.1"}[5m]) * 8 / 1e9   # Gbps out
rate(ifHCInOctets{instance="192.0.2.1"}[5m]) * 8 / 1e9    # Gbps in

# interface error rate
rate(ifInErrors{instance="192.0.2.1"}[5m]) / rate(ifHCInOctets{instance="192.0.2.1"}[5m])

# alert: interface down (ifOperStatus up = 1, down = 2)
expr: ifOperStatus{ifDescr!~"Loopback.*"} == 2
for: 2m

Prefer ifHC* (64-bit) counters where supported - 32-bit counters wrap on busy 10G+ links in minutes. The for: 2m guard on the down alert prevents flap noise.

Utilization needs the interface speed to be expressed as a percentage rather than a raw rate, which means joining in the speed metric:

# percent utilization, in/out, per interface
(rate(ifHCInOctets[5m]) * 8) / (ifHighSpeed * 1e6) * 100

# top 10 busiest interfaces across the estate
topk(10, (rate(ifHCOutOctets[5m]) * 8) / (ifHighSpeed * 1e6) * 100)

# error ratio as a percentage, ignoring near-idle interfaces
100 * rate(ifInErrors[5m]) / rate(ifHCInOctets[5m]) > 0.01
  and rate(ifHCOutOctets[5m]) > 1000000

# device is not answering SNMP at all
up{job="snmp-network"} == 0

Complete Alerting Rules

A minimum viable network alert set covers four failure modes: the device stopped answering, an important interface went down, an interface is saturated, and errors are climbing. All four are cheap to evaluate.

groups:
- name: network-snmp
  rules:
  - alert: SnmpDeviceUnreachable
    expr: up{job="snmp-network"} == 0
    for: 5m
    labels: { severity: critical }
    annotations:
      summary: "SNMP scrape failing for {{ $labels.instance }}"

  - alert: InterfaceDown
    expr: ifOperStatus{ifDescr!~"Loopback.*"} == 2
    for: 2m
    labels: { severity: warning }
    annotations:
      summary: "{{ $labels.instance }} {{ $labels.ifDescr }} is down"

  - alert: InterfaceHighUtilization
    expr: (rate(ifHCOutOctets[5m]) * 8) / (ifHighSpeed * 1e6) * 100 > 80
    for: 15m
    labels: { severity: warning }
    annotations:
      summary: "{{ $labels.ifDescr }} on {{ $labels.instance }} above 80%"

  - alert: InterfaceErrorsIncreasing
    expr: rate(ifInErrors[5m]) > 1
    for: 10m
    labels: { severity: warning }
    annotations:
      summary: "Input errors climbing on {{ $labels.ifDescr }}"

Two tuning notes. The for: durations matter more than the thresholds: a 15-minute guard on utilization keeps a legitimate backup window from paging anyone, and a 2-minute guard on the down alert absorbs a spanning-tree reconvergence. And the Loopback exclusion on the down alert exists because many devices report loopbacks as administratively down by design. Once the rules are written, route and silence them properly using the patterns in our Alertmanager routing, grouping and silences guide.

Scaling to Hundreds of Devices

Static target lists do not survive contact with a real network. Use file-based service discovery so targets are generated by whatever already knows your device inventory — a CMDB export, a CSV, or a script that walks the network.

# prometheus.yml
scrape_configs:
- job_name: "snmp-network"
  file_sd_configs:
  - files:
    - /etc/prometheus/targets/snmp/*.json
    refresh_interval: 5m
  metrics_path: /snmp
  params:
    module: [if_mib]
  relabel_configs:
  - source_labels: [__address__]
    target_label: __param_target
  - source_labels: [__param_target]
    target_label: instance
  - target_label: __address__
    replacement: snmp-exporter:9116
# /etc/prometheus/targets/snmp/core.json
[
  {
    "targets": ["192.0.2.1", "192.0.2.2"],
    "labels": {"site": "dc1", "role": "core", "vendor": "arista"}
  },
  {
    "targets": ["198.51.100.1"],
    "labels": {"site": "dc2", "role": "access", "vendor": "cisco"}
  }
]

Labels added here flow through to every series, so a Grafana variable of site or a role="core" selector works immediately. Three capacity rules of thumb: SNMP scrapes belong in their own Prometheus job with a long scrape_interval (60–120 seconds is plenty for interface counters, and far kinder to device CPUs than 15 seconds); a single exporter instance handles a few hundred devices comfortably, so run two behind a load balancer for availability rather than for throughput; and set an explicit scrape_timeout — an unreachable device will otherwise hold a scrape slot for the full default timeout. If you are running large numbers of series, the storage and query side is worth sizing deliberately — see our VictoriaMetrics single-node versus cluster comparison for when a plain Prometheus TSDB stops being the right choice.

Troubleshooting the Pipeline

When metrics do not appear, isolate the layer. Test the exporter directly with curl before touching Prometheus — it removes the entire scrape configuration from the equation.

# 1. Does the exporter respond, and does it reach the device?
curl -s "http://localhost:9116/snmp?target=192.0.2.1&module=if_mib" | head -30

# 2. Which auth is in use? add &auth=snmpv3_authpriv
curl -s "http://localhost:9116/snmp?target=192.0.2.1&module=if_mib&auth=snmpv3_authpriv" | head

# 3. Can the host reach UDP/161 at all?
snmpwalk -v2c -c public 192.0.2.1 1.3.6.1.2.1.2.2.1.2

# 4. Exporter log while a scrape runs (run with --log.level=debug)
journalctl -u snmp_exporter -f

# 5. Prometheus side: is the target up?
#    http://prometheus:9090/targets  -> check the snmp-network job

Reading the symptom: a curl that hangs and then times out means the exporter cannot reach UDP/161 — a firewall, an ACL or a wrong management address. A curl that returns promptly but with no interface metrics usually means a community-string or SNMPv3 credential mismatch, which the exporter reports as an error in its logs. A curl that works perfectly while Prometheus shows the target down points at rule 3 of the relabel block. And if metrics appear but ifDescr is missing, the lookup OID was not walked.

Operational Notes

  • Start with the if_mib module and a read-only v2c community (public_v2 auth) to prove the pipeline, then move to SNMPv3 (authPriv) - configuration steps for v3 users on Cisco/Junos are in our AAA article and the SNMPv3 guides on this site. The engine-ID and security-level details that trip people up are covered in the SNMPv3 security levels and engine ID walkthrough.
  • When the NMS cannot reach a device at all, work the connectivity problem methodically - the 8-step SNMP troubleshooting flow in our 华为 CE 交换机 SNMP 排障指南 applies to any vendor.
  • Verify the underlying NIC/interface health from the host side with ethtool when exporter numbers disagree with device CLI output.
  • Keep a read-only SNMP credential that is only used by the monitoring system, and never reuse the community string the rest of the estate uses. The exporter stores it in plaintext in snmp.yml, so file permissions on that file are your access control — 0640, owned by the exporter's service account.

原文链接:https://github.com/prometheus/snmp_exporter