📶

Prometheus SNMP ExporterでSNMP WalkのデータをSplunkに送る

2024/06/06に公開

はじめに

ネットワーク機器の監視はいまだにSNMPが主流ですよね。
CiscoなどがTelemetryを頑張っているようですが複数の種類が混在する環境ではSNMPに頼らざるを得ないです。

Splunkでも同じくネットワーク機器の状況を分析するにはSNMPが主要な選択肢の一つになります。
※その他にもマネージャーがあればAPIで取れる場合もあります
※ネットワーク通信の分析にはNetflowやStream(パケットキャプチャ)がいいです

じゃあSNMP取ろうかとなると、いくつか選択肢が考えられます。

  • snmpwalkをホスト上で実行ファイル出力し結果をUnivesal Forwarderで送信する
    シンプルではありますが、ある程度のスクリプト開発が必要、イベントとして入ってきてしまう、IF名が個別に得られるので結合するのに手間がかかる、と言った懸念点はあります。
    台数が多くなる場合のスケーラビリティも注意が必要です。

  • Splunk Connect for SNMPを使う
    https://splunk.github.io/splunk-connect-for-snmp/main/
    非常に高機能でsnmpwalkを実行したりSNMP Trapを受信もできます。
    1で見られたようイベントではなくメトリクスとして送信され、またIF名も自動的に結合されます。MIBも数千種類登録されています。
    そして、これはなんとKubernetes (microk8s) 上で動作します。
    そのためにスケーラビリティは問題ないのですが、いかんせんKubernetesの運用知識が必要というハードルもあります。
    (Docker Compose版もβ版で開発中のようではあります)

  • 既存の監視ツールから取得
    ZabbixとかPRTGとか、いい感じにエクスポートできるものがあれば。

  • Prometheus SNMP Exporterを使う
    今回のお話です。
    Prometheus SNMP Exporterはsnmpwalkを実行できるPrometheus Exporterの一種です。
    ある程度高機能で、メトリクスとして取り込め、IF名も結合されます。拡張MIBはセルフで登録できます。
    Prometheus本体を用意せずともこのSNMP ExporterがあればSplunkにデータ送信することができます。
    本記事ではその方法を見ていこうと思います。

Prometheus SNMP Exporter

インストール

Docker Imageを利用するか、パッケージを利用します。

バイナリを使用する場合はsystemdで自動起動するようにしておきましょう。

パッケージを使う場合のコマンドはこちらです(Ubuntuの例)。

# パッケージの取得と展開
wget https://github.com/prometheus/snmp_exporter/releases/download/v0.26.0/snmp_exporter-0.26.0.linux-amd64.tar.gz 
sudo tar xvzf snmp_exporter-0.26.0.linux-amd64.tar.gz -C /opt/
sudo mv /opt/snmp_exporter-0.26.0.linux-amd64 /opt/snmp_exporter

# prometheusユーザー作成(割愛可)
sudo useradd prometheus
sudo passwd prometheus

# systemd登録 Userは適宜置き換え
sudo tee /etc/systemd/system/snmp-exporter.service > /dev/null <<EOF
[Unit]
Description=SNMP Exporter
After=network-online.target

# This assumes you are running snmp_exporter under the user "prometheus"

[Service]
User=prometheus
Restart=on-failure
ExecStart=/opt/snmp_exporter/snmp_exporter --config.file=/opt/snmp_exporter/snmp.yml

[Install]
WantedBy=multi-user.target
EOF

# 起動
sudo systemctl daemon-reload
sudo systemctl enable snmp-exporter
sudo systemctl start snmp-exporter

# 起動確認
curl localhost:9116/metrics

Generator

次は設定ファイルを作成します。
SNMP Exporterの設定(snmp.yml)自体は非常に長大で複雑です(サンプル)。

たぶん人間が作れる代物ではないので、generatorが用意されています。
https://github.com/prometheus/snmp_exporter/tree/main/generator

Goでのビルドが必要なのでGoが入っていない場合は入れておきます。

wget https://golang.org/dl/go1.22.4.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.22.4.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin

手順通りビルドします。

# Debian-based distributions.
sudo apt-get install unzip build-essential libsnmp-dev # Debian-based distros
# Redhat-based distributions.
sudo yum install gcc make net-snmp net-snmp-utils net-snmp-libs net-snmp-devel # RHEL-based distros

git clone https://github.com/prometheus/snmp_exporter.git
cd snmp_exporter/generator
make generator mibs

