Rubyスクリプトで、CiscoルータにSSHでログインして、CLIから10秒間隔でshow interface叩いて、実行結果から必要な値を正規表現で抜き出して、InfluxDBに保存して、Grafanaで視覚化してみました。
GrafanaとInfluxDBでネットワークリソースを視覚化してみました。データベースはInfluxDB、ビジュアライゼーションツールはGrafana、データ取得はRubyスクリプト+expect4rライブラリで実装しています。
本記事はUsing InfluxDB + Grafana to Display Network Statisticsを参考にしています。データ取得にInfluxsnmpを利用していますが、今回は自作のRubyスクリプトで実行しています。
カスタマイズすると上図のようにできます。今回はデータの視覚化までの基本的なところを説明します。
目的
ルータのトラフィックなどのネットワークリソースの視覚化することを目的としています。
従来はSNMPとRRDtoolなどを組み合わせて視覚化していました。ただし、トラフィック以外に取得項目が増えた場合や、取得間隔の変更など、手間がかかりました。そこで、代替ツールとしてInfluxDB+Grafanaを試用してみました。
今回は時系列データベースのInfluxDBを使います。メリットとして、スキーマレスのため、取得項目が増えた場合でも、データベース側で設定変更不要です。ガンガン監視項目の追加やルータの台数を増やすことができます。
視覚化ツールはGrafanaを使って、表示時間の変更や一度に複数の項目を表示などができます。ダイナミックに時間を変更でき、かっこ良く表示できます。
ネットワークリソースの値取得はSNMPが標準的ですが、今回はSSHからCLIでshowコマンドを実行し、その結果をデータベースに保存しています。想定はSNMPに対応していない値やSNMPできない環境を想定しています。
仕組み
準備
今回はDockerHub上のイメージを使ってInfluxDBとGrafanaを使います。
- Ubuntu 14.04.04 LTS
- Docker v1.6.2
- tutum/influxdb v0.10
- 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でトラフィックを流しています。
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!が表示されます。
上記のデータベース作成とポリシーを設定すると、下記のとおりとなります。
データ取得用の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してコマンド実行するを見てください。
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秒間隔で実行します。
$ 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
cURLコマンドでデータ一覧を取得することができます。APIのエンドポイントはhttp://192.168.122.103:8086/query となります。パラメータにデータベースとクエリを指定します。結果はJSONとなります。詳細はInfluxDB Querying Dataを参照してください。
$ 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でログインできます。
Grafanaでデータソースの指定
ログイン後、最初にデータソースの設定をします。データソースにInfluxDBを使用するよう指定します。
左上のGrafanaアイコンをクリックし、メニューが表示されたら、Data Sourcesリンクをクリックし、Data Sources画面に移動します。
Data Sources画面でAdd data sourceボタンをクリックします。
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 ※入力必須項目のためダミーで入力
追加後にテスト接続し、問題ないことを確認するため、Test Connectionボタンをクリックし、Successと表示がでることを確認します。問題なければ、Saveボタンで保存します。
Grafanaダッシュボードの作成
Grafanaアイコン => Dashboards => Newからダッシュボード作成画面に移動します。
新しいダッシュボードを作成したら、グラフパネルを追加します。ROWアイコン => Add Panel => Graphからグラフパネル作成画面に移動します。
メトリクスの追加は下記のYouTubeの動画のとおりに設定します。
※追記:動画ではGraph Metricsでfill(null)となっていますが、fill(none)に変更が必要です。fill(null)で表示時間を変更した場合、うまく表示されない場合があります。
Graph Generalの設定。グラフのタイトルを設定します。
Graph Metricsの設定。データベースを選択し、表示対象のメトリクスを設定します。
トラフィックの場合、累積カウンタとなるため、直前の値と差分を取る必要があります。non_negative_derivative(1s)関数を使用し、直前との差分の1秒間隔の平均をグラフにプロットします。ただし、ラップアラウンドは考慮されていません。値がバイトのため、ビットに変換するためにmath(*8)関数を使用して、8倍しています。GROUP BYでfill(none)を選択します。
Graph Axesの設定。X-Y軸の設定をします。Unit(単位)をbits/sec、Y-Minを0、Right YをOFFに設定します。
Graph Legendの設定。判例の設定をします。判例に最大と現在の値を表示するよう設定します。
今回は必要最小限の設定のみ説明しました。今回説明していないTemplatingを使うことで、動的にポートを選択できるようになります。詳しくはUsing InfluxDB + Grafana to Display Network Statisticsを参照してください。