4-byte AS の SNMP MIB 対応状況 (201904)
やること
JANOG20(2007.07.12) / 4-byte AS の世界へようこそ のスライド 45 で以下のように書かれています。
MIB関連 ■現状,最新のMIBは,RFC4273:Definitions of Managed Objects for BGP-4 ) □AS関係は,Inteter32 として規定されているが, bgpLocalAs OBJECT-TYPE SYNTAX Integer32 (0..65535) なっており,改訂が必要. □draft-ietf-idr-bgp4-mibv2 に取り込まれる方向 で議論が進行中 □が,現在,このドラフトはExpire状態
この状況が 12 年経った 2019/04
になっても変わっていなかった、というのを思い知ったので、その調査(ほぼ机上)を書き残したメモです。
「2019 年にもなって SNMP についてウダウダ言ってんじゃないよ!」と殴りかかるのは止めて下さい。
調査のきっかけ
ある日、4-byte Private AS(4200000001) を設定したネットワークスイッチ(仮想)を弄っていて
$ snmpwalk -v 2c -c <COMMUNITY_STRING> tor000b .1.3.6.1.2.1.15 | more BGP4-MIB::bgpVersion.0 = Hex-STRING: 10 BGP4-MIB::bgpLocalAs.0 = INTEGER: -94967295 BGP4-MIB::bgpIdentifier.0 = IpAddress: 172.31.0.1 BGP4-MIB::bgp4PathAttrPeer.172.16.0.0.16.172.30.128.1 = IpAddress: 172.30.128.1 BGP4-MIB::bgp4PathAttrPeer.172.16.0.0.16.172.30.128.3 = IpAddress: 172.30.128.3 BGP4-MIB::bgp4PathAttrPeer.172.16.0.0.16.172.30.128.5 = IpAddress: 172.30.128.5 BGP4-MIB::bgp4PathAttrPeer.172.17.0.0.16.172.30.128.1 = IpAddress: 172.30.128.1 BGP4-MIB::bgp4PathAttrPeer.172.17.0.0.16.172.30.128.3 = IpAddress: 172.30.128.3 BGP4-MIB::bgp4PathAttrPeer.172.17.0.0.16.172.30.128.5 = IpAddress: 172.30.128.5 ...snip
BGP4-MIB
を舐めていたら BGP4-MIB::bgpLocalAs.0 = INTEGER: -94967295
という Overflow に出くわしました。
別にこれを SNMP でとりたいという強い想いは全くないのですが。
4-byte AS に MIB が未対応なんだろなあ...、いや今 2019 年だぞ??って少し気になって調べることに。
調査
JANOG20(2007.07.12) スライド / 4-byte AS の世界へようこそ
まず雑にググっていたら冒頭のスライド JANOG20(2007.07.12) / 4-byte AS の世界へようこそ が目に入りました。
でも今 2019 だぞ??(しつこい)
RFC4273 Definitions of Managed Objects for BGP-4
最初に突いた BGP4-MIB
の定義をしている RFC4273 / Definitions of Managed Objects for BGP-4 を参照しました。
これは RFC1657 / Definitions of Managed Objects for the Fourth Version of the Border Gateway Protocol (BGP-4) using SMIv2 を Obsoletes しているやつ。
bgpLocalAs OBJECT-TYPE SYNTAX Integer32 (0..65535) MAX-ACCESS read-only STATUS current DESCRIPTION "The local autonomous system number." REFERENCE "RFC 4271, Section 4.2, 'My Autonomous System'." ::= { bgp 2 } -- BGP Peer table. This table contains, one entry per -- BGP peer, information about the BGP peer.
Integer32 (0..65535)
...のままなんですが!!!
draft-ietf-idr-bgp4-mibv2
冒頭のスライドでは「draft-ietf-idr-bgp4-mibv2 に取り込まれる方向で議論が進行中(が,現在,このドラフトはExpire状態)」と書かれていたもので、見に行ってみると...
Tracker / draft-ietf-idr-bgp4-mibv2 ...はい、確かに Expire 状態です。
draft-ietf-idr-bgp4-mibv2 / 7. Definitions で AS Number 周りの情報を見ると
BGP4V2-MIB DEFINITIONS ::= BEGIN IMPORTS MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, mib-2, Counter32, Gauge32, Unsigned32 FROM SNMPv2-SMI InetAddressType, InetAddress, InetPortNumber, InetAutonomousSystemNumber, InetAddressPrefixLength FROM INET-ADDRESS-MIB ...snip
と、AS Number の型定義は INET-ADDRESS-MIB
から引っ張ってきていて。
それが定義されている RFC4001 / Textual Conventions for Internet Network Addresses を見ると
InetAutonomousSystemNumber ::= TEXTUAL-CONVENTION DISPLAY-HINT "d" STATUS current DESCRIPTION "Represents an autonomous system number that identifies an Autonomous System (AS). An AS is a set of routers under a single technical administration, using an interior gateway protocol and common metrics to route packets within the AS, and using an exterior gateway protocol to route packets to other ASes'. IANA maintains the AS number space and has delegated large parts to the regional registries. Autonomous system numbers are currently limited to 16 bits (0..65535). There is, however, work in progress to enlarge the autonomous system number space to 32 bits. Therefore, this textual convention uses an Unsigned32 value without a range restriction in order to support a larger autonomous system number space." REFERENCE "RFC 1771, RFC 1930" SYNTAX Unsigned32
これは将来的な拡張のために...という理由と共に Unsigned32
と定義されております。
なので draft-ietf-idr-bgp4-mibv2
に基づいた実装ならば、4-byte AS Welcome なんでしょー。
Network OS の Private MIB
いわゆる大手メーカ NW 機器では Private MIB でどうにかしてるんだろーなーってことで、軽く見てみます。
一例 Juniper
Juniper の場合には Private MIB の BGP4-V2-MIB-JUNIPER (mib-jnx-bgpmib2)
で
- Juniper 公式 SNMP MIB Explorer / JUNOS 18.4R1 / jnxBgpM2AsSize
- Juniper 公式 SNMP MIB Explorer / JUNOS 18.4R1 / jnxBgpM2LocalAs
以下のように 4-byte AS を使えるように定義しています。
REFERENCE として draft-ietf-idr-as4bytes-04
...のちの RFC6793 / BGP Support for Four-Octet Autonomous System (AS) Number Space を明言してます。
jnxBgpM2AsSize OBJECT-TYPE SYNTAX INTEGER { twoOctet(1), fourOctet(2) } MAX-ACCESS read-only STATUS current DESCRIPTION "The size of the AS value in this implementation. The semantics of this are determined as per the as-4bytes draft." REFERENCE "draft-ietf-idr-as4bytes-04" ::= { jnxBgpM2BaseScalars 4 } jnxBgpM2LocalAs OBJECT-TYPE SYNTAX InetAutonomousSystemNumber MAX-ACCESS read-only STATUS current DESCRIPTION "The local autonomous system number. If the jnxBgpM2AsSize is twoOctet, then the range is constrained to be 0-65535." ::= { jnxBgpM2BaseScalars 5 }
一例 Arista
Arista の場合には Private MIB の ARISTA-BGP4V2-MIB で 4-byte AS を使えるように定義しています。
まず冒頭に extracted from draft-ietf-idr-bgp4-mibv2-13.txt
(例の Expire しているやつで Author は Juniper の人) と書いてあります。
-- extracted from draft-ietf-idr-bgp4-mibv2-13.txt -- at Tue Mar 13 06:12:27 2012
具体的に AS Number についての定義を見ると
ARISTA-BGP4V2-MIB DEFINITIONS ::= BEGIN IMPORTS MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, Counter32, Gauge32, Unsigned32 FROM SNMPv2-SMI InetAddressType, InetAddress, InetPortNumber, InetAutonomousSystemNumber, InetAddressPrefixLength FROM INET-ADDRESS-MIB ...snip aristaBgp4V2PeerLocalAs OBJECT-TYPE SYNTAX InetAutonomousSystemNumber MAX-ACCESS read-only STATUS current DESCRIPTION "Some implementations of BGP can represent themselves as multiple ASes. This is the AS that this peering session is representing itself as to the remote peer." ::= { aristaBgp4V2PeerEntry 7 }
てな具合に AS 番号の定義は INET-ADDRESS-MIB
から持ってきているよーで、
これは draft-ietf-idr-bgp4-mibv2 の項で追った通り RFC4001 / Textual Conventions for Internet Network Addresses にて Unsigned32
で定義されております。
一例 Cumulus Linux
Cumulus Linux / Supported MIBs を見ると、BGP 周りの Private MIB は無さそうです。
少し脇に反れますが Cumulus Linux / Enable SNMP Support for FRRouting を眺めると...
At this time, SNMP does not support monitoring BGP unnumbered neighbors.
ってことで「4-byte AS 番号を使って FRRouting 同士は Unnumberd 設定にして......」なんていうことをしようとすると、実質 SNMP は使えたもんじゃなさそうです。
実際冒頭で試した target は Cumulus VX だったのですが、確かに「Unnumberd な Peer 情報はとれず、Local AS Number は Overflow」って状況でした。
ちなみに Cumulus Linux の Community Slack でこの話をしたら、Cumulus Linux SE の人から以下のように返信が。うーーん、ごもっとも。
That is one of the problems with the BGP (and others) snmp MIBs that they don’t support new standards.
Personally I always advice to look at a different solution then SNMP.
OpenConfig YANG Model
現代の SNMP MIB と言っても過言ではない(貶しているわけじゃないです、念のため) OpenConfig の YANG Model だと openconfig-inet-types.yang / typedef as-number
typedef as-number { type uint32; description "A numeric identifier for an autonomous system (AS). An AS is a single domain, under common administrative control, which forms a unit of routing policy. Autonomous systems can be assigned a 2-byte identifier, or a 4-byte identifier which may have public or private scope. Private ASNs are assigned from dedicated ranges. Public ASNs are assigned from ranges allocated by IANA to the regional internet registries (RIRs)."; reference "RFC 1930 Guidelines for creation, selection, and registration of an Autonomous System (AS) RFC 4271 A Border Gateway Protocol 4 (BGP-4)"; }
はい、流石に 4-byte AS 対応しているっぽいですねー。
おしまい
- SNMP MIB なんて、この件に限らずこんな感じですよねー
- OpenConfig の YANG Model だって "Standard(?) + メーカの Private" っていう大枠の仕組みは一緒だし、同じようなことは起き(てい)る/起きていくんでしょうね...
- ...2019年にもなって改めて語るような内容でもなかったわ(完)
Cumulus VX の HTTP API を弄る
はじめに
やること
Cumulus Linux にも HTTP API
というリモートから HTTP/HTTPS でアクセスして情報取得・設定変更・メンテナンス操作が出来る API があります。
ザックリ言うと「HTTP リクエストの中に CLI コマンドを入れて投げつける」系のやつです(Arista EOS eAPI や Cisco NX-OS の NX-API CLI と同じノリ)。
そういう作りな分、クライアント側の実装方式は割と何でも使えるのは嬉しい。
Cumulus Linux を相手にした時、実際のところ
- Ansible の各種 Linux 系 Modules (files, apt, systemd etc) による Linux 的な操作
- Ansible の Cumulus Linux 専用 Modules (nclu) による Network OS 的な操作
- Ansible 公式 / nclu module を参照 (Examples で雰囲気を察することができる)
- 内容は CLI で使う net 系コマンド(NCLU) を使ってコマンドを叩き込む expect 的なやつ(雑)
で完結できそうなのですが、後者相当のことを API でもできますよ、って話です。
繰り返し・条件判定などを要するちょっとした処理を、使い慣れた言語で使い捨てスクリプトを作ってサクッと済ませたい(人によっては Ansible Playbook の構文に付き合うより楽な筈)というような時に使えそうです。
参考資料
これだけ
- Cumulus Networks 公式 / HTTP API
- 設定方法と超基本的な使い方サンプル
- Cumulus Networks 公式 / SOLUTION / Automation
- Automation 周りの KB 色々
- Ansible とか Serverspec なんかの「サーバと同じように運用管理できるぜ!」って推している雰囲気の記事が多し
- HTTP API の話はなさげ
API Doc 的なやつは見つからず......。
環境情報
仮想版の Cumulus VX を使っております。
アカウントを作ればだれでも使える(2019/03/09
現在)やつで、使い方の公式リンクは 「Cumulus VX で VXLAN+EVPN (original : 2017/03/22) / 参考資料」あたりにも纏めているので、適宜ご参照下さいませ。
cumulus@tor000a:~$ net show system Hostname......... tor000a Build............ Cumulus Linux 3.7.3 Uptime........... 22 days, 21:36:43.650000 Model............ Cumulus VX Memory........... 426MB Disk............. 6GB Vendor Name...... Cumulus Networks Part Number...... 3.7.3 Base MAC Address. 0C:AA:FD:C7:3D:00 Serial Number.... 0c:aa:fd:c7:3d:00 Product Name..... VX cumulus@tor000a:~$ cat /etc/os-release NAME="Cumulus Linux" VERSION_ID=3.7.3 VERSION="Cumulus Linux 3.7.3" PRETTY_NAME="Cumulus Linux" ID=cumulus-linux ID_LIKE=debian CPE_NAME=cpe:/o:cumulusnetworks:cumulus_linux:3.7.3 HOME_URL="http://www.cumulusnetworks.com/" SUPPORT_URL="http://support.cumulusnetworks.com/"
API を突くクライアント側は...まー何でもいいですわ。
使う
事前処理 Cumulus Linux で機能有効化
Cumulus Linux 公式 / HTTP API に従ってやれば良し。
必要なパッケージは最初から入っていそうなので
cumulus@tor000a:~$ apt search python-cumulus-restapi Sorting... Done Full Text Search... Done python-cumulus-restapi/updates,now 0.1-cl3u9 all [installed] Rest API for Cumulus Networks
必要なサービスを起動して (認証方式や待ち受けポートなどなどは、必要に応じて nginx
の設定ファイルを弄る、ここではデフォルトのまま)
cumulus@tor000a:~$ systemctl status restserver cumulus@tor000a:~$ sudo systemctl enable restserver cumulus@tor000a:~$ sudo systemctl start restserver cumulus@tor000a:~$ systemctl status restserver cumulus@tor000a:~$ systemctl status nginx
これだけ。
試用
まずは公式ガイドを参考に available な endpoint 一覧を取得。
endpoint を見ても、何となく機能が限られていそうな雰囲気(BGP とかを動かしているのに routing protocol 関係などが全く見当たらない)しか......。
cumulus@tor000a:~$ curl -X GET -k -u cumulus:CumulusLinux! https://127.0.0.1:8080 | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 573 100 573 0 0 12220 0 --:--:-- --:--:-- --:--:-- 12456 { "endpoints": { "bridgeCmd": "/ml2/v1/bridge/{bridge_name}/{vlan_id}", "bridgeIntfCmd": "/ml2/v1/bridge/{bridge_name}/{vlan_id}/hosts/{host}", "bridgeVxlanCmd": "/ml2/v1/bridge/{bridge_name}/{vlan_id}/vxlan/{vni_id}", "hashCmd": "/ml2/v1/hash", "intfCmd": "/ml2/v1/networks/{network_id}/hosts/{host}", "main": "/", "networkCmd": "/ml2/v1/networks/{network_id}", "rpc": "/nclu/v1/rpc", "vxlanCmd": "/ml2/v1/networks/{network_id}/vxlan/{vni}" }, "version": { "api_codename": "evo", "api_status": "GA", "api_version": "0.0.2", "documentation": "http://docs.cumulusnetworks.com" } }
公式ガイドを参考に投げ込んでみると。
これは endpoint 的には /nclu/v1/rpc
を使って CLI でいうところの net
コマンドを包んで投げつけておきゃー大抵のこと出来るんだろうな、って察しました。
また、net コマンドでは json
とつけると JUNOS の display json
や EOS の | json
みたいに JSON 形式で出力を得られたり得られなかったりします(全部得られるのか知らん)。
cumulus@tor000a:~$ curl -X POST -k -u cumulus:CumulusLinux! -H "Content-Type: application/json" -d '{"cmd": "show counters"}' https://127.0.0.1:8080/nclu/v1/rpc Kernel Interface table Iface MTU Met RX_OK RX_ERR RX_DRP RX_OVR TX_OK TX_ERR TX_DRP TX_OVR Flg ------- ----- ----- ------- -------- -------- -------- ------- -------- -------- -------- ----- eth0 1500 0 216222 0 0 0 84162 0 0 0 BMRU lo 65536 0 131 0 0 0 131 0 0 0 LRU swp1 9216 0 127045 0 0 0 158455 0 0 0 BMRU swp2 9216 0 108224 0 0 0 155806 0 0 0 BMRU swp3 9216 0 127772 0 0 0 157187 0 0 0 BMRU swp8 9216 0 74970 0 2 0 79474 0 0 0 BMRU swp9 9216 0 72022 0 5 0 72909 0 0 0 BMRU cumulus@tor000a:~$ curl -X POST -k -u cumulus:CumulusLinux! -H "Content-Type: application/json" -d '{"cmd": "show counters json"}' https://127.0.0.1:8080/nclu/v1/rpc { "eth0": { "Flg": "BMRU", "MTU": 1500, "Met": 0, "RX_DRP": 0, "RX_ERR": 0, "RX_OK": 216459, "RX_OVR": 0, "TX_DRP": 0, "TX_ERR": 0, "TX_OK": 84284, "TX_OVR": 0 }, "lo": { ...<snip>
設定変更系
雰囲気でやってますが、net pending
は JUNOS でいうところの show | compare
で net commit
は commit
みたいなもんです、多分。
cumulus@tor000a:/home/kotetsu$ curl -X POST -k -u cumulus:CumulusLinux! -H "Content-Type: application/json" -d '{"cmd": "add time ntp server 10.0.0.64 iburst"}' https://127.0.0.1:8080/nclu/v1/rpc cumulus@tor000a:/home/kotetsu$ curl -X POST -k -u cumulus:CumulusLinux! -H "Content-Type: application/json" -d '{"cmd": "pending"}' https://127.0.0.1:8080/nclu/v1/rpc --- /etc/ntp.conf 2018-09-05 14:20:56.000000000 +0900 +++ /run/nclu/ntp/ntp.conf 2019-03-09 20:31:28.736733340 +0900 @@ -53,10 +53,11 @@ #broadcast 192.168.123.255 # If you want to listen to time broadcasts on your local subnet, de-comment the # next lines. Please do this only if you trust everybody on the network! #disable auth #broadcastclient # Specify interfaces, don't listen on switch ports interface listen eth0 +server 10.0.0.64 iburst net add/del commands since the last "net commit" ================================================ User Timestamp Command ------- -------------------------- ---------------------------------------- cumulus 2019-03-09 20:31:28.738095 net add time ntp server 10.0.0.64 iburst cumulus@tor000a:/home/kotetsu$ curl -X POST -k -u cumulus:CumulusLinux! -H "Content-Type: application/json" -d '{"cmd": "commit"}' https://127.0.0.1:8080/nclu/v1/rpc --- /etc/ntp.conf 2018-09-05 14:20:56.000000000 +0900 +++ /run/nclu/ntp/ntp.conf 2019-03-09 20:31:28.736733340 +0900 @@ -53,10 +53,11 @@ #broadcast 192.168.123.255 # If you want to listen to time broadcasts on your local subnet, de-comment the # next lines. Please do this only if you trust everybody on the network! #disable auth #broadcastclient # Specify interfaces, don't listen on switch ports interface listen eth0 +server 10.0.0.64 iburst net add/del commands since the last "net commit" ================================================ User Timestamp Command ------- -------------------------- ---------------------------------------- cumulus 2019-03-09 20:31:28.738095 net add time ntp server 10.0.0.64 iburst cumulus@tor000a:/home/kotetsu$ curl -X POST -k -u cumulus:CumulusLinux! -H "Content-Type: application/json" -d '{"cmd": "pending"}' https://127.0.0.1:8080/nclu/v1/rpc cumulus@tor000a:/home/kotetsu$
Cumulus Linux ローカルで突いていますが、remote からも勿論突けます。
$ curl --noproxy "*" -X POST -k -u cumulus:CumulusLinux! -H "Content-Type: application/json" -d '{"cmd": "show counters json"}' https://tor000a:8080/nclu/v1/rpc { "eth0": { "Flg": "BMRU", "MTU": 1500, "Met": 0, "RX_DRP": 0, "RX_ERR": 0, "RX_OK": 223026, "RX_OVR": 0, "TX_DRP": 0, "TX_ERR": 0, "TX_OK": 85717, "TX_OVR": 0 }, ...<snip>
公式ガイドの最後に申し訳程度に載っていたサンプルをそのまま実行して /nclu/v1/rpc
以外の endpoint への PUT なんかも一応動くみたいだとはわかりましたが......。
いかんせんガイドもないので、何も分からん。
いわゆる普通の REST API っぽい雰囲気は醸し出しておりますが...。
cumulus@tor000a:~$ curl -X PUT -k -u cumulus:CumulusLinux! https://127.0.0.1:8080/ml2/v1/bridge/"br1"/200 "" cumulus@tor000a:~$ ip l sh br1 12: br1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default link/ether f6:81:d1:12:75:fa brd ff:ff:ff:ff:ff:ff cumulus@tor000a:~$ bridge -d vlan port vlan ids br1 200
まあ /etc/network/interfaces
に書き込まれちゃいないので、再起動すりゃー消えるんですが。
サンプルスクリプト
適当なサンプルとして以下のような処理をしてみます。
- 対象機器の BGP Neighbor を走査して
- 以下の全ての条件に合致する Neighbor だった場合は
- Local の Peer-Group が PG_SPINE である
- Remote の hostname が正規表現で "a$" に match しない (Remote の hostname capability が動いている前提)
- BGP clear する
......あんまり実用的ではない気はしますが、繰り返し処理や条件処理をして Ansible で書くのに僕が面倒くささを感じるあたり、を例示したものです。
言語的には HTTP リクエストと Response の JSON 構造パースあたりがあれば何でも良いので、好きなもの使えるよって言いたいだけ。(shell + curl + jq 程度でも)
ちなみに、あんまりシンプルだからか Cumulus Networks の Github リポジトリ を探してもラッパライブラリは見つからなかったです。
例示環境の対象 BGP 設定ですが、以下が /etc/frr/frr.conf
より抜粋です。
interface swp1 description DEV=node1 IF=ens4 ! interface swp2 description DEV=node2 IF=ens4 ! interface swp3 description DEV=node3 IF=ens4 ! interface swp8 description DEV=spine000a IF=swp1 ! router bgp 4200000000 bgp router-id 172.31.0.0 bgp bestpath as-path multipath-relax bgp bestpath compare-routerid neighbor PG_K8S peer-group neighbor PG_K8S remote-as external neighbor PG_K8S description k8s-node neighbor PG_SPINE peer-group neighbor PG_SPINE remote-as external neighbor PG_SPINE description tor neighbor swp1 interface peer-group PG_K8S neighbor swp2 interface peer-group PG_K8S neighbor swp3 interface peer-group PG_K8S neighbor swp8 interface peer-group PG_SPINE neighbor swp9 interface peer-group PG_SPINE ! address-family ipv4 unicast redistribute connected exit-address-family !
んで、さっきの処理を今回は Ruby で書きました。
例によって、例外処理とかは適当な即席なやつで。
# encoding: utf-8 require 'rest-client' require 'optparse' require 'json' require 'pp' def exec_nclu(params, cmd) uri = "https://#{params[:target]}:8080/nclu/v1/rpc" payload = { 'cmd': cmd } headers = {content_type: 'application/json'} response = RestClient::Request.execute( :url => uri, :user => params[:username], :password => params[:password], :method => :post, :headers => headers, :verify_ssl => false, :payload => payload.to_json ) case response.code when 200 return JSON.parse(response) rescue "" else p response.code return nil end end # # ARGV check # # set default params arg_configs = { #:username => ENV['CUMULUS_USER'], #:password => ENV['CUMULUS_PASS'] } # required ARGV arg_required = [ :target, :username, :password ] # parse ARGV OptionParser.new do |opts| begin opts = OptionParser.new opts.on('-t', '--target STRING', "reaquired param. IP address or FQDN of Cumulus.") { |v| arg_configs[:target] = v } opts.on('-u', '--username STRING', "username of Cumulus.") { |v| arg_configs[:username] = v } opts.on('-p', '--password STRING', "password of Cumulus.") { |v| arg_configs[:password] = v } opts.parse!(ARGV) for field in arg_required raise ArgumentError.new("recuired param #{field} is not set...") if arg_configs[field].nil? end rescue => e puts opts.help puts puts e.message exit 1 end end # # main # begin # clean proxy env if I need ["http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY"].each do |environment| ENV.delete(environment) end bgp_nei = exec_nclu(arg_configs, "show bgp neighbor json") exit if bgp_nei.nil? pp bgp_nei # for debug # key : value (portname : BGP Nei status) bgp_nei.each do |port,value| if value["peerGroup"] != "PG_SPINE" then puts "#{port} BGP peerGroup : #{value["peerGroup"]} good bye." next end puts "#{port} BGP peerGroup : #{value["peerGroup"]}" if value["hostname"] =~ Regexp.new("a$") then puts "#{port} BGP Remote Hostname : #{value["hostname"]} is system A, good bye." next end puts "#{port} BGP Remote Hostname : #{value["hostname"]} , OK, Let's clear!!" clear_bgp = exec_nclu(arg_configs, "clear bgp #{port}") puts "#{port} BGP Remote Hostname : #{value["hostname"]}, executed clear!!" if clear_bgp == "" end rescue Exception=>e puts e puts e.backtrace puts "\n\n!! Oh!! Exception!!\n\n" end
んで、これを実行すると
$ bundle exec ruby cumulus.rb -t tor000a -u cumulus -p CumulusLinux! {"swp1"=> {"bgpNeighborAddr"=>"172.30.0.1", "remoteAs"=>4290000001, "localAs"=>4200000000, "nbrExternalLink"=>true, "peerGroup"=>"PG_K8S", "bgpVersion"=>4, "remoteRouterId"=>"172.16.37.60", "bgpState"=>"Established", "bgpTimerUp"=>8561000, "bgpTimerUpMsec"=>8561000, "bgpTimerUpString"=>"02:22:41", "bgpTimerUpEstablishedEpoch"=>1552010935, "bgpTimerLastRead"=>1000, "bgpTimerLastWrite"=>1000, "bgpInUpdateElapsedTimeMsecs"=>8561000, "bgpTimerHoldTimeMsecs"=>9000, "bgpTimerKeepAliveIntervalMsecs"=>3000, "neighborCapabilities"=> {"4byteAs"=>"advertisedAndReceived", "addPath"=> {"IPv4 Unicast"=>{"txReceived"=>true, "rxAdvertisedAndReceived"=>true}}, "routeRefresh"=>"advertisedAndReceivedNew", "multiprotocolExtensions"=> {"IPv4 Unicast"=>{"advertisedAndReceived"=>true}}, "hostName"=>{"advHostName"=>"tor000a", "advDomainName"=>"n/a"}, "gracefulRestart"=>"advertisedAndReceived", "gracefulRestartRemoteTimerMsecs"=>120000, "addressFamiliesByPeer"=>{"IPv4 Unicast"=>{}}}, "gracefulRestartInfo"=> {"endOfRibSend"=>{"IPv4 Unicast"=>true}, "endOfRibRecv"=>{"IPv4 Unicast"=>true}}, ...<snip> "swp9"=> {"bgpNeighborAddr"=>"fe80::eaa:fdff:fe52:8e01", "remoteAs"=>4210000001, "localAs"=>4200000000, "nbrExternalLink"=>true, "hostname"=>"spine000b", "peerGroup"=>"PG_SPINE", "bgpVersion"=>4, "remoteRouterId"=>"172.31.1.1", "bgpState"=>"Established", "bgpTimerUp"=>31000, "bgpTimerUpMsec"=>31000, "bgpTimerUpString"=>"00:00:31", "bgpTimerUpEstablishedEpoch"=>1552019465, "bgpTimerLastRead"=>1000, "bgpTimerLastWrite"=>1000, "bgpInUpdateElapsedTimeMsecs"=>29000, "bgpTimerHoldTimeMsecs"=>9000, "bgpTimerKeepAliveIntervalMsecs"=>3000, "neighborCapabilities"=> {"4byteAs"=>"advertisedAndReceived", "addPath"=>{"IPv4 Unicast"=>{"rxAdvertisedAndReceived"=>true}}, "extendedNexthop"=>"advertisedAndReceived", "extendedNexthopFamililesByPeer"=>{"IPv4 Unicast"=>"recieved"}, "routeRefresh"=>"advertisedAndReceivedOldNew", "multiprotocolExtensions"=> {"IPv4 Unicast"=>{"advertisedAndReceived"=>true}}, "hostName"=> {"advHostName"=>"tor000a", "advDomainName"=>"n/a", "rcvHostName"=>"spine000b", "rcvDomainName"=>"n/a"}, "gracefulRestart"=>"advertisedAndReceived", "gracefulRestartRemoteTimerMsecs"=>120000, "addressFamiliesByPeer"=>"none"}, "gracefulRestartInfo"=> {"endOfRibSend"=>{"IPv4 Unicast"=>true}, "endOfRibRecv"=>{"IPv4 Unicast"=>true}}, ...<snip> swp1 BGP peerGroup : PG_K8S good bye. swp2 BGP peerGroup : PG_K8S good bye. swp3 BGP peerGroup : PG_K8S good bye. swp8 BGP peerGroup : PG_SPINE swp8 BGP Remote Hostname : spine000a is system A, good bye. swp9 BGP peerGroup : PG_SPINE swp9 BGP Remote Hostname : spine000b , OK, Let's clear!! swp9 BGP Remote Hostname : spine000b, executed clear!!
とまあ、条件に合致した swp9
の Neighbor だけが BGP clear されましたとさ。めでたしめでたし。
おしまい
Cumulus Linux って名前からして「設定ファイルを書き換えて systemctl で操作!Linux と同じ管理ツールが使えて嬉しいな!!!以上!!」って感じかと勝手に思っていたんですが、こんな風にネットワーク屋さん向けなユルフワ(失礼)な API もあったんですねえ。
そんなわけで、今回は排他処理がどうこうとか面倒なことは一切していないユルフワな「やってみた」系の内容でした。
Arista + Openconfigbeat で試す OpenConfig gNMI ベース Telemetry
はじめに
やること
個人でもサクッと入手できる実装が整ってきている感じなので、軽く試してみます。
これまでは gRPC server 側に追加パッケージが必要かつ個人アカウントでは取得できない、とか gRPC client の実装が大変(& 個人で実装している特定 NOS 向け野良とか、メーカが出している自社 NOS 専用のはあったけど)とかで手を出しにくかったんですが、今やそんなこともなさそーだという。
以下やること概要
- OpenConfig の Telemety 動作を試す
gNMI (gRPC Network Management Interface)
ベースで動かす- gRPC client (collector) 側
- Arista の Openconfigbeat という OpenConfig 準拠(っぽい)ものを使う
- Elastic の Beats というフレームワークで実装されているので、Elasticsearch / Kibana との連携が楽
- gRPC server に subscribe して streaming push を受け取り、Elasticsearch にデータを叩き込む
- Arista の Openconfigbeat という OpenConfig 準拠(っぽい)ものを使う
- gRPC server 側
- Arista vEOS
- OpenConfig gNMI ベースで動かすのが手っ取り早そう & openconfigbeat のテストにはきっと EOS を使っているに違いない(Inteoperability でハマりにくそう)と推測したから
- Arista vEOS
- データ保持・視覚
- Elasticsearch + Kibana
- Beats ベースな openconfigbeat と組み合わせて使うのに、一番手っ取り早かったから
- Elasticsearch + Kibana
参考資料
- OpenConfig
- GitHub / openconfig/reference / gRPC Network Management Interface (gNMI)
- gNMI の仕様
- 3.5 Subscribing to Telemetry Updates
- 今回取り扱う「gRPC client (collector) が gRPC server (NW装置) に Subscribe 要求することで、以降 NW 装置からの update Push を受ける」動きの詳細が定義されている
- ちなみに
openconfig.proto
はdeprecated - and has been replaced with the gRPC Network Management Interface.
という扱いになっている
- GitHub / openconfig/public
YANG
データモデル
- GitHub / openconfig/reference / gRPC Network Management Interface (gNMI)
- Arista
- ARISTA EOS Central / OpenConfig 4.20.2.1F Release Notes
- (v)EOS 側の OpenConfig 情報全般
- (v)EOS を gRPC server として動かすための設定方法
- 準じている OpenConfig の仕様/バージョン
- 対応している YANG モデルの Path 一覧
- (v)EOS 側の OpenConfig 情報全般
- Github / aristanetworks/openconfigbeat
- 今回使う Openconfig client 実装
- 単に Arista の人が作っているというだけで、実装としては OpenConfig 準拠で 非 ARISTA な gRPC server も取り扱えそう
- Elastic の Beats というプラットフォームで実装されているので、サクッと Elasticsearch に叩き込むところまでやってくれる
- Elastic 公式 / Beats
- GitHub / elastic/beats
- Elastic 公式 / Community Beats
- Beats の各種コミュニティが並んでおり、openconfigbeat もここに並んでいる
- Elastic 公式ブログ / 09 OCTOBER 2016 / Brewing in Beats: New community Beat for network devices
- 冒頭に
New community beat: Openconfigbeat
としてサラリと紹介されている
- 冒頭に
- ARISTA EOS Central / OpenConfig 4.20.2.1F Release Notes
環境情報
Arista EOS 側
いつも通りの vEOS-lab
vEOS01#show ver Arista vEOS Hardware version: Serial number: System MAC address: 0c00.da26.7071 Software image version: 4.20.1F Architecture: i386 Internal build version: 4.20.1F-6820520.4201F Internal build ID: 790a11e8-5aaf-4be7-a11a-e61795d05b91 Uptime: 21 minutes Total memory: 2017260 kB Free memory: 1232576 kB
Openconfigbeat 側
$ cat /etc/os-release NAME="Ubuntu" VERSION="18.04 LTS (Bionic Beaver)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 18.04 LTS" VERSION_ID="18.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=bionic UBUNTU_CODENAME=bionic $ uname -a Linux ocb 4.15.0-23-generic #25-Ubuntu SMP Wed May 23 18:02:16 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
Openconfigbeat をビルドするのに go と glide が必要です(が GitHub にビルド済のファイルもあるし、Docker でも動くようなので、動かし方によっては必要ない)
$ go version go version go1.10.1 linux/amd64 $ glide --version glide version 0.13.1-3
とってきた Openconfigbeat は以下
$ git show commit 32d071a8bdaf7f7f2b3aa8e504d171c888a14113 (HEAD -> master, origin/master, origin/HEAD) Author: Giuseppe Valente <gvalente@arista.com> Date: Wed Mar 14 12:21:13 2018 -0700 docker: download latest release Change-Id: I3015b84b6f438bba8ddb884e2f332fad4f5e16e1
Elasticsearch + Kibana 環境
Elasticsearch と Kibana は docker でテキトーに動かします。母艦のサーバとしては、Openconfigbeat と相乗りです。
$ docker --version Docker version 18.03.1-ce, build 9ee9f40 $ docker-compose --version docker-compose version 1.21.2, build a133471
Elasticsearch も Kibana も 6.2.4
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 99fc3355e3b6 docker.elastic.co/kibana/kibana:6.2.4 "/bin/bash /usr/loca…" 4 days ago Up 4 days 0.0.0.0:5601->5601/tcp kibana 69663eaabc79 docker.elastic.co/elasticsearch/elasticsearch:6.2.4 "/usr/local/bin/dock…" 4 days ago Up 4 days 0.0.0.0:9200->9200/tcp, 9300/tcp elasticsearch $ curl -XGET 'http://localhost:9200' { "name" : "r5cOGL4", "cluster_name" : "docker-cluster", "cluster_uuid" : "wMLRO2rxT0aUX2WrcybkSw", "version" : { "number" : "6.2.4", "build_hash" : "ccec39f", "build_date" : "2018-04-12T20:37:28.497551Z", "build_snapshot" : false, "lucene_version" : "7.2.1", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search" }
動かす
Elasticsearch + Kibana 環境 構築
今回は openconfigbeat と同じサーバ上で virtualenv で動かしている docker-compose を使って立ち上げました。以下が使った docker-compose.yml
version: '3' services: elasticsearch: container_name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:6.2.4 environment: - cluster.name=docker-cluster - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" ulimits: memlock: soft: -1 hard: -1 volumes: - esdata1:/usr/share/elasticsearch/data ports: - 9200:9200 kibana: container_name: kibana image: docker.elastic.co/kibana/kibana:6.2.4 ports: - 5601:5601 environment: SERVER_NAME: ocb ELASTICSEARCH_URL: http://<母艦のIPアドレス>:9200 volumes: esdata1: driver: local
vEOS 側
ARISTA EOS Central / OpenConfig 4.20.2.1F Release Notes を参考に gNMI ベースで動かす設定を入れるだけです。
vEOS01(config)#management api gnmi vEOS01(config-mgmt-api-gnmi)#transport grpc ? WORD transport name vEOS01(config-mgmt-api-gnmi)#transport grpc TEST-vEOS01 vEOS01#bash [kotetsu@vEOS01 ~]$ [kotetsu@vEOS01 ~]$ ss -natu | grep 6030 tcp LISTEN 0 1024 :::6030 :::*
これで Openconfigbeat (gRPC client)からの Subscribe 要求を受ける server 動作をしている筈。
なお、Openconfigbeat 側からの user / password 認証を受けられるように、アカウントは作っておきます(ssh とかして設定している時点で、それは済んでいる筈...)。
ちなみに、プロセスやらログやらを眺めていると vEOS 上で動く server 実装も Go みたいです。今まで vEOS 上で動くもろもろって Python ばかりだった気がしますが。
Openconfigbeat 側
ビルド環境準備
前述の通り GitHub にビルド済のファイルもあるし、Docker でも動くようなので、動かし方によっては必要ないですが。今回はビルドからやっていきます。
まずは Openconfigbeat をビルドするための requirements に入っている go と glide をば。以下を参考にパパッと。
$ sudo apt install -y software-properties-common $ sudo add-apt-repository ppa:gophers/archive $ sudo apt install -y golang-1.10-go $ echo "export PATH=$PATH:/usr/lib/go-1.10/bin/" | sudo tee /etc/profile.d/golang.sh $ sudo apt install -y golang-glide
本筋ではないですが、久々に glide を使ったら前述の glide README に以下の表記が...。
The Go community now has the dep project to manage dependencies. Please consider trying to migrate from Glide to dep. If there is an issue preventing you from migrating please file an issue with dep so the problem can be corrected. Glide will continue to be supported for some time but is considered to be in a state of support rather than active feature development.
設定作成~ビルド
まずは GitHub から clone してきて
$ mkdir -p /home/kotetsu/go/src/github.com/aristanetwork $ cd /home/kotetsu/go/src/github.com/aristanetworks/ $ git clone https://github.com/aristanetworks/openconfigbeat.git $ cd openconfigbeat/
<git clone してきた dir>/_meta/beat.yml
が設定ファイルなので、必要な情報を書き換えます。
$ more ~/go/src/github.com/aristanetworks/openconfigbeat/_meta/beat.yml ################### Openconfigbeat Configuration Example ######################### ############################# Openconfigbeat ###################################### openconfigbeat: # The addresses of the OpenConfig devices to connect to. addresses: ["10.0.0.171"] # The OpenConfig paths to subscribe to. paths: ["/"] # The default port to connect to if none is configured. default_port: 6030 # The username on the device. username: "kotetsu" # The password for the user on the device. password: "kotetsu"
上の例だと、以下の感じ。
openconfigbeat
- gRPC server の情報
addresses
- vEOS の IP アドレスを配列で書き連ねる
paths
- suvscribe するツリーの指定
/
にしておけば、とれるもんは全部送ってもらえるようになる筈
default_port
- vEOS 側で設定変更していないデフォルトなら
6030
- vEOS 側で設定変更していないデフォルトなら
username
とpassword
- vEOS のアカウント情報
また <git clone してきた dir>/openconfigbeat.yml
にデフォルト値っぽいのが定義されているので そこに定義されている output.elasticsearch: hosts: ["localhost:9200"]
は明示的に指定しておりません。(今回は Kibana も Openconfigbeat とサーバ相乗りしているので)
何も指定せずに <git clone してきた dir>/
で make
すればビルドされて openconfigbeat
というファイルが出来ます。
$ make $ file openconfigbeat openconfigbeat: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=33438dfebfda1d7a42d3a0ad3db140fbf8b47ff8, stripped $ chmod go-w openconfigbeat.yml
Usage
$ ~/go/src/github.com/aristanetworks/openconfigbeat/openconfigbeat -h Usage: openconfigbeat [flags] openconfigbeat [command] Available Commands: export Export current config or index template help Help about any command keystore Manage secrets keystore run Run openconfigbeat setup Setup index template, dashboards and ML jobs test Test config version Show current version info Flags: -E, --E setting=value Configuration overwrite -N, --N Disable actual publishing for testing -c, --c string Configuration file, relative to path.config (default "openconfigbeat.yml") --cpuprofile string Write cpu profile to file -d, --d string Enable certain debug selectors -e, --e Log to stderr and disable syslog/file output -h, --help help for openconfigbeat --httpprof string Start pprof http server --memprofile string Write memory profile to this file --path.config string Configuration path --path.data string Data path --path.home string Home path --path.logs string Logs path --plugin pluginList Load additional plugins --setup Load the sample Kibana dashboards --strict.perms Strict permission checking on config files (default true) -v, --v Log at INFO level Use "openconfigbeat [command] --help" for more information about a command.
以下で設定確認
$ ~/go/src/github.com/aristanetworks/openconfigbeat/openconfigbeat export config openconfigbeat: addresses: - localhost default_port: 6042 paths: - / output: elasticsearch: hosts: - localhost:9200 path: config: /home/kotetsu/go/src/github.com/aristanetworks/openconfigbeat data: /home/kotetsu/go/src/github.com/aristanetworks/openconfigbeat/data home: /home/kotetsu/go/src/github.com/aristanetworks/openconfigbeat logs: /home/kotetsu/go/src/github.com/aristanetworks/openconfigbeat/logs setup: kibana: null
起動
先ほどビルドしたものを実行して、起動します。以下のようにやれば、フォアグラウンド動作・stdout に PUSH されてきた情報など垂れ流されます。
$ ./openconfigbeat -e -d openconfigbeat.go
様子を見る
ここからは Kibana で様子を見ていきます。
設定空っぽの Kibana の WebUI にアクセスすると「はよ Index 作れ」と促されるので、言われるがままに Openconfigbeat が投げ込んでくるやつ用の Index Pattern を作っておきます。
そうすると、Discover
のところでデータがズラズラと出てくるので
ポチポチと様子見用の search とか Visualize とか Dashboard を作りました。今回は「vEOS の特定物理インターフェースを流れる in-octets と in-unicast-pkts (累計)」を。
何で累計かというと、Kibana で Timelion とか使って bps なり pps の算出設定をするのが面倒くさかったからです。(個人的に、その手の設定は Grafana の方がなんぼか楽)
で、平常時は LLDP と BGP KeepAlive くらいしか流れていない当該物理インターフェースに、13s 程度だけトラフィックを流してみると
16:06:02 - 16:06:15
くらいの間だけ、vEOS 側がインターフェースカウンタの上昇に合わせて 1s 間隔程度で push してきてくれている様子が見て取れました。(グラフの丸部分の間隔がその時だけ狭まっている)
おしまい
ダラダラと所感を。
- gNMI と YANG に準拠してくれている(っぽい) OpenConfigbeat ならば、それに準拠している NOS ならば一元的に collector 動作してくれそうな匂いを感じとった
- 何せ vEOS しか試せていないので...
- Elastic の Beats というフレームワークは(自分で実装したわけじゃないけど)、fluentd プラグインと同じように使えそう
- Ruby より Go が良いとか、fluentd のバージョン対応どうしようとか、そういう開発者には良いのかも
- Telemetry でよく言われる「バーストトラフィック検知」は Openconfig ベースなものでも実現できそうな気がする
2018/07/22
時点の感触としては、データモデルが YANG 準拠な分、メーカ独自に自分たちの NOS 向けに作りこんでいる仕組みには取得できる情報観点では及ばなさそう- 一例として、以前試した 「Nexus9000v で Telemetry」 だと Cisco 独自のデータモデルを使っているので、結構細かい情報も取れていた
- 目的がハッキリしているならば、現時点でもそれなりに使えそうな感はある
ThousandEyes で NW 装置 monitoring を Free Try
はじめに
やること
ShowNet 2018 で「ネットワーク可視化 SaaS」として使われていた ThousandEyes を試用します(Free Try で)。
見る機能は「SNMP 対応 NW 装置から情報収集してインターネット経由で SaaS 環境に投げ込む probe 的な Enterprise Agents
を動かして様子を見る(Devices
機能)」部分に絞ります。ザッと公式ページを見た感じ、本機能はオマケっぽいですが。
こんなんが見えるところまで。
構成概要・環境情報
以下の通りです。
今回物理装置や KVM+GNS3
部分の構築には触れません。Enterprise Agents
は ESXi
とかベアメタルサーバとか Docker
とかでも動くので、そこの動かし方はやりやすいようにやれば良かろうかと。
Cisco IOS XE
や Juniper NFX
上の専用コンテナとかもあるみたいですが、今回弄ってないです。
参考資料
- ThousandEyes 公式
- top
Try It Free
というボタンがデカデカとあるので、ありがたく使わせて頂く
- Pricing
- 今回試用するフリー版で出来ること、課金すると出来ること、が並んでいる
Devices
機能観点では、課金することでカスタム MIB を使ってより多くの情報を収集可能になりそう
- Knowledge Base
- マニュアル
- top
- その他紹介記事など
- Interop Tokyo 2018 公式 / ShowNet 今年のみどころ
- 「Best of ShowNet Award 受賞結果」に「サウザンドアイズ:ネットワーク可視化 SaaS」というのが載っている
- Geekなページ / ShowNet 2018モニタリング
- 「ネットワーク品質監視/経路可視化を行うThousandEyes」としてスライドの抜粋が載っている
- Interop Tokyo 2018 公式 / ShowNet 今年のみどころ
動かす
環境準備
ThousandEye Free Try 登録
まずは ThousandEyes 公式 で Try It Free
を押して、ユーザ登録します。2018/06/17
現在、個人ユーザ + Web メールでも弾かれたりせず。
メールが来るので、レジスター用のリンクから本登録しておわり。
公式ページ上の Login
リンクからダッシュボードに飛べます。
ThousandEye Enterprise Agents 取得 ~ GNS3にインポートして起動 ~ 初期設定
probe として動く Enterprise Agents
のイメージは、ダッシュボードで SETTINGS
-> Agents
-> Enterprise Agents
と進めばダウンロードできます。
今回は Virtual Appliance
を選択して OVA
を頂いてきました。
ダウンロードした ova ファイルを展開して、qcow2 に変換して KVM+GNS3
が動いている母艦に放り込みます。
$ ll thousandeyes-va-0.126.ova -rw-r--r-- 1 kotetsu kotetsu 965096448 Jun 16 23:43 thousandeyes-va-0.126.ova $ tar -xvf thousandeyes-va-0.126.ova thousandeyes-va-64-16.04.ovf thousandeyes-va-64-16.04-disk1.vmdk $ ll thousandeyes-va-* -rw-r--r-- 1 kotetsu kotetsu 965096448 Jun 16 23:43 thousandeyes-va-0.126.ova -rw-r--r-- 1 kotetsu kotetsu 965087744 May 10 05:23 thousandeyes-va-64-16.04-disk1.vmdk -rw-r--r-- 1 kotetsu kotetsu 6261 May 10 05:21 thousandeyes-va-64-16.04.ovf $ qemu-img convert -f vmdk -O qcow2 thousandeyes-va-64-16.04-disk1.vmdk thousandeyes-va-64-16.04.qcow2 $ file thousandeyes-va-* thousandeyes-va-0.126.ova: POSIX tar archive thousandeyes-va-64-16.04-disk1.vmdk: VMware4 disk image thousandeyes-va-64-16.04.ovf: XML 1.0 document, ASCII text, with very long lines thousandeyes-va-64-16.04.qcow2: QEMU QCOW Image (v3), 21474836480 bytes
ThousandEyes 公式 KB / How to set up the Virtual Appliance あたりと、ova に入っていた ovf の中身を参考に以下の感じで GNS3
にデプロイ。
以下、生情報。
$ ps -ef | grep [T]housand root 8862 5214 2 13:32 pts/12 00:10:10 /usr/bin/qemu-system-x86_64 -name ThousandEyes-va-64-16.04-1 -m 2048M -smp cpus=1 -enable-kvm -machine smm=off -boot order=c -drive file=/home/kotetsu/GNS3/projects/thousandeyes/project-files/qemu/2c910cdc-3d96-4de7-b8f5-42541188cd17/hda_disk.qcow2,if=ide,index=0,media=disk -uuid 2c910cdc-3d96-4de7-b8f5-42541188cd17 -serial telnet:127.0.0.1:5007,server,nowait -monitor tcp:127.0.0.1:41113,server,nowait -net none -device e1000,mac=0c:00:da:cd:17:00,netdev=gns3-0 -netdev socket,id=gns3-0,udp=127.0.0.1:10031,localaddr=127.0.0.1:10030 -nographic
ネットワークインターフェースは、The Internet に到達できるように適宜 GNS3
の Cloud
とかを使って繋いでおきます。
あとは、起動してコンソールを見ていると Enterprise Agents
の初期 URL (DHCPで得た IP アドレスに http で)と初期アカウント・パスワードが出るので、それに従って WebUI でアクセスします。
ポチポチして、ログインパスワードや Static IP アドレスや DNS サーバや NTP サーバや Default Gateway 情報や ssh ログイン用の公開鍵を設定します。
試していないですが、ssh で入って様子を見ると Ubuntu 16.04 なので、WebUI で出来ない細かい設定も可能かもです。
Enterprise Agents
がインターネット上の SaaS サービスに情報を送ることができていれば、ダッシュボードにこのエージェントが登場します。
SNMP + LLDP が動く NW 機器を GNS3 で動かす
何か適当にどうぞ(クソ雑)。
今回は以下の通り Arista の vEOS-lab
を 3 個並べておきました。
Enterprise Agents
が SNMP manager として情報を取得にくるので、そこの到達性を確保し、SNMP 設定はしておきます。あとは LLDP を動かしておくとトポロジ情報も出してくれるので、動かしておきます。
ダッシュボードで色々やる
Devices の登録
Enterprise Agents
から情報収集する対象(Devices
というらしい)の NW 装置を設定してみます。
まずは Device Credentials
として収集対象の SNMP Community
設定を登録して
Device Settings
にて Find New Devices
で対象の IP アドレスや先の Device Credentials
や、どの Enterprise Agents
を manager として使うか、を入力すると
以下のように登録されます。
情報収集対象の Interface を選択するときには Monitored Interfaces
設定のチェックボックスをポチポチと。
interval
は 5 minutes
から変えられないっぽいですね......。
Views で Device Layer を眺める
これで何となくそれっぽいのが出力されている筈なので、ダラダラと様子を見ていきます。
Topology として LLDP ベースで勝手にそれらしい絵が出来ています(今回は vEOS 側で管理インターフェースである Ma1
の LLDP を無効化してみた)。良いねー。
なお、上の時間軸を弄ることで特定時刻の Topology や Interface Metrics の様子を見ることができます。
Topology はノードを DD で動かすこともできます。
2 本接続して ECMP させているリンクは、こんな風にもなるし
ワンクリックでバラして見ることもできます。
今回、仮想環境だから Et
インターフェースの speed 値を取得できていなかったので、まともに動かせたのは Ma1
だけだったのですが、Highlighting
で「この時間帯に帯域の XX % 以上使っていたリンクを赤くする」ってのを出来ます。まあ、interval は 5 minutes なんですが......。
Interface Metric の様子...は、まあ単に標準 ifMib 見ているだけですね。無課金なので。
通知系設定
Devices
にできる設定としては以下があるようです。
Devices
->Notification Rules
- トリガ
- 対象
Devices
への到達性変化 (SNMP Get の失敗とか) - 対象
Devices
のインターフェースの増減
- 対象
- 通知方法
- メール
- 各種 Webhooks
- Slack や Hipchat など
- トリガ
Alerts
->Alert Rules
- トリガ
- インターフェースの以下が特定の条件・閾値を超えた時
- Throughput
- Error
- Discards
- Admin Status
- Operational Status
- インターフェースの以下が特定の条件・閾値を超えた時
- 通知方法は上のと一緒
- トリガ
標準 MIB ベースで SNMP Manager が拾えるトリガなので、このくらいでしょうねー。
以下設定例です。Topology の Highlighting 機能で赤くするやつだと「speed の X%」っていう設定しか出来ないのですが、こちらでは Mbps での設定も出来るようです。
検知を WebUI で参照した例
で、View の Device Layer でも Alert は時系列でオレンジ色で出してくれています。
おしまい
以下ダラダラtp所感です。
- WebUI は綺麗だしサクサク (この規模だと)
- 1000台単位で
Devices
を長期間動かした時の様子を見てみたい- Topology が一体どうなってしまうのか
- インターフェース数はどこまで拾えるのか
- 中身は rrd 的な動作してるのか・データの持ち方どうなっているのか(SaaS側?)
- 5 minutes interval 固定だとすると、「メンテナンス時や障害時に設計通りにトラフィックが切り替わったかのリアルタイム/事後確認」用途には使いづらいかも (バースト検知とかは言うまでもなく...)
- LLDP の自動描画系のは割と聞くけど、良い
- 静的に描画するものでも描画設定ファイルを自動生成すれば良かろう、だけど面倒くさいことはしたくない
- Network Weathermap とかで手をかけて人間が分かりやすいものをちゃんと作ってやるか、こういうので手抜きしてそれなりにやるか、ってのは情報量とか用途次第かな...
- 適用対象によっては 「SaaS 型である」という一点で一発アウトなやつかも
- 無課金の
Devices
機能は「標準 MIB の SNMP で出来ること」が限度なので、こんなもんでしょうなー- 多分今回見ていない RIPE Atlas みたいな機能がメインなんだと思う
- でもオマケにしてはよく出来ていた
ARISTA vEOS に jq を入れて使う (小ネタ)
小ネタです。twitter で「Arista に jq 欲しい」って話を見かけたので「確かに vEOS デフォルトで入ってなくて、入れりゃー使えるけど(この記事)...最初から入っていて欲しいよなあ」というだけのお話。
環境
いつも通り(3年ぶり)の vEOS ですぞな。
localhost#show version Arista vEOS Hardware version: Serial number: System MAC address: 0021.96aa.9b52 Software image version: 4.20.1F Architecture: i386 Internal build version: 4.20.1F-6820520.4201F Internal build ID: 790a11e8-5aaf-4be7-a11a-e61795d05b91 Uptime: 2 minutes Total memory: 2017260 kB Free memory: 1259404 kB [admin@localhost ~]$ uname -a Linux localhost 3.18.28.Ar-6765725.4201F #1 SMP PREEMPT Wed Nov 15 09:47:13 PST 2017 x86_64 x86_64 x86_64 GNU/Linux
インストール
前述の環境では一応 /etc/yum
配下が揃っていそうなのですが、ササッと binary ファイル取ってきて PATH 通ったところに放り込む感じで。
まず curl でインターネッツからとってこれるように DNS キャッシュサーバと Ma1
の IP アドレスくらいを EOS で設定して。
ip name-server vrf default <DNSキャッシュサーバの IP address> ! interface Management1 ip address <EOS の IPv4 アドレス(DHCPでも)> ! ip route 0.0.0.0/0 <インターネッツに抜ける Gateway IP address>
bash
におりて binary ファイルを取ってくるだけ。
localhost#bash Arista Networks EOS shell [admin@localhost ~]$ [admin@localhost tmp]$ cd /var/tmp [admin@localhost tmp]$ curl -LO https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 [admin@localhost tmp]$ ll total 2964 drwxrwxrwt 4 root root 100 Nov 29 13:30 agents drwxrwxrwx 2 root root 40 Nov 29 13:19 cli -rw-r--r-- 1 admin eosadmin 3027945 Nov 29 13:40 jq-linux64 -rw-rw-rw- 1 root root 2 Nov 29 13:20 startup-config.loaded [admin@localhost tmp]$ sudo cp jq-linux64 /bin/jq [admin@localhost tmp]$ sudo chmod 755 /bin/jq [admin@localhost ~]$ which jq /bin/jq
EOS から使う
後はお好きなように。EOS シェル側に戻れば、ほいこの通り。
localhost#show int | json | jq '.interfaces[] | [.name, .lineProtocolStatus]' [ "Management1", "up" ] [ "Ethernet2", "up" ] [ "Ethernet3", "up" ] [ "Ethernet1", "up" ]
最後に
僕も EOS にデフォルトで入れておいて欲しいです。 (EOS ならばお手軽に eAPI を突いてスクリプト言語の json ライブラリで処理する、のが常道なのかも知れないけど...shell だけでチョチョイとやりたいことも多々あるよね)
Nexus9000v の API を弄る(NX-API REST, NX-API CLI)
最初に
やること
Cisco 公式 / Cisco Nexus 9000 Series NX-OS Programmability Guide, Release 7.x のツリーを眺めると、NX-OS には色々な API が揃っていそうです。なので、様子を見ていきます。
ドキュメントレベルでは、少なくとも以下が揃っていそうで、今回はその一部を取り上げます。
なお、まずは機器側としての様子を見たかったため、ツールに関してはほぼ触れず。(まずは機器側の限界を知っておきたい)
- NX-API
- NETCONF
- ドキュメントを流し読みした感じ、割とちゃんと作られていそう
- OnBox Python
参考資料
試用する
環境情報と準備
NX-OS 側
torsw101a# show version | egrep -i nxos NXOS: version 7.0(3)I6(1) NXOS image file is: bootflash:///nxos.7.0.3.I6.1.bin NXOS compile time: 5/16/2017 22:00:00 [05/17/2017 15:21:28]
以下のように機能を有効化するだけ
torsw101a(config)# feature scp-server torsw101a(config)# feature nxapi torsw101a# show nxapi nxapi enabled HTTP Listen on port 80 HTTPS Listen on port 443
APIクライアント側準備
kotetsu@receiver:~/nxos_rest$ uname -a Linux receiver 4.4.0-97-generic #120-Ubuntu SMP Tue Sep 19 17:28:18 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux kotetsu@receiver:~/nxos_rest$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=16.04 DISTRIB_CODENAME=xenial DISTRIB_DESCRIPTION="Ubuntu 16.04.3 LTS"
$ sudo apt install -y python3-pip $ pip3 install requests
...(無言)
NX-API REST 試用
参照
以下のような VLAN 設定の NX-OS に対して
torsw101a# show run vlan !Command: show running-config vlan !Time: Sun Oct 29 11:19:38 2017 version 7.0(3)I6(1) vlan 1,100,300,3901 vlan 100 vn-segment 10100 vlan 300 vn-segment 10300 vlan 3901 vn-segment 50001
こんな感じのサンプルスクリプトを作って実行すれば
(なお前半の認証部分は Cisco APIC REST API User Guide / Testing the API with Python のサンプルコードをマルパク)
#! /usr/bin/env python3 # -*- coding: utf-8 -*- import requests import json base_url = 'http://10.0.0.230/api/' # create credentials structure name_pwd = {'aaaUser': {'attributes': {'name': 'admin', 'pwd': 'P@ssw0rd'}}} json_credentials = json.dumps(name_pwd) # log in to API login_url = base_url + 'aaaLogin.json' post_response = requests.post(login_url, data=json_credentials) # get token from login response structure auth = json.loads(post_response.text) login_attributes = auth['imdata'][0]['aaaLogin']['attributes'] auth_token = login_attributes['token'] # create cookie array from token cookies = {} cookies['APIC-Cookie'] = auth_token bd_url = base_url + 'node/mo/sys/bd.json?rsp-subtree=children' get_response = requests.get(bd_url, cookies=cookies, verify=False) print(json.dumps(get_response.json(), indent=2))
以下のような出力を得られる
{ "totalCount": "1", "imdata": [ { "bdEntity": { "attributes": { "childAction": "", "modTs": "2017-08-17T02:27:52.937+00:00", "persistentOnReload": "true", "sysDefaultSVIAutostate": "enable", "descr": "", "dn": "sys/bd", "monPolDn": "uni/fabric/monfab-default", "status": "" }, "children": [ { "l2BD": { "attributes": { "modTs": "2017-09-09T23:58:01.301+00:00", "vlanmgrCfgState": "0", "status": "", "fwdMode": "bridge,route", "uid": "0", "epOperSt": "", "pcTag": "1", "type": "bd-regular", "monPolDn": "", "controllerId": "", "childAction": "", "BdOperName": "VLAN0300", "fwdCtrl": "mdst-flood", "id": "300", "mode": "CE", "rn": "bd-[vlan-300]", "media": "enet", "bdDefDn": "", "adminSt": "active", "vlanmgrCfgFailedBmp": "", "hwId": "0", "bridgeMode": "mac", "accEncap": "vxlan-10300", "createTs": "1970-01-01T09:00:00.000+00:00", "vlanmgrCfgFailedTs": "00:00:00:00.000", "name": "", "unkMacUcastAct": "proxy", "unkMcastAct": "flood", "fabEncap": "vlan-300", "ctrl": "", "persistentOnReload": "true", "BdState": "active", "operSt": "up" } } }, { "l2BD": { "attributes": { "modTs": "2017-09-09T23:57:01.911+00:00", "vlanmgrCfgState": "0", "status": "", "fwdMode": "bridge,route", "uid": "0", "epOperSt": "", "pcTag": "1", "type": "bd-regular", "monPolDn": "", "controllerId": "", "childAction": "", "BdOperName": "VLAN0100", "fwdCtrl": "mdst-flood", "id": "100", "mode": "CE", "rn": "bd-[vlan-100]", "media": "enet", "bdDefDn": "", "adminSt": "active", "vlanmgrCfgFailedBmp": "", "hwId": "0", "bridgeMode": "mac", "accEncap": "vxlan-10100", "createTs": "1970-01-01T09:00:00.000+00:00", "vlanmgrCfgFailedTs": "00:00:00:00.000", "name": "", "unkMacUcastAct": "proxy", "unkMcastAct": "flood", "fabEncap": "vlan-100", "ctrl": "", "persistentOnReload": "true", "BdState": "active", "operSt": "up" } } }, { "l2BD": { "attributes": { "modTs": "2017-09-10T13:32:39.908+00:00", "vlanmgrCfgState": "0", "status": "", "fwdMode": "bridge,route", "uid": "0", "epOperSt": "", "pcTag": "1", "type": "bd-regular", "monPolDn": "", "controllerId": "", "childAction": "", "BdOperName": "VLAN3901", "fwdCtrl": "mdst-flood", "id": "3901", "mode": "CE", "rn": "bd-[vlan-3901]", "media": "enet", "bdDefDn": "", "adminSt": "active", "vlanmgrCfgFailedBmp": "", "hwId": "0", "bridgeMode": "mac", "accEncap": "vxlan-50001", "createTs": "1970-01-01T09:00:00.000+00:00", "vlanmgrCfgFailedTs": "00:00:00:00.000", "name": "", "unkMacUcastAct": "proxy", "unkMcastAct": "flood", "fabEncap": "vlan-3901", "ctrl": "", "persistentOnReload": "true", "BdState": "active", "operSt": "0" } } } ] } } ] }
設定
同様にこんな感じのサンプルスクリプトで
#! /usr/bin/env python3 # -*- coding: utf-8 -*- import requests import json base_url = 'http://10.0.0.230/api/' # create credentials structure name_pwd = {'aaaUser': {'attributes': {'name': 'admin', 'pwd': 'P@ssw0rd'}}} json_credentials = json.dumps(name_pwd) # log in to API login_url = base_url + 'aaaLogin.json' post_response = requests.post(login_url, data=json_credentials) # get token from login response structure auth = json.loads(post_response.text) login_attributes = auth['imdata'][0]['aaaLogin']['attributes'] auth_token = login_attributes['token'] # create cookie array from token cookies = {} cookies['APIC-Cookie'] = auth_token bd_url = base_url + 'node/mo/sys/bd.json' post_payload = { "bdEntity": { "children": [ { "l2BD": { "attributes": { "accEncap": "vxlan-10190", "fabEncap": "vlan-190", "pcTag": "1" }}}]}} post_response = requests.post(bd_url, data=json.dumps(post_payload), cookies=cookies, verify=False) print(json.dumps(post_response.json(), indent=2))
{ "imdata": [] }
設定が追加されている。しかし startup-config
に反映されているわけではないので、要注意。
torsw101a# show run vlan !Command: show running-config vlan !Time: Sun Oct 29 11:27:28 2017 version 7.0(3)I6(1) vlan 1,100,190,300,3901 vlan 100 vn-segment 10100 vlan 190 vn-segment 10190 vlan 300 vn-segment 10300 vlan 3901 vn-segment 50001
さっきの情報取得をもう一回やると、設定したものが増えているのが分かります。
また URL 末尾に options として query-target-filter
で条件指定も出来ます。この辺はまあ netconf で xml 扱うのと同じようなノリで。
>>> bd_url = base_url + 'node/mo/sys/bd.json?query-target==children&query-target-filter=eq(l2BD.id,"190")' >>> >>> get_response = requests.get(bd_url, cookies=cookies, verify=False) >>> print(json.dumps(get_response.json(), indent=2))
{ "imdata": [ { "l2BD": { "attributes": { "BdOperName": "VLAN0190", "monPolDn": "", "hwId": "0", "status": "", "createTs": "1970-01-01T09:00:00.000+00:00", "unkMacUcastAct": "proxy", "accEncap": "vxlan-10190", "adminSt": "active", "pcTag": "1", "uid": "62982", "epOperSt": "", "controllerId": "", "vlanmgrCfgState": "0", "fwdMode": "bridge,route", "vlanmgrCfgFailedBmp": "", "fwdCtrl": "mdst-flood", "type": "bd-regular", "dn": "sys/bd/bd-[vlan-190]", "BdState": "active", "bridgeMode": "mac", "bdDefDn": "", "name": "", "id": "190", "persistentOnReload": "true", "childAction": "", "mode": "CE", "modTs": "2017-10-29T11:27:05.932+00:00", "ctrl": "", "operSt": "down", "fabEncap": "vlan-190", "unkMcastAct": "flood", "media": "enet", "vlanmgrCfgFailedTs": "00:00:00:00.000" } } } ], "totalCount": "1" }
NX-API CLI
参照
JSON-RPC message format
JSON RPC
には cli
と cli_ascii
というコマンドタイプがあります。
レスポンスを JSON フォーマットで得られないような (bash コマンドとか)やつは、cli_ascii
の方を使わないとエラーになります。
#! /usr/bin/env python3 # -*- coding: utf-8 -*- import requests import json payload = [ { "jsonrpc": "2.0", "method": "cli", "params": { "cmd": "show nve vni 50001 detail", "version": 1 }, "id": 1 } ] response = requests.post('http://10.0.0.230/ins', data=json.dumps(payload), headers={'content-type': 'application/json-rpc'}, auth=('admin', 'P@ssw0rd'), verify=False) print(json.dumps(response.json(), indent=2))
{ "id": 1, "jsonrpc": "2.0", "result": { "body": { "TABLE_nve_vni": { "ROW_nve_vni": { "svi-state": "UP [vrf-id: 3]", "vlan-bd": "3901", "if-name": "nve1", "prvsn-state": "add-complete", "mcast": "n/a", "vni-state": "Up", "cp-submode": "bgp", "flags": "", "type": "L3 [VRF001]", "mode": "control-plane", "vni": "50001" } } } } }
こんな感じで bash コマンドも打てます。
#! /usr/bin/env python3 # -*- coding: utf-8 -*- import requests import json payload = [ { "jsonrpc": "2.0", "method": "cli_ascii", "params": { "cmd": "run bash df -h", "version": 1 }, "id": 1 } ] response = requests.post('http://10.0.0.230/ins', data=json.dumps(payload), headers={'content-type': 'application/json-rpc'}, auth=('admin', 'P@ssw0rd'), verify=False) print(json.dumps(response.json(), indent=2))
{ "result": { "msg": "Filesystem Size Used Avail Use% Mounted on\n/dev/root 8.9G 761M 8.1G 9% /\nnone 10M 1.4M 8.7M 14% /nxos/tmp\nnone 10M 2.9M 7.2M 29% /nxos/xlog\nnone 80M 9.9M 71M 13% /nxos/dme_logs\nnone 50M 3.0M 48M 6% /var/volatile/log\nnone 2.0M 12K 2.0M 1% /var/home\nnone 120M 364K 120M 1% /var/volatile/tmp\nnone 900M 256K 900M 1% /var/sysmgr\nnone 500M 60K 500M 1% /var/sysmgr/ftp\nnone 20M 0 20M 0% /var/sysmgr/srv_logs\nnone 2.0M 0 2.0M 0% /var/sysmgr/ftp/debug_logs\nnone 1.0G 349M 676M 35% /dev/shm\nnone 600M 61M 540M 11% /volatile\nnone 2.0M 16K 2.0M 1% /debug\nnone 1.0G 736K 1.0G 1% /mnt/ifc/cfg/db\n/dev/loop1 57M 57M 0 100% /isan_lib_ro\n/dev/loop2 65M 65M 0 100% /isan_bin_ro\n/dev/loop3 28M 28M 0 100% /isan_bin_eth_ro\n/dev/loop4 14M 14M 0 100% /isan_lib_eth_ro\n/dev/loop5 768K 768K 0 100% /isan_lib_n9k_ro\n/dev/loop6 128K 128K 0 100% /isan_bin_n9k_ro\nunionfs 8.9G 761M 8.1G 9% /isan/bin\nunionfs 8.9G 761M 8.1G 9% /isan/lib\n/dev/sda4 3.3G 1.1G 2.3G 33% /bootflash\n/dev/sda5 643M 18M 592M 3% /mnt/cfg/0\n/dev/sda6 643M 18M 592M 3% /mnt/cfg/1\n/dev/sda2 317M 12M 289M 4% /mnt/plog\nnone 400M 6.1M 394M 2% /var/sysmgr/startup-cfg\n/dev/loop7 49M 49M 0 100% /lc_ro\n/dev/loop8 41M 41M 0 100% /lc_n9k_ro\nunionfs 8.9G 761M 8.1G 9% /lc\n/dev/sda3 643M 19M 592M 4% /mnt/pss\n/dev/sda7 2.4G 80M 2.2G 4% /logflash\n/dev/loop9 57M 1.2M 53M 3% /bootflash/.rpmstore/patching\n" }, "id": 1, "jsonrpc": "2.0" }
bash ではデフォルト VRF での動作になるが Cisco Nexus 9000 Series NX-OS Programmability Guide, Release 7.x / Guest Shell 2.3 にあるように chvrf management
とかをいれて別 VRF から IP 通信系のコマンドも実行できます。
#! /usr/bin/env python3 # -*- coding: utf-8 -*- import requests import json payload = [ { "jsonrpc": "2.0", "method": "cli_ascii", "params": { "cmd": "run guestshell chvrf management ping 10.0.0.231 -c 3", "version": 1 }, "id": 1 } ] response = requests.post('http://10.0.0.230/ins', data=json.dumps(payload), headers={'content-type': 'application/json-rpc'}, auth=('admin', 'P@ssw0rd'), verify=False) print(json.dumps(response.json(), indent=2))
{ "result": { "msg": "PING 10.0.0.231 (10.0.0.231) 56(84) bytes of data.\n64 bytes from 10.0.0.231: icmp_seq=1 ttl=255 time=0.663 ms\n64 bytes from 10.0.0.231: icmp_seq=2 ttl=255 time=0.733 ms\n64 bytes from 10.0.0.231: icmp_seq=3 ttl=255 time=0.568 ms\n\n--- 10.0.0.231 ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2001ms\nrtt min/avg/max/mdev = 0.568/0.654/0.733/0.073 ms\n" }, "jsonrpc": "2.0", "id": 1 }
JSON message format
JSON
には以下のような type
があります(名前で察せられる通り)
cli_show
cli_show_ascii
cli_conf
bash
JSON
の場合は payload の JSON key 順序によっては Wrong request message version, expecting 1.0 Request.is.rejected
と Status Code 400 を返してくる...。
仕方がないので Python の dict 型は避けて、単純に文字列で渡す。
(正直、この辺で「JSON-XML に出来なくて JSON で出来ること、がなければ...もうこいつとは付き合わなくていいかなー」と思い始めた)
こんな感じで、JSON-XML
の時と同じ出力を得られます。
#! /usr/bin/env python3 # -*- coding: utf-8 -*- import requests import json payload = """{ "ins_api": { "version": "1.0", "type": "cli_show", "chunk": "0", "sid": "1", "input": "show version", "output_format": "json" } }""" response = requests.post('http://10.0.0.230/ins', data=payload, headers={'content-type':'application/json'}, auth=('admin', 'P@ssw0rd')) print(json.dumps(response.json(), indent=2))
#! /usr/bin/env python3 # -*- coding: utf-8 -*- import requests import json payload = """{ "ins_api": { "version": "1.0", "type": "bash", "chunk": "0", "sid": "1", "input": "df -h", "output_format": "json" } }""" response = requests.post('http://10.0.0.230/ins', data=payload, headers={'content-type':'application/json'}, auth=('admin', 'P@ssw0rd')) print(json.dumps(response.json(), indent=2))
binding library を探せ
NX-API REST
見つかりませんでした(完)。
NX-API CLI
以下 2017/10/30
現在の様子ですが。
- github / datacenter/nexus9000
- github / jedelman8/pycsco
- 以前は
napalm
でも下回りで使われていたっぽい (napalm
側のドキュメントを見た感じ) 2017/10/30
現在、最後の commit は2016/09/20
とか...数件溜まっている Issue や PR も進捗ない
- 以前は
- github / networktocode/pynxos
naparm
の下回りとして使われている- これ単体ではドキュメントなどもなさげ
- Ansible 公式 / Network Modules / Nxos
- 今のところこれが一番機能は揃っていそう
ちなみに napalm
で NX-OS を数分弄った感触は以下の通りです。
2年半ぶりにNAPALMを数分弄って(対象は Nexus9000v)、なんか標準 getter 少ないし下回りのライブラリを自分で呼ぶか、RestClient 使って機器の REST API 直接叩いた方が何ぼかマシでは...という感触を得た
— こてつ (@kakkotetsu) 2017年10月25日
単体の Network OS を相手にしたら、そりゃそういう感触になるのは当然か
— こてつ (@kakkotetsu) 2017年10月25日
getter が足りていないなら...って、こーゆーのはもうえーっちゅーねん pic.twitter.com/TdOBJAfxXt
— こてつ (@kakkotetsu) 2017年10月25日
おしまい
感想を好きに述べます。
- NX-API REST
- DN が最新バージョンで変わっていたりして、まだまだ開発中ではありそう
- それゆえにかライブラリはまだ無さげ (欲しい)
- データモデル的は Telemetry と共通の DME で、そのドキュメントは「よー分からんところもあるから試行錯誤」が必要なところもあった
- 参照・設定に関しては良くて、その他のオペレーション(ファイル操作・再起動 etc)に関しては不足している感
-
- とりあえずこれを使えば何でも出来そう
- 「この処理は API で出来ないから expect で...」というケースは避けられる
- CLI で NW 装置を扱っているネットワーク屋にも扱いやすい気がする
- ARISTA eAPI と使用感がほぼ同じなので...感想も一緒
NX-OS の NX-API CLI と EOS の eAPI を見ていると、君たち仲良くして一緒に標準化すれば?と言いたくなる
— こてつ (@kakkotetsu) 2017年9月13日
- とりあえずこれを使えば何でも出来そう
ライブラリ
- Junos でいうところの
PyEZ
みたいに特定 NOS に特化した良い感じに機能が揃ったやつはなさげ - 例えば ARISTA EOS の
pyeapi
なんかは、(インストール手順を見ると)リモート制御用サーバと EOS 側とどちらで動かせるようになっているので- 現状 On-box library として NX-OS 側に入っているライブラリを、リモート制御用サーバに入れて使うような展開もありうるかも??
- パブリックな情報を眺めていると、公式的には ACI 方面の開発優先に見える
- でもまあ HTTP Client としての最低限処理だけなら、ライブラリがなくとも...
- Junos でいうところの
まあ色々と言いましたが、選択肢が多くて驚きですね。
最後に、もう ncclient
とかでシコシコと NETCONF を弄る気にはならなかったんで、そこもライブラリ欲しいな...。
Nexus9000v で VxLAN+EVPN (MAC Mobility Extended Community 簡易動作確認編)
最初に
やること
先日の記事(Nexus9000v で VxLAN+EVPN (anycast gateway 編)) で、EVPN で学習した MAC アドレステーブルを見た僕が「Seq No があるってことは MAC Mobility Extended Community が使えるんじゃないのか!?」と口走っていたので、その簡易動作確認をします。
EVPN MAC Mobility ?
RFC 7432 (BGP MPLS-Based Ethernet VPN) / 15. MAC Mobility に書かれています。
EVPN PE を跨ぐような LiveMigration や Flapping が発生した際に、網内の PE 達が当該 MAC アドレスの最新の居場所(どの PE 配下にあるか)を正しく把握するために Sequence No を埋め込んで使う Extended Community ... それが MAC Mobility Extended Community です。 今回は確認できませんでしたが、RFCによれば同じ Sequence No が複数 PE から来た場合には PE の IP アドレスが小さい方を選択...てなルールもあるようです。
なーんて、僕の拙い日本語よりも、"EVPN MAC Mobility" で Google 画像検索でもすれば、Juniper さんあたりの分かりやすい図面が出てきますよ(雑)。
環境情報 / 事前準備
先日の記事(Nexus9000v で VxLAN+EVPN (anycast gateway 編)) のまま、完全シングルホーム環境。
なお、後述のシナリオのために torsw101a
と torsw201a
の Et 1/3
に以下設定をした上で、
torsw101a# show run int et 1/3 !Command: show running-config interface Ethernet1/3 !Time: Sun Oct 22 17:31:42 2017 version 7.0(3)I6(1) interface Ethernet1/3 description DEV=ostinato IF=Port0 switchport access vlan 100
torsw201a# show run int et 1/3 !Command: show running-config interface Ethernet1/3 !Time: Sun Oct 22 17:31:49 2017 version 7.0(3)I6(1) interface Ethernet1/3 description DEV=ostinato IF=Port1 switchport access vlan 100
トラフィックジェネレータとして Ostinato を接続してあります。
一応軽く GNS3 で手っ取り早く準備できるトラフィックジェネレータである Ostinato の参考資料も以下に載せておきます。
- OSTINATO
- OSTINATO 公式 / HOME
- OSTINATO 公式 / Download / vDrone Appliance
- GNS3 用のアプライアンス
Community member Bernhard has created a custom vDrone appliance for use with GNS3 based on TinyCore Linux - see Ostinato for GNS3 for download and detailed usage instructions.
- GNS3 用のアプライアンス
- Bernhard's Homepage / Ostinato for GNS3
- 前述の通りなので、ありがたく使わせて頂こう
- OSTINATO 公式 / Python API Guide
- GNS3
簡易動作確認
シナリオ
こんな風に様子を見ます。
- EVPN PE である
torsw101a
とtorsw201a
それぞれのEt 1/3
(VNI 10100 にマッピングされる VLAN を割当済)に、同一 Src MAC address(11:11:11:11:11:11
) の ARP トラフィックを流し続ける - 以下を見る
torsw101a
とtorsw201a
間に流れるパケット(主にEVPN 周りの UPDATE)torsw101a
とtorsw201a
での EVPN MAC アドレス学習情報 遷移
結果
MAC Mobility Extended Community シーケンス
拾ったパケットを追っていくと、以下の感じ (thanks WebSequenceDiagrams)
今回 PE x2 のみで Ostinato でチンタラトラフィックを流している環境では、Sequence No 2 以上が使われることはなかったです。
各 loop の最後に MAC Mobility Extended Community なしの(= Sequence No 0 扱い) UPDATE が出るのは RFC で以下の記載があるからかも知れません。
In order to process mobility events correctly, an implementation MUST handle scenarios in which sequence number wraparound occurs.
上記の loop を 4 回繰り返した後、30s 程度の間は一切の UPDATE を双方が出さなくなりました。
これはどうやら「180s の間に 5 回 move が発生したら 30s の hold timer を発動する(カスタマイズ可能)」というデフォルト値動作によるものみたいです。(以下の本いわく)
Building Data Centers with VXLAN BGP EVPN: A Cisco NX-OS Perspective (Networking Technology)
- 作者: David Jansen,Lukas Krattiger,Shyam Kapadia
- 出版社/メーカー: Cisco Press
- 発売日: 2017/04/04
- メディア: Kindle版
- この商品を含むブログを見る
こんな syslog も出ていた
2017 Oct 22 11:40:17 torsw101a %USER-2-SYSTEM_MSG: Detected duplicate host 1111.1111.1111, topology 100, during Local update, with host located at remote VTEP 198.18.1.21, VNI 10100 - l2rib
次に MAC Mobility Extended Community で Sequence No = 1 で UPDATE 吐いているパケットをピックアップすると以下の感じ
RFC 7432 (BGP MPLS-Based Ethernet VPN) / 7.7. MAC Mobility Extended Community にある format を見ると、Sticky/static
を示す Flags
が 0 になっています。この Flags
の用途は RFC 7432 (BGP MPLS-Based Ethernet VPN) / 15.2. Sticky MAC Addresses の通り、MAC address の移動が起こりえない環境で 1 をたててアラートをあげるような使い方を想定している模様。
NX-OS の設定で変更できるのかは調査した限りでは分からずです。余談ですが、例えば Juniper の実装だと Juniper 公式 / EVPN MAC Pinning Overview のように、この Flags を有効化する設定で MAC アドレス遷移事故を防ぐようなことも出来る(当然、ライブマイグレーションを使わないなどの制約と引き換えに)ようです。
MAC アドレス学習状況
先のシーケンス図と比較しながら見ていきましょう。(余談ですがこれ、拾うタイミングが結構シビアでした。)
torsw101a (VTEP 用の lo IP アドレス = 198.18.1.11)
配下に当該 MAC address があると思っている torsw201a
だったが
torsw201a# show l2route evpn mac all Flags -(Rmac):Router MAC (Stt):Static (L):Local (R):Remote (V):vPC link (Dup):Duplicate (Spl):Split (Rcv):Recv (AD):Auto-Delete(D):Del Pending (S):Stale (C):Clear (Ps):Peer Sync (O):Re-Originated Topology Mac Address Prod Flags Seq No Next-Hops ----------- -------------- ------ ------------- ---------- ---------------- 100 1111.1111.1111 BGP Rcv 0 198.18.1.11
自分配下から同 MAC address を学習し、それを Sequence No = 1
として扱い
torsw201a# show l2route evpn mac all Flags -(Rmac):Router MAC (Stt):Static (L):Local (R):Remote (V):vPC link (Dup):Duplicate (Spl):Split (Rcv):Recv (AD):Auto-Delete(D):Del Pending (S):Stale (C):Clear (Ps):Peer Sync (O):Re-Originated Topology Mac Address Prod Flags Seq No Next-Hops ----------- -------------- ------ ------------- ---------- ---------------- 100 1111.1111.1111 Local L, 1 Eth1/3 100 1111.1111.1111 BGP D 0 198.18.1.11
多分 torsw101a
が WithDrawn を送ってくれたタイミングで、Next-Hops 198.18.1.11
の経路を削除しつつ、自分が持っている経路の Sequence No
を 0 にリセットして再度 UPDATE を送っている。
torsw201a# show l2route evpn mac all Flags -(Rmac):Router MAC (Stt):Static (L):Local (R):Remote (V):vPC link (Dup):Duplicate (Spl):Split (Rcv):Recv (AD):Auto-Delete(D):Del Pending (S):Stale (C):Clear (Ps):Peer Sync (O):Re-Originated Topology Mac Address Prod Flags Seq No Next-Hops ----------- -------------- ------ ------------- ---------- ---------------- 100 1111.1111.1111 Local L, 0 Eth1/3 torsw201a#
なお、タイミング次第では Dup
フラグを観測できたこともありました。
torsw101a# show l2route evpn mac all Flags -(Rmac):Router MAC (Stt):Static (L):Local (R):Remote (V):vPC link (Dup):Duplicate (Spl):Split (Rcv):Recv (AD):Auto-Delete(D):Del Pending (S):Stale (C):Clear (Ps):Peer Sync (O):Re-Originated Topology Mac Address Prod Flags Seq No Next-Hops ----------- -------------- ------ ------------- ---------- ---------------- 100 1111.1111.1111 Local L,Dup, 1 Eth1/3 100 1111.1111.1111 BGP Dup,Rcv 0 198.18.1.21
完
「Seq No があるってことは MAC Mobility Extended Community が使えるんじゃないのか!?」という自分自身の問いに対して「何となく動いていそうな雰囲気はあるぞ」と答えておきます。
それはそれとして、この Sequence No がリセットする間もなくブリバリカウントアップしていくような環境を今回は作れずでしたが、まぁそんな恐ろしい環境には関わりたくないお気持ちでございます。