SNMPを利用したモニタリング

# はじめに
SNMPを利用したモニタリングは昔から行われていて
MRTG Munin Cacti Zabbix などなど、いくらでもツールがありますよね。
でもなんか最近はやりのOSSなモニタリングツールで気軽に拾いたい！とか思ったので試してみようと思った次第です。

具体的には collectd, fluentd, prometheus を比べてみました。

collectd, fluentd はデータをルーティングするものなので、データストアとしてgraphite, influxdbなどを利用することを想定しています。
がその設定はとりあえずここでは関係がないので記載していません。

## collectd

[Plugin](https://collectd.org/wiki/index.php/Plugin:SNMP)で対応可能です。
設定は

```
LoadPlugin snmp
<Plugin snmp>
  <Data "dataname">
    Type "tipename"
    Table true
    Instance "IF-MIB::ifDescr"
    Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
  </Data>
  <Data "dataname">
    Type "users" 
    Table false
    Instance ""
    Shift -1
    Values "HOST-RESOURCES-MIB::hrSystemNumUsers.0"
  </Data>
  <Host "Hostname">
    Address "192.168.1.1"
    Version 2
    Interval 60
    Community "another_string"
    Collect "dataname" "dataname" "dataname"
  </Host>
</Plugin>
```
Pluginの読み込み、
データの定義、(indexものならTable true)Instanceで指定したものがindex名として使用される
ホストの定義 (収集するデータの指定)
みたいな形です。
データ定義とホストの定義が分かれているので、設定はそこまで多く増えません。
ただ、データ定義をグルーピングしたりできない(?)ので、
ホストの Collect がすごく、長くなる可能性はあります。


## fluentd
[Plugin](https://rubygems.org/gems/fluent-plugin-snmp/)で対応可能です。
設定は

```
<source>
  type snmp
  tag snmp.hostname
  host "192.168.1.1"
  Community "another_string"
  mib ifDescr, ifInOctets, ifOutOctets
  method_type walk
  polling_time 1
  out_executor "/path/of/exec.rb"
</source>
<source>
  type snmp
  tag snmp.hostname
  host "192.168.1.1"
  Community "another_string"
  mib "hrSystemNumUsers.0"
  method_type get
</source>
```
ホスト名とデータの定義 をセットで行うので、
収集したいデータとホストが多くなると、source定義がどんどん増えていきます。
つらい。
何もしないと収集したデータはValue={Name=hoge,Value=fuga} のような形で届くので
exec.rb を用意して加工しないとつらいかも。

interfaceは ifDescr.ifInOctets=value, ifDescr.ifOutOctets=valueのように加工して吐き出すようにしてみました。下記

```
module Fluent
  class SnmpInput
    def out_exec manager, opts={}
      manager.walk(opts[:mib]) do |row|
        time = Time.now.to_i
        time = time - time  % 5
        record = {}
        data = {}
        row.each do |vb|
          data["name"] = vb.value.to_s if vb.name.to_s =~ /Descr/
          data["InOctets"] = vb.value.to_s if vb.name.to_s =~ /InOctets/
          data["OutOctets"] = vb.value.to_s if vb.name.to_s =~ /OutOctets/
        end
        if data.has_key?("name")
          record["#{data['name']}.InOctets"] = data["InOctets"] if data.has_key?("InOctets")
          record["#{data['name']}.OutOctets"] = data["OutOctets"] if data.has_key?("OutOctets")
        end
        if record
          router.emit opts[:tag], time, record
        end
      end
    end
  end
end
```
また収集データタイプがカウンタだとかそういうのを気にせず値だけ拾うので、差分計算などは
別途fluentdのフィルタなどもしくはデータストア側が処理できればカウンタ値として放り込む必要があります。

## Prometheus
[Exporter](https://github.com/prometheus/snmp_exporter)で対応可能です。
設定は
snmp_exporterとPrometheusでそれぞれ行います。
データ定義はsnmp_exporter、ポーリング設定はPrometheusです。

```
specialnode:
   version: 2
   auth:
      community: "another_string"
   walk:
      - 1.3.6.1.2.1.2
      - 1.3.6.1.2.1.25.1.5
   metrics:
      - name: ifInOctets
        oid: 1.3.6.1.2.1.2.2.1.10 
        indexes:
          - labelname: ifDescr
            type: Integer32
        lookups:
          - labelname: ifDescr
            oid: 1.3.6.1.2.1.2.2.1.2
      - name: ifOutOctets
        oid: 1.3.6.1.2.1.2.2.1.16 
        indexes:
          - labelname: ifDescr
            type: Integer32
        lookups:
          - labelname: ifDescr
            oid: 1.3.6.1.2.1.2.2.1.2
      - name: hrSystemNumUsers
        oid: 1.3.6.1.2.1.25.1.5 
```
以上がsnmp_exporter側のデータ定義。
ここではspecialnode という名前で1つ定義しています。

この状態で、snmp_exporterを動かして
curl "http://snmp_exporterhost:9116/snmp?targer=hostname&module=specialnode"
とやると、データが拾えるのがわかります。

```
scrape_configs:
  - job_name: 'snmp'
    scrape_interval: 60s
    target_groups:
      - targets:
        - 192.168.1.1
    metrics_path: /snmp
    params:
      module: [specialnode]
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: 127.0.0.1:9116
```

Prometheus側ではjobとして登録し、Prometheusからsnmp_exporterに定期的にデータ収集しに行きます。
Prometheusもfluentdと同様、Counterデータについて、そのままだと差分計算などを行ってくれないため、加工をする必要があります。
が、そのあたりはまだ調べていないのでどのくらい面倒なのかはわかりません。

### Prometheusでの差分計算
Prometheusでの差分計算方法としては、rateという集計関数が用意されています。
上記設定にて収集すると、60秒ごとのifInOctetsなどが収集されていますので、
rate(IfInOctets[5m]) とGraphのExpressionに記載すると、5minでの差分が表示されます
また設定ファイルに

```
rule_files:
  - prometheus.rule
```
と収集ルールを記載するファイルを記載し、そのファイル(prometheus.rule)に

```
ifInOctets_5min_rate = rate(ifInOctets[5m])
```
と記載すると、`ifInOctets_5min_rate` という名称で5分の差分値が保存されます。
rateの計算には2個のデータが必要になりますので、1minの差分値を計算しようと思うと、`scrape_interval = 30s`とするなど、何らかの手当てが必要です。

```
ifInbps_5min_average = rate(ifInOctets[5m])*8/300
```
このようにbps計算させることもできます。

# おわりに
SNMPを利用したモニタリングに必要な設定の差を確認しました。
設定ファイルの記述の面倒くささという意味ではfluentd単体ではこの用途に使うのはなんだかなぁ？という感じがしました。
どのみちそんなのジェネレータ作るから一緒でしょ？と言われればそれまでですが。