次にビルドされたディレクトリにgenerator.ymlを作成します。
見ればだいたい分かると思いますが、authsで認証情報、modulesでsnmpwalk対象を指定します。
lookupsでifIndexに対してIF名を付与することができます。
(Generatorのページに詳細情報があります)
このように定義を作っておいて、後で取得先デバイスに対して任意に割り当てられます。

auths:
  general_2c_auth:
    version: 2
    community: public

modules:
  general_2c_module:
    walk:
      - sysName
      - sysUpTime
      - sysDescr
      - sysLocation
#     IF-MIB系を全部取るなら
#     - ifTable
#     - ifXTable 
      - ifNumber
      - ifIndex
      - ifDescr
      - ifAlias
      - ifAdminStatus
      - ifOperStatus
      - ifMtu
      - ifSpeed
      - ifPhysAddress
      - ifInNUcastPkts 
      - ifOutNUcastPkts 
      - ifHCInOctets
      - ifHCOutOctets
      - ifHCInUcastPkts
      - ifHCOutUcastPkts
      - ifHCInMulticastPkts
      - ifHCOutMulticastPkts
      - ifHCInBroadcastPkts
      - ifHCOutBroadcastPkts
      - ifInDiscards
      - ifOutDiscards
      - ifInErrors
      - ifOutErrors
      - ifInUnknownProtos
    lookups:
      - source_indexes: [ifIndex]
        lookup: ifAlias
      - source_indexes: [ifIndex]
        lookup: ifDescr
      - source_indexes: [ifIndex]
        lookup: ifName
      - source_indexes: [ifIndex]
        lookup: ifPhysAddress 
    overrides:
      ifAlias:
        ignore: true # Lookup metric
      ifDescr:
        ignore: true # Lookup metric
      ifName:
        ignore: true # Lookup metric
      ifPhysAddress:
        ignore: true # Lookup metric
      ifType:
        type: EnumAsInfo
    max_repetitions: 25
    retries: 3
    timeout: 10s

snmp.ymlを生成します。

make generate

生成したsnmp.ymlをSNMP Exporterのディレクトリに移動します。

sudo cp ./snmp.yml /opt/snmp_exporter/snmp.yml

SNMP Exporterを再起動します。念のためステータスも確認しておきましょう。

sudo systemctl restart snmp-exporter
sudo systemctl status snmp-exporter

最後に実験です。Scrapeしてみます。
(実験できる機器がなければsnmpdをインストールしてtarget=localhostしてください)

curl "http://localhost:9116/snmp?module=general_2c_module&auth=general_2c_auth&target=<IP Address>"

結果例です。

# HELP ifAdminStatus The desired state of the interface - 1.3.6.1.2.1.2.2.1.7
# TYPE ifAdminStatus gauge
ifAdminStatus{ifAlias="",ifDescr="Amazon.com, Inc. Elastic Network Adapter (ENA)",ifIndex="2",ifName="ens5"} 1
ifAdminStatus{ifAlias="",ifDescr="lo",ifIndex="1",ifName="lo"} 1
# HELP ifHCInBroadcastPkts The number of packets, delivered by this sub-layer to a higher (sub-)layer, which were addressed to a broadcast address at this sub-layer - 1.3.6.1.2.1.31.1.1.1.9
<以下省略>

いいですね。
今後定義を変更したい場合も同じように generator.yml変更⇒make generate⇒snmp.ymlコピー⇒snmp-exporter再起動 でOKです。

拡張MIB

拡張MIBに含まれるMIBオブジェクトやOIDを指定し、makeしようとするとOIDが見つからないというエラーが出ます。

caller=main.go:139 level=error msg="Error generating config netsnmp" err="cannot find oid '1.2.3.4.5' to walk"

その際はgenerator/mibs/に拡張MIBのファイルを置いてあげると反映されます。

それではSplunkにデータを送信してみましょうか。

OpenTelemetry Collector

Prometheus ExporterのScrape結果をSplunkに送信するためにOpenTelemetry Collector(以下、OTel Collector)を使用します。
細かい話はこちら。

https://qiita.com/symmr/items/148db3e2f0776ecc3d04

本家のOTel Collectorでもいいですが、せっかくなのでSplunk Distribution of OTel Collectorを使います。
Splunk Core (Enterprise / Cloud)へ送る方法とSplunk Observability CloudのInfrastructure Monitoringに送信する方法をそれぞれ見ていきます。

※SplunkならUFとかHFで取れないの?と思われたと思います。
私も思いました。
実際こんなのがあります。
Prometheus Metrics for Splunk
しかし、これはデバイスを1スタンザごとに登録しなければならず、対象が1,000台とかになると破綻するなと思い辞めました。

