GrafanaとInfluxDBでネットワークリソースの視覚化

Rubyスクリプトで、CiscoルータにSSHでログインして、CLIから10秒間隔でshow interface叩いて、実行結果から必要な値を正規表現で抜き出して、InfluxDBに保存して、Grafanaで視覚化してみました。

GrafanaとInfluxDBでネットワークリソースを視覚化してみました。データベースはInfluxDB、ビジュアライゼーションツールはGrafana、データ取得はRubyスクリプト＋expect4rライブラリで実装しています。
本記事は[Using InfluxDB + Grafana to Display Network Statistics](http://lkhill.com/using-influxdb-grafana-to-display-network-statistics/)を参考にしています。データ取得にInfluxsnmpを利用していますが、今回は自作のRubyスクリプトで実行しています。
![範囲を選択_032.png](https://qiita-image-store.s3.amazonaws.com/0/12026/2b27ee62-f371-7e1f-da22-9a0d508cbc0b.png)
カスタマイズすると上図のようにできます。今回はデータの視覚化までの基本的なところを説明します。

# 目的
ルータのトラフィックなどのネットワークリソースの視覚化することを目的としています。

従来はSNMPとRRDtoolなどを組み合わせて視覚化していました。ただし、トラフィック以外に取得項目が増えた場合や、取得間隔の変更など、手間がかかりました。そこで、代替ツールとしてInfluxDB+Grafanaを試用してみました。

今回は時系列データベースのInfluxDBを使います。メリットとして、スキーマレスのため、取得項目が増えた場合でも、データベース側で設定変更不要です。ガンガン監視項目の追加やルータの台数を増やすことができます。
視覚化ツールはGrafanaを使って、表示時間の変更や一度に複数の項目を表示などができます。ダイナミックに時間を変更でき、かっこ良く表示できます。

ネットワークリソースの値取得はSNMPが標準的ですが、今回はSSHからCLIでshowコマンドを実行し、その結果をデータベースに保存しています。想定はSNMPに対応していない値やSNMPできない環境を想定しています。

# 仕組み
データ取得・保存・表示は下記の仕組みで実現します。
![スクリーンショット 2016-04-12 7.35.31.png](https://qiita-image-store.s3.amazonaws.com/0/12026/162387c7-f875-6929-e1e7-108225057a69.png)


# 準備
今回はDockerHub上のイメージを使ってInfluxDBとGrafanaを使います。

- [Ubuntu](http://www.ubuntu.com/) 14.04.04 LTS
- [Docker](https://www.docker.com/) v1.6.2
- [tutum/influxdb](https://hub.docker.com/r/tutum/influxdb/) v0.10
- [grafana/grafana](https://hub.docker.com/r/grafana/grafana/) v3.0.0-beta1
- Ruby 2.2.4
    - clockwork 1.2.0
    - expect4r 0.0.11
    - influxdb 0.2.3
- Cisco VIRL：ルータ

※Grafana v3.0.0-beta1がInfluxDB v0.11以降に対応していないため、今回はInfluxDB v0.10を利用します。

# ルータの準備
Cisco VIRLで下記の環境を構築します。擬似トラフィックを流すため、両端にlxc-iperf-1/2間でiperfでトラフィックを流しています。
![VM Maestro _031.png](https://qiita-image-store.s3.amazonaws.com/0/12026/e42eb4b5-9aff-f08e-46ac-4776b662595f.png)

# Ubuntu/Dockerの準備
InfluxDBとGrafanaをDocker上で動かすため、Dockerをインストールします。Ubuntu14.04で提供されているdocker.ioパッケージをインストールします。バージョンはv1.6.2です。

```
$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
$ sudo apt install docker.io
```

# InfluxDBの準備
InfluxDBは時系列データベースです。スキーマレスのため、動的に保存する項目を増やしたりできます。
今回はInfluxDB v0.10をDockerHubのコンテナイメージを使って利用します。データベースは＄HOME/influxdbに保存するよう設定します。InfluxDBはTCPポート8083,8086を使用します。TCP8083はWeb管理インタフェース、TCP8086はAPIのポートです。

```
# InfluxDB v0.10のコンテナイメージを取得
$ sudo docker pull tutum/influxdb:0.10

# InfluxDBのコンテナ起動
$ sudo docker run -d --name=influxdb --volume=$HOME/influxdb:/data -p 8083:8083 -p 8086:8086 tutum/influxdb:0.10

# InfluxDBが起動しているか確認
$ sudo docker ps
CONTAINER ID        IMAGE                 COMMAND             CREATED             STATUS              PORTS                                            NAMES
cb5e24f3065a        tutum/influxdb:0.10   "/run.sh"           4 minutes ago       Up 4 minutes        0.0.0.0:8083->8083/tcp, 0.0.0.0:8086->8086/tcp   influxdb            

# コンテナを停止したい場合に実行
$ sudo docker stop influxdb

# 停止したコンテナを起動
$ sudo docker start influxdb
```

## InfluxDBでデータベースの作成

続いて、Web管理インタフェースからInfluxDB上でデータベースを作成します。今回はtraffic1というデータベース名で、4週間データを保存する設定をします。

DockerホストのTCPポート8083をWebブラウザで表示し、下記のクエリを発行し、データベースの作成とポリシーを設定します。

```
# データベースの作成
CREATE DATABASE traffic1
# 作成済みのデータベース確認
SHOW DATABASES

# デフォルトのポリシーtrafficを設定、4週間保存する
CREATE RETENTION POLICY traffic ON traffic1 DURATION 4w REPLICATION 1 DEFAULT
# ポリシーの確認
SHOW RETENTION POLICIES ON "traffic1"
```

今回は192.168.122.103のDockerホスト上で動作しているため、Web管理インタフェースはhttp://192.168.122.103:8083/ となります。WebブラウザでWeb管理インタフェースを開き発行したいクエリを「Query」フォームに入力し、Enterで確定します。問題なければ、Success!が表示されます。
![範囲を選択_001.png](https://qiita-image-store.s3.amazonaws.com/0/12026/2169c772-8ca7-1f23-d0f3-738d0de91fa1.png)

上記のデータベース作成とポリシーを設定すると、下記のとおりとなります。
![範囲を選択_002.png](https://qiita-image-store.s3.amazonaws.com/0/12026/3c387e22-71ec-14d1-a169-697440552636.png)


# データ取得用のRubyスクリプト
今回はSNMPを使わずに手製スクリプトでルータにSSHして、データを取得しています。想定としてはSNMPでは取得できないメトリクスや、そもそもSNMPできないルータに対して、CLIからコマンドを実行して、値を保存する方法を想定しています。

CiscoルータにSSHし、show interfaceコマンドの実行結果をデータベースに保存します。データベースはtraffic1で、SQLデータベースで言ういわゆるテーブル名はcisco_showintとしていします。InfluxDBでは、テーブル名はシリーズ名と言われます。

show interfaceから正規表現を利用して下記のパラメータを抽出します。抽出した結果は、ホスト名とインタフェース名を組み合わせたportというタグをつけてデータベースに保存します。

- interface: インタフェース名
- out_drops: 送信ドロップ数
- in_packets: 受信パケット数
- in_bytes: 受信バイト数
- in_errors: 受信エラーパケット数
- out_packets: 送信パケット数
- out_bytes: 送信バイト数
- out_errors: 送信エラーパケット数
- lost_carriers: Ethernetキャリア信号の喪失回数

expect4rライブラリを使って、CiscoルータにSSHしています。詳細は[Rubyのexpect4rでCiscoルータにTelnet/SSHしてコマンド実行する](http://qiita.com/kooshin/items/27f2b9ed5c8896279bcf)を見てください。

```rb:collector.rb
require 'expect4r'
require 'awesome_print'
require 'clockwork'
require 'influxdb'
require 'parallel'
require 'yaml'

# InfluxDBの設定
influxdb = InfluxDB::Client.new 'traffic1', host: '192.168.122.103'

# 取得先ホスト
hosts = YAML.load(<<EOL)
- :hostname: iosv-1
  :host: 172.16.1.205
- :hostname: iosv-2
  :host: 172.16.1.206
EOL

# show interfaceの結果から値を抜き出す正規表現
REGEX_SHOWINT = %r{
  ^(?<interface>[\w\.\/]+)\ is
  .+?
  Total\ output\ drops:\ (?<out_drops>\d+)
  .+?
  (?<in_packets>\d+)\ packets\ input,\ (?<in_bytes>\d+)\ bytes
  .+?
  (?<in_errors>\d+)\ input\ errors,
  .+?
  (?<out_packets>\d+)\ packets\ output,\ (?<out_bytes>\d+)\ bytes
  .*?
  (?<out_errors>\d+)\ output\ errors,
  .+?
  (?<lost_carriers>\d+)\ lost\ carrier
}xm
# 上記の正規表現は下記のshow interfaceの結果をインタフェース単位に抽出する
# GigabitEthernet0/2 is up, line protocol is up 
#   Hardware is iGbE, address is fa16.3e59.cd19 (bia fa16.3e59.cd19)
#   Description: to iosv-2
#   Internet address is 10.0.0.9/30
#   MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec, 
#      reliability 255/255, txload 1/255, rxload 1/255
#   Encapsulation ARPA, loopback not set
#   Keepalive set (10 sec)
#   Full Duplex, Auto Speed, link type is auto, media type is RJ45
#   output flow-control is unsupported, input flow-control is unsupported
#   ARP type: ARPA, ARP Timeout 04:00:00
#   Last input 00:00:07, output 00:00:00, output hang never
#   Last clearing of "show interface" counters never
#   Input queue: 0/75/0/0 (size/max/drops/flushes); Total output drops: 842948
#   Queueing strategy: fifo
#   Output queue: 0/40 (size/max)
#   5 minute input rate 115000 bits/sec, 171 packets/sec
#   5 minute output rate 2092000 bits/sec, 172 packets/sec
#      3475718 packets input, 286108879 bytes, 0 no buffer
#      Received 0 broadcasts (0 IP multicasts)
#      0 runts, 0 giants, 0 throttles 
#      0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored
#      0 watchdog, 0 multicast, 0 pause input
#      3517278 packets output, 5302646275 bytes, 0 underruns
#      0 output errors, 0 collisions, 3 interface resets
#      0 unknown protocol drops
#      0 babbles, 0 late collision, 0 deferred
#      0 lost carrier, 0 no carrier, 0 pause output
#      0 output buffer failures, 0 output buffers swapped out

hosts.each do |host|
  ios = Expect4r::Ios.new_ssh(
    host: host[:host], user: 'cisco',
    pwd: 'cisco', enable_password: 'cisco'
  )
  # 10秒間隔で定期的に実行する
  Clockwork.every(10.seconds, "#{host[:hostname]} collector") do
    ios.login

    # Ciscoルータでshow interfaceを実行し、
    # 正規表現でメトリクスを抜き出す
    response = ios.exec('show interface').join.delete("\r")
    response.scan(REGEX_SHOWINT) do
      m = Regexp.last_match
      data = {
        values: {
          out_drops:     m[:out_drops].to_i,
          in_packets:    m[:in_packets].to_i,
          in_bytes:      m[:in_bytes].to_i,
          in_errors:     m[:in_errors].to_i,
          out_packets:   m[:out_packets].to_i,
          out_bytes:     m[:out_bytes].to_i,
          out_errors:    m[:out_errors].to_i,
          lost_carriers: m[:lost_carriers].to_i
        },
        tags: {
          port: "#{host[:hostname]} #{m[:interface]}"
        }
      }

      # InfluxDBにメトリクスを保存
      influxdb.write_point('cisco_showint', data)
      ap data
    end
  end
end
```

## データ取得用のRubyスクリプトの実行

上記のRubyスクリプトは定期実行するためにclockworkを利用して、10秒間隔で実行します。

```bash:実行結果
$ clockwork collector.rb 
I, [2016-04-10T11:30:36.846900 #1318]  INFO -- : Starting clock for 2 events: [ iosv-1 collector iosv-2  collector ]
I, [2016-04-10T11:30:36.847066 #1318]  INFO -- : Triggering 'iosv-1 collector'
{
    :values => {
            :out_drops => 0,
           :in_packets => 744202,
             :in_bytes => 45856886,
            :in_errors => 0,
          :out_packets => 780556,
            :out_bytes => 126581158,
           :out_errors => 0,
        :lost_carriers => 1
    },
      :tags => {
        :port => "iosv-1 GigabitEthernet0/0"
    }
}
{
    :values => {
            :out_drops => 0,
           :in_packets => 5049224,
             :in_bytes => 7628494232,
            :in_errors => 0,
          :out_packets => 4034600,
            :out_bytes => 332766793,
           :out_errors => 0,
        :lost_carriers => 1
    },
      :tags => {
        :port => "iosv-1 GigabitEthernet0/1"
    }
}
{
    :values => {
            :out_drops => 988441,
           :in_packets => 4029369,
             :in_bytes => 332510281,
            :in_errors => 0,
          :out_packets => 4080969,
            :out_bytes => 6136203204,
           :out_errors => 0,
        :lost_carriers => 0
    },
      :tags => {
        :port => "iosv-1 GigabitEthernet0/2"
    }
}
```

# InfluxDBでデータ保存結果の確認

データが保存されているか、InfluxDBのWeb管理インタフェースで確認します。
右上から対象のデータベースtraffic1を選択し、下記のクエリを実行します。

```
select * from cisco_showint where port = 'iosv-1 GigabitEthernet0/1' order by desc
```

![InfluxDB - Admin Interface - Mozilla Firefox_004.png](https://qiita-image-store.s3.amazonaws.com/0/12026/223a5508-c4c0-af12-c711-c09c3d1950d2.png)

cURLコマンドでデータ一覧を取得することができます。APIのエンドポイントはhttp://192.168.122.103:8086/query となります。パラメータにデータベースとクエリを指定します。結果はJSONとなります。詳細は[InfluxDB Querying Data](https://docs.influxdata.com/influxdb/v0.10/guides/querying_data/)を参照してください。

```bash
$ curl -G "http://192.168.122.103:8086/query?pretty=true" --data-urlencode "db=trffic1" --data-urlencode "q=SELECT * FROM cisco_showint WHERE port = 'iosv-1 GigabitEthernet0/1' order by desc limit 2"
{
    "results": [
        {
            "series": [
                {
                    "name": "cisco_showint",
                    "columns": [
                        "time",
                        "in_bytes",
                        "in_errors",
                        "in_packets",
                        "lost_carriers",
                        "out_bytes",
                        "out_drops",
                        "out_errors",
                        "out_packets",
                        "port"
                    ],
                    "values": [
                        [
                            "2016-04-10T02:51:33Z",
                            7.628494232e+09,
                            0,
                            5.049224e+06,
                            1,
                            3.32786237e+08,
                            0,
                            0,
                            4.034858e+06,
                            "iosv-1 GigabitEthernet0/1"
                        ],
                        [
                            "2016-04-10T02:51:23Z",
                            7.628494232e+09,
                            0,
                            5.049224e+06,
                            1,
                            3.32786087e+08,
                            0,
                            0,
                            4.034856e+06,
                            "iosv-1 GigabitEthernet0/1"
                        ]
                    ]
                }
            ]
        }
    ]
}
```


# Grafanaの準備
ビジュアライゼーションツールのGrafanaを設定します。今回はv3.0.0の正式リリース前だったため、v3.0.0-beta1を使用します。
$HOME/grafanaに設定データを保存するよう設定します。TCP3000ポートで稼働します。

```
# Grafana v3.0.0-beta1のコンテナイメージを取得
$ sudo docker pull grafana/grafana:3.0.0-beta1

# Grafanaのコンテナ起動
sudo docker run -d --name=grafana --volume=$HOME/grafana/:/var/lib/grafana -p 3000:3000 grafana/grafana:3.0.0-beta1

# InfluxDBとGrafanaが起動しているか確認
$ sudo docker ps
CONTAINER ID        IMAGE                         COMMAND             CREATED             STATUS              PORTS                                            NAMES
813046bdf50e        grafana/grafana:3.0.0-beta1   "/run.sh"           6 seconds ago       Up 6 seconds        0.0.0.0:3000->3000/tcp                           grafana             
cb5e24f3065a        tutum/influxdb:0.10           "/run.sh"           58 minutes ago      Up 58 minutes       0.0.0.0:8083->8083/tcp, 0.0.0.0:8086->8086/tcp   influxdb            

# コンテナを停止したい場合に実行
$ sudo docker stop grafana

# 停止したコンテナを起動
$ sudo docker start grafana
```

## Grafanaにログイン
Grafanaの設定をするために、WebブラウザからGrafanaを表示します。
Webブラウザでhttp://192.168.122.103:3000 を開くと、ログイン画面が表示されます。デフォルトは、ユーザ名：admin、パスワード：adminでログインできます。
![範囲を選択_006.png](https://qiita-image-store.s3.amazonaws.com/0/12026/8efeac6c-b4de-abc6-ac55-6310e84c9613.png)

## Grafanaでデータソースの指定
ログイン後、最初にデータソースの設定をします。データソースにInfluxDBを使用するよう指定します。

左上のGrafanaアイコンをクリックし、メニューが表示されたら、Data Sourcesリンクをクリックし、Data Sources画面に移動します。
![範囲を選択_007.png](https://qiita-image-store.s3.amazonaws.com/0/12026/a7fdda70-dc19-7e57-6175-d6ab8484d2e0.png)

Data Sources画面でAdd data sourceボタンをクリックします。
![範囲を選択_008.png](https://qiita-image-store.s3.amazonaws.com/0/12026/9137b767-bc2b-5389-fab1-dc56c8989e6b.png)

Add data source画面で下記のパラメータを入力します。



- Name: traffic1  ※データベース名と同じ名前を設定します。
- Default: ON
- Type: InfluxDB  ※データベースにInfluxDBを指定。指定すると入力項目がInfluxDBに合わせて変化します
- Http Settings
    - Url: http://192.168.122.130:8086 ※InfluxDBのTCP8086を指定します。
    - Access: direct ※ブラウザから直接アクセスするように指定します。
- InfluxDB Details
    - Database: traffic1 ※InfluxDBで作成したデータベースを指定
    - User: admin  ※入力必須項目のためダミーで入力
    - Password: admin  ※入力必須項目のためダミーで入力

Addボタンをクリックし、データソースを追加します。
![範囲を選択_009.png](https://qiita-image-store.s3.amazonaws.com/0/12026/df406215-d293-4e13-43f5-50ca57649455.png)

追加後にテスト接続し、問題ないことを確認するため、Test Connectionボタンをクリックし、Successと表示がでることを確認します。問題なければ、Saveボタンで保存します。
![範囲を選択_010.png](https://qiita-image-store.s3.amazonaws.com/0/12026/44dff8e1-ab35-8e3b-e588-c19d42355c6a.png)

## Grafanaダッシュボードの作成
Grafanaアイコン => Dashboards => Newからダッシュボード作成画面に移動します。
![Screenshot from 2016-04-10 13:06:32.png](https://qiita-image-store.s3.amazonaws.com/0/12026/e7f477d7-dfd9-5c1b-49fb-6b35f9ae6752.png)

新しいダッシュボードを作成したら、グラフパネルを追加します。ROWアイコン => Add Panel => Graphからグラフパネル作成画面に移動します。
![ワークスペース 1_020.png](https://qiita-image-store.s3.amazonaws.com/0/12026/ae851f4f-2668-950b-58b5-cc6a422bf0f3.png)

メトリクスの追加は下記のYouTubeの動画のとおりに設定します。
[![Grafanaの設定](https://qiita-image-store.s3.amazonaws.com/0/12026/29d8d40b-caab-5292-593c-996df83ac8b0.png)](http://www.youtube.com/watch?v=KF3DlE8mwho)
※追記：動画ではGraph Metricsでfill(null)となっていますが、fill(none)に変更が必要です。fill(null)で表示時間を変更した場合、うまく表示されない場合があります。

Graph Generalの設定。グラフのタイトルを設定します。
![範囲を選択_023.png](https://qiita-image-store.s3.amazonaws.com/0/12026/b68b29b0-52ee-22ad-7c62-06d464cb6125.png)

Graph Metricsの設定。データベースを選択し、表示対象のメトリクスを設定します。
トラフィックの場合、累積カウンタとなるため、直前の値と差分を取る必要があります。non_negative_derivative(1s)関数を使用し、直前との差分の1秒間隔の平均をグラフにプロットします。ただし、ラップアラウンドは考慮されていません。値がバイトのため、ビットに変換するためにmath(*8)関数を使用して、8倍しています。GROUP BYでfill(none)を選択します。
![範囲を選択_029.png](https://qiita-image-store.s3.amazonaws.com/0/12026/f6a6a266-89e1-1531-b47f-b74fe7407994.png)

Graph Axesの設定。X-Y軸の設定をします。Unit（単位）をbits/sec、Y-Minを0、Right YをOFFに設定します。
![範囲を選択_026.png](https://qiita-image-store.s3.amazonaws.com/0/12026/5f47d332-d035-8e37-c1ec-c4b4ca7a2858.png)

Graph Legendの設定。判例の設定をします。判例に最大と現在の値を表示するよう設定します。
![範囲を選択_027.png](https://qiita-image-store.s3.amazonaws.com/0/12026/6fb26483-240d-934c-bbec-da36fcb11c8c.png)

最後にダッシュボードを保存します。
![Screenshot from 2016-04-10 13:44:35.png](https://qiita-image-store.s3.amazonaws.com/0/12026/889d44d3-edf1-983e-a3e7-46608c2e2a05.png)

今回は必要最小限の設定のみ説明しました。今回説明していないTemplatingを使うことで、動的にポートを選択できるようになります。詳しくは[Using InfluxDB + Grafana to Display Network Statistics](http://lkhill.com/using-influxdb-grafana-to-display-network-statistics/)を参照してください。

# 参考文献
- [Using InfluxDB + Grafana to Display Network Statistics](http://lkhill.com/using-influxdb-grafana-to-display-network-statistics/)
- [InfluxDB を Ruby から操作するチュートリアル](http://inokara.hateblo.jp/entry/2014/06/01/092905)
- [InfluxDBとGrafanaを使ってサーバーリソースの可視化をする - Qiita](http://qiita.com/airtoxin/items/806e0bfbdecba115b3be)
- [InfluxDBとGrafanaとfluentdで、twitterデータのリアルタイム集計・可視化 - Qiita](http://qiita.com/ixixi/items/a56ea15b582c7f014a57)
- [Network Interface Traffic Calculation #363 - GitHub](https://github.com/grafana/grafana/issues/363)
- [SNMP Counter calculation using InfluxDB #844 - GitHub](https://github.com/grafana/grafana/issues/844)