##はじめに
Ciscoのネットワーク機器の監視を行う際に、showコマンドの結果を参照したいと思うことがあると思います。
今回はIOS-XEのGuest Shellから、pythonを用いてshowコマンドの結果を取得し、Elasticsearchへ送信して可視化してみたいと思います。
Guest Shellについて
Guest ShellとはIOxでホスティングされるLinuxのコンテナ環境で、Catalyst9000シリーズやISR4000シリーズなどの製品で実装されています。
Guest Shellではpythonのcliライブラリが提供されており、これを用いるとGuest Shellで実行されるpythonスクリプトからIOSdのコマンドを実行することができます。
IOxについてはこちらを参照すると良いと思います。
Programmability Configuration Guide, Cisco IOS XE Fuji 16.9.xより
##環境
以下のような構成で環境を構築します。
(構築方法は省略)
監視対象はCSR1とします。
簡略化していますが、CSR1に通信負荷をかけるためにVMも用意しました。
Software | Version |
---|---|
Elasticsearch | 6.5.0 |
Kibana | 6.5.0 |
CSR1000V | IOS-XE 16.09.04 |
##Elasticsearchの準備
Elasticsearchは全文検索エンジンであり、ドキュメントストア(もしくはドキュメント指向型)と呼ばれるデータベースのマネジメントシステムとしても使用されます。
簡単に用語を書いておきます。
(飽くまで自分の理解です)
用語 | 意味 |
---|---|
Document | 格納されるJSONデータの単位 |
Mapping | Documentの中身を具体的に定義したもの |
Index | Documentに必ず付与される索引名 設定やクエリを行う単位として使用する 事前に作成することもDocumentの登録時に自動で作成することも可能 |
Template | 新たなIndexを作成するときに適用される設定とMapping |
CSR1から監視項目として以下の情報を取得することにします。
- 時刻(date)
- IOSdのCPU使用率(%)
- kernelのCPU使用率(%)
- IOSdのメモリ使用率(%)
- kernelのメモリ使用率(%)
- GigabitEthernet1の送受信量(bps)
- Interface/line protocolのステータス一覧(text)
- ルーティングテーブル(text)
上記のデータを格納するためにIndexのTemplateを定義します。
CSR1からDocumentを格納する際のIndexはrouter-csrとでもしておきましょう。
curl -XPUT 'http://elasticsearch.example.com:9200/_template/router' -H 'Content-Type: application/json' -d @router_template.json
{
"template": "router-*",
"mappings":{
"monitoring": {
"dynamic": "strict",
"properties": {
"@timestamp":{
"type": "date",
"format": "date_time_no_millis"
},
"cpu_utilization_iosd":{
"type": "integer"
},
"cpu_utilization_kernel":{
"type": "integer"
},
"memory_utilization_iosd":{
"type": "integer"
},
"memory_utilization_kernel":{
"type": "integer"
},
"GigabitEthernet1_input":{
"type": "integer"
},
"GigabitEthernet1_output":{
"type": "integer"
},
"interface_status":{
"type": "keyword"
},
"routing_table":{
"type": "keyword"
}
}
}
}
}
##CSR 1000Vの設定
GigabitEthernet1の送受信量は30秒間の平均を用いることにします。
送受信パケット数の差分から算出することも可能ですが、load-interval
を調整してshow interface
から取得するほうが楽かなと思います。
CSR1#conf t
CSR1(config)#interface gigabitethernet 1
CSR1(config-if)#load-interval 30
次にGuest Shellを有効にする設定と、Guest Shellから通信を行うためのNATの設定を同時に行います。
CSR1#conf t
CSR1(config)#iox
CSR1(config)#ip http server
CSR1(config)# interface GigabitEthernet3
CSR1(config-if)# ip nat outside
CSR1(config-if)# exit
CSR1(config)# interface VirtualPortGroup0
CSR1(config-if)# ip address 192.168.35.1 255.255.255.0
CSR1(config-if)# ip nat inside
CSR1(config-if)# exit
CSR1(config)# ip nat inside source list GS_NAT_ACL interface GigabitEthernet3 overload
CSR1(config)# ip access-list standard GS_NAT_ACL
CSR1(config-std-nacl)# permit 192.168.35.0 0.0.0.255
CSR1(config-std-nacl)# exit
CSR1(config)#app-hosting appid guestshell
CSR1(config-app-hosting)# app-vnic gateway0 virtualportgroup 0 guest-interface 0
CSR1(config-app-hosting-gateway0)# guest-ipaddress 192.168.35.2 netmask 255.255.255.0
CSR1(config-app-hosting-gateway0)# app-default-gateway 192.168.35.1 guest-interface 0
CSR1(config-app-hosting)# name-server0 192.0.2.1
CSR1(config-app-hosting)# end
CSR1#guestshell enable
これでGuest Shellが有効になりました。
特権モードからguestshell
を実行するとコンテナのbashが起動してログインできます。
試しにGuset Shellを起動してIOSdのコマンドを実行してみましょう。
CSR1#guestshell
[guestshell@guestshell ~]$ dohost "show app-hosting list"
App id State
------------------------------------------------------
guestshell RUNNING
[guestshell@guestshell ~]$ python
Python 2.7.5 (default, Jun 17 2014, 18:11:42)
[GCC 4.8.2 20140120 (Red Hat 4.8.2-16)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import cli
>>> cli.cli("show app-hosting list")
'\nApp id State\n------------------------------------------------------\nguestshell RUNNING\n'
>>> exit()
[guestshell@guestshell ~]$
IOS-XEからGuest Shellのコマンドを実行するにはEEMを使用します。
event timer watchdog
をトリガーとすることで、pythonスクリプトを定期的に実行することができます。
CSR1#conf t
CSR1(config)#event manager applet elasticsearch
CSR1(config-applet)#event timer watchdog time 30
CSR1(config-applet)#action 100 cli command "enable"
CSR1(config-applet)#action 200 cli command "guestshell run python /home/guestshell/monitoring.py"
##実行スクリプト
それぞれの監視項目に対応するshowコマンドから目的の文字列を抽出してディクショナリ型オブジェクトの値とし、キーはMappingと同様のものを使用します。
時刻はフォーマットの変換、メモリ使用率は値の計算が必要です。
- 時刻(date) ->
show clock
- IOSdのCPU使用率(%) ->
show processes cpu | i CPU
- kernelのCPU使用率(%) ->
show processes cpu platform | i CPU
- IOSdのメモリ使用率(%) ->
show processes memory | i Processor
- kernelのメモリ使用率(%) ->
show processes memory platform | i System memory
- GigabitEthernet1の送受信量(bps) ->
show interface GigabitEthernet1
- Interface/line protocolのステータス一覧(text) ->
show ip interface brief
- ルーティングテーブル(text) ->
show ip route
すべてのディクショナリ型オブジェクトを連結しJSONデータとしてElasticsearchに送信します。
コードは下記リンク先に載せておきます。
https://gist.github.com/ecodrive-18/f808df5a69b9a47e97288789cfa53c9d
実行前に必要なライブラリはインストールしておきましょう。
CSR1#guestshell
[guestshell@guestshell ~]$ sudo pip install -r requirements.txt
elasticsearch>=6.0.0,<7.0.0
python-dateutil==2.8.1
##実行結果の確認と可視化
スクリプトの実行結果が格納できているかElasticsearchにクエリを投げてみます。
curl -XGET 'http://elasticsearch.example.com:9200/router-csr/monitoring/_search?size=1' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2765 100 2765 0 0 128k 0 --:--:-- --:--:-- --:--:-- 128k
{
"took": 13,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 232,
"max_score": 1,
"hits": [
{
"_index": "router-csr",
"_type": "monitoring",
"_id": "hHV2Xm4BlqMaqiTz5GDW",
"_score": 1,
"_source": {
"GigabitEthernet1_output": "30000",
"routing_table": "\nCodes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP\n D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area \n N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2\n E1 - OSPF external type 1, E2 - OSPF external type 2\n i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2\n ia - IS-IS inter area, * - candidate default, U - per-user static route\n o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP\n a - application route\n + - replicated route, % - next hop override, p - overrides from PfR\nGateway of last resort is 172.16.0.254 to network 0.0.0.0\nS* 0.0.0.0/0 [254/0] via 172.16.0.254\n 10.0.0.0/32 is subnetted, 2 subnets\nO 10.0.0.254 [110/2] via 192.168.255.2, 19:32:27, GigabitEthernet2\nO 10.100.0.254 [110/2] via 192.168.255.2, 19:32:27, GigabitEthernet2\n 172.16.0.0/16 is variably subnetted, 3 subnets, 2 masks\nC 172.16.0.0/24 is directly connected, GigabitEthernet3\nL 172.16.0.207/32 is directly connected, GigabitEthernet3\nS 172.16.0.254/32 [254/0] via 172.16.0.254, GigabitEthernet3\n 192.168.0.0/24 is variably subnetted, 2 subnets, 2 masks\nC 192.168.0.0/24 is directly connected, GigabitEthernet1\nL 192.168.0.254/32 is directly connected, GigabitEthernet1\nO 192.168.1.0/24 [110/2] via 192.168.255.2, 19:34:13, GigabitEthernet2\n 192.168.35.0/24 is variably subnetted, 2 subnets, 2 masks\nC 192.168.35.0/24 is directly connected, VirtualPortGroup0\nL 192.168.35.1/32 is directly connected, VirtualPortGroup0\n 192.168.255.0/24 is variably subnetted, 2 subnets, 2 masks\nC 192.168.255.0/30 is directly connected, GigabitEthernet2\nL 192.168.255.1/32 is directly connected, GigabitEthernet2\n",
"cpu_utilization_iosd": "0",
"cpu_utilization_kernel": "22",
"memory_utilization_kernel": "61",
"@timestamp": "2019-11-12T16:14:30+09:00",
"interface_status": "\nInterface IP-Address OK? Method Status Protocol\nGigabitEthernet1 192.168.0.254 YES manual up up \nGigabitEthernet2 192.168.255.1 YES manual up up \nGigabitEthernet3 172.16.0.207 YES DHCP up up \nVirtualPortGroup0 192.168.35.1 YES manual up up \n",
"memory_utilization_iosd": "14",
"GigabitEthernet1_input": "787000"
}
}
]
}
}
hits.hits._sourceの中にCSR1から送られてきた情報がありました。
ちゃんと格納されていますね。
Kibanaで可視化
Elasticsearchに蓄積した情報をKibanaで可視化してみます。
Visualizeで格納されたテキストをそのまま表示するという機能がなかなか見つからず...
CanvasのMarkdownにその機能がありそうだったので使用してみました。
コードブロックにテキストをそのまま表示させれば改行もされるのでいい感じですね。
もちろんグラフなども作成できます。
##まとめ
IOS-XEのGuest Shellから機器の情報を送信して可視化してみました。
この機能が多くの機器に実装されれば、SNMPではなく監視Agentを用いた機器監視ができるかもしれませんね。
ちなみにPull型の監視を行う場合、Guest ShellにIOSdのVTYを取得させたままPythonを常駐プロセスとしなければならないので、EEMのevent
を設定する際にmaxrun
を0とする必要があります。