Splunk Core (Enterprise / Cloud) の場合

事前準備

以下を用意しておいてください。

  • MetricタイプのIndex
  • HECトークン

OTel Collectorインストール

OTel Collectorをインストールする訳ですが通常はインストールスクリプトが用意されています。
しかし、これはインストールする際にSplunk Observability Cloudへのトークン照会が行われます。今回は照会ができないのでマニュアルインストール方法を取ります。

手順通り、まずはパッケージでインストールします。
インストール先はSNMP Exporterが動いているホストで良いでしょう。

curl -sSL https://splunk.jfrog.io/splunk/otel-collector-deb/splunk-B3CD4420.gpg > /etc/apt/trusted.gpg.d/splunk.gpg
echo 'deb https://splunk.jfrog.io/splunk/otel-collector-deb release main' > /etc/apt/sources.list.d/splunk-otel-collector.list
apt-get update
apt-get install -y splunk-otel-collector

次にHECトークン周りの設定をします。
設定ファイルをコピーして

sudo cp /etc/otel/collector/splunk-otel-collector.conf.example /etc/otel/collector/splunk-otel-collector.conf

/etc/otel/collector/splunk-otel-collector.confを以下のように設定します。

SPLUNK_HEC_URL=https://<Splunk Enterprise or Cloudのホスト名>:<Entepriseは8088、Cloudは443>/services/collector
SPLUNK_HEC_TOKEN=<HECトークン>

次に/etc/otel/collector/agent_config.yamlを以下のように追加します。
scrape_intervalやtarget、paramなどは適宜変更してください。
また、service.pipelinesは不要な設定は削除してしましょう。

receivers:
  prometheus/general_demo:
    config:
      scrape_configs:
        - job_name: 'snmp'
          scrape_interval: 300s 
          static_configs:
            - targets:
              - <デバイスのIPアドレス1>
              - <デバイスのIPアドレス2>
          metrics_path: /snmp
          params:
            auth: [general_2c_auth]
            module: [general_2c_module]
          relabel_configs:
            - source_labels: [__address__]
              target_label: __param_target
            - source_labels: [__param_target]
              target_label: instance
            - target_label: __address__
              replacement: localhost:9116

exporters:
  splunk_hec/prometheus:
    token: "${SPLUNK_HEC_TOKEN}"
    endpoint: "${SPLUNK_HEC_URL}"
    source: "snmp-exporter"
    sourcetype: "prometheus:metrics"
    # 適宜変更
    index: "prometheus"
    profiling_data_enabled: false
    tls:
      # Splunk Enterpriseで自己署名証明書の場合
      insecure_skip_verify: true

service:
  pipelines:
    metrics/prometheus:
      receivers: [prometheus/general_demo]
      processors:
      - memory_limiter
      - batch
      - resourcedetection
      exporters: [splunk_hec/prometheus]

最後にOTel Collectorを再起動します。

sudo systemctl restart splunk-otel-collector
sudo systemctl status splunk-otel-collector

データを眺める

| mpreview index=prometheus

メトリクスとして取得できました。
ifIndexに紐づくifNameもDimensionとして追加されています。

メトリクスではなくテキストで得られるMIB(sysDescrなど)はMetric value自体は1で、Dimensionとして得られるようです。

SNMPのデータが特殊なのは大抵が累積値であることです。
例えばifHCInOctetsは取得した時点のそれまでの累積値なので、そのままでは使えません。
そのため差分を計算し取得間隔の秒数で割る必要があります。

SPLは便利なことに、そのためのコマンドrate_avgが用意されています。

| mstats rate_avg(ifHCInOctets) where index=prometheus service.instance.id="127.0.0.1" by ifName span=1m
| rename rate_avg(*) as *
| timechart avg(ifHCInOctets) span=1m by ifName

いい感じですね。

複数のメトリクスを組み合わせることもできます。

IFの使用率:

| mstats rate_avg(ifHCInOctets) rate_avg(ifHCOutOctets) max(ifSpeed) where index=prometheus service.instance.id="127.0.0.1" by ifName span=1m
| rename rate_avg(*) as *
| eval IO=ifHCInOctets+ifHCOutOctets, ifUtil=100*8*IO/ifSpeed
| fields _time, ifName, ifUtil
| where isnotnull(ifUtil)
| timechart avg(ifUtil) as ifUtil by ifName span=1m

破棄率

| mstats rate_avg(ifInDiscards) rate_avg(ifOutDiscards) 
rate_avg(ifInErrors) rate_avg(ifOutErrors)
rate_avg(ifHCInUcastPkts) rate_avg(ifHCOutUcastPkts)
rate_avg(ifHCInBroadcastPkts) rate_avg(ifHCOutBroadcastPkts)
rate_avg(ifHCInMulticastPkts) rate_avg(ifHCOutMulticastPkts) 
rate_avg(ifInUnknownProtos)
 where index=prometheus service.instance.id="127.0.0.1" by ifName span=1m 
| rename rate_avg(*) as *
| eval TotalDiscards=ifInDiscards+ifOutDiscards
| eval TotalPackets=ifInDiscards+ifOutDiscards+ifInErrors+ifOutErrors+ifHCInUcastPkts+ifHCOutUcastPkts+ifHCInBroadcastPkts+ifHCOutBroadcastPkts+ifHCInMulticastPkts++ifHCOutMulticastPkts+ifInUnknownProtos
| eval DiscardRate=100*TotalDiscards/TotalPackets
| fields _time, ifName, DiscardRate
| where isnotnull(DiscardRate)
| timechart avg(DiscardRate) as DiscardRate by ifName span=1m

Splunk Observability Cloud Infrastructure Monitoring (IM) の場合

事前準備

以下を用意しておいてください。

  • Ingest Access Token

OTel Collectorインストール

OTel Collectorをスクリプトでインストールします。
インストール先はSNMP Exporterが動いているホストで良いでしょう。

curl -sSL https://dl.signalfx.com/splunk-otel-collector.sh > /tmp/splunk-otel-collector.sh;
sudo sh /tmp/splunk-otel-collector.sh --realm $SPLUNK_REALM --memory $SPLUNK_MEMORY_TOTAL_MIB -- $SPLUNK_ACCESS_TOKEN

次に/etc/otel/collector/agent_config.yamlを以下のように追加します。
scrape_intervalやtarget、paramなどは適宜変更してください。

receivers:
  prometheus/general_demo:
    config:
      scrape_configs:
        - job_name: 'snmp'
          scrape_interval: 300s 
          static_configs:
            - targets:
              - <デバイスのIPアドレス1>
              - <デバイスのIPアドレス2>
          metrics_path: /snmp
          params:
            auth: [general_2c_auth]
            module: [general_2c_module]
          relabel_configs:
            - source_labels: [__address__]
              target_label: __param_target
            - source_labels: [__param_target]
              target_label: instance
            - target_label: __address__
              replacement: localhost:9116

service:
  pipelines:
    metrics/prometheus:
      receivers: [prometheus/general_demo]
      processors:
      - memory_limiter
      - batch
      - resourcedetection
      exporters: [signalfx]

最後にOTel Collectorを再起動します。

sudo systemctl restart splunk-otel-collector
sudo systemctl status splunk-otel-collector

データを眺める

Metric Finderでsnmpとサーチするとちゃんと取れていました。

累積値で得られるデータから差分を取るにはRollupをRate/sec rollupにするだけでOKです。
差分を計算し取得間隔の秒数で割ってくれます。

複数のデータに基づいて計算もできます。
破棄率を計算しています。

ぽちぽち入れるのが面倒な場合はSignalFlowでコードとして記述もできます。

A = data('ifInDiscards', rollup='rate').publish(label='A', enable=False)
B = data('ifOutDiscards', rollup='rate').publish(label='B', enable=False)
C = data('ifInErrors', rollup='rate').publish(label='C', enable=False)
D = data('ifOutErrors', rollup='rate').publish(label='D', enable=False)
E = data('ifHCInUcastPkts', rollup='rate').publish(label='E', enable=False)
F = data('ifHCOutUcastPkts', rollup='rate').publish(label='F', enable=False)
G = data('ifHCInBroadcastPkts', rollup='rate').publish(label='G', enable=False)
H = data('ifHCOutBroadcastPkts', rollup='rate').publish(label='H', enable=False)
I = data('ifHCInMulticastPkts', rollup='rate').publish(label='I', enable=False)
J = data('ifHCOutMulticastPkts', rollup='rate').publish(label='J', enable=False)
K = data('ifInUnknownProtos', rollup='rate').publish(label='K', enable=False)
L = (A+B).publish(label='L', enable=False)
M = (A+B+C+D+E+F+G+H+I+K).publish(label='M', enable=False)
N = (100*L/M).publish(label='N')

その他

スケーラビリティは?

A single instance of snmp_exporter can be run for thousands of devices.
だそうです。

まとめ

SNMPデータを取得する一つの方法としてPrometheus SNMP Exporterとの連携についてまとめました。

Discussion