kakkotetsu

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 にデータを叩き込む
    • gRPC server 側
      • Arista vEOS
        • OpenConfig gNMI ベースで動かすのが手っ取り早そう & openconfigbeat のテストにはきっと EOS を使っているに違いない(Inteoperability でハマりにくそう)と推測したから
    • データ保持・視覚
      • Elasticsearch + Kibana
        • Beats ベースな openconfigbeat と組み合わせて使うのに、一番手っ取り早かったから

参考資料

環境情報

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
    • usernamepassword
      • 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 を作っておきます。

f:id:kakkotetsu:20180722220910p:plain

そうすると、Discover のところでデータがズラズラと出てくるので

f:id:kakkotetsu:20180722220853p:plain

ポチポチと様子見用の search とか Visualize とか Dashboard を作りました。今回は「vEOS の特定物理インターフェースを流れる in-octets と in-unicast-pkts (累計)」を。
何で累計かというと、Kibana で Timelion とか使って bps なり pps の算出設定をするのが面倒くさかったからです。(個人的に、その手の設定は Grafana の方がなんぼか楽)

で、平常時は LLDP と BGP KeepAlive くらいしか流れていない当該物理インターフェースに、13s 程度だけトラフィックを流してみると

f:id:kakkotetsu:20180722220928p:plain

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 独自のデータモデルを使っているので、結構細かい情報も取れていた
  • 目的がハッキリしているならば、現時点でもそれなりに使えそうな感はある
    • 例えば「SNMP Get や API での監視で 1-5 min 間隔なインターフェースカウンタや細かいデータ(NOS や HW に応じた監視項目や設定に応じた table 情報)を取得し、xflow で通信内容の傾向をつかめるようにはなっている」「が、バーストトラフィックの検知ができていないからそこを何とかしたい」というような場合、こういうのがピッタリと嵌りそう
    • 逆に、何もかもこの仕組みで賄おうとするのは(当面は)夢見すぎ感ある

ThousandEyes で NW 装置 monitoring を Free Try

はじめに

やること

ShowNet 2018 で「ネットワーク可視化 SaaS」として使われていた ThousandEyes を試用します(Free Try で)。
見る機能は「SNMP 対応 NW 装置から情報収集してインターネット経由で SaaS 環境に投げ込む probe 的な Enterprise Agents を動かして様子を見る(Devices 機能)」部分に絞ります。ザッと公式ページを見た感じ、本機能はオマケっぽいですが。

こんなんが見えるところまで。

f:id:kakkotetsu:20180617232453p:plain

構成概要・環境情報

以下の通りです。

  • 宅内
    • KVM+GNS3 上で以下を動かす
      • ThousandEyes の Enterprise Agents
        • 物理 NW では NAPT で The Internet に到達可能になっている
        • こいつ自体では The Internet に到達する為のネットワーク設定をするくらいで、ほとんど弄ることはない
        • こいつが SNMP manager になるので、以下の収集対象 NW 装置と疎通可能にしておくこと
      • 収集対象 NW 装置
        • 今回は Arista の vEOS-lab を使っているが、SNMP と LLDP が動けば何でも良い筈
  • SaaS 環境のダッシュボード
    • 設定や参照は全てここでやる
    • The Internet

今回物理装置や KVM+GNS3 部分の構築には触れません。Enterprise AgentsESXi とかベアメタルサーバとか Docker とかでも動くので、そこの動かし方はやりやすいようにやれば良かろうかと。
Cisco IOS XEJuniper NFX 上の専用コンテナとかもあるみたいですが、今回弄ってないです。

参考資料

  • ThousandEyes 公式
    • top
      • Try It Free というボタンがデカデカとあるので、ありがたく使わせて頂く
    • Pricing
      • 今回試用するフリー版で出来ること、課金すると出来ること、が並んでいる
      • Devices 機能観点では、課金することでカスタム MIB を使ってより多くの情報を収集可能になりそう
    • Knowledge Base
      • マニュアル
  • その他紹介記事など

動かす

環境準備

ThousandEye Free Try 登録

まずは ThousandEyes 公式Try It Free を押して、ユーザ登録します。2018/06/17 現在、個人ユーザ + Web メールでも弾かれたりせず。

f:id:kakkotetsu:20180617232412p:plain

メールが来るので、レジスター用のリンクから本登録しておわり。
公式ページ上の Login リンクからダッシュボードに飛べます。

ThousandEye Enterprise Agents 取得 ~ GNS3にインポートして起動 ~ 初期設定

probe として動く Enterprise Agents のイメージは、ダッシュボードで SETTINGS -> Agents -> Enterprise Agents と進めばダウンロードできます。
今回は Virtual Appliance を選択して OVA を頂いてきました。

f:id:kakkotetsu:20180617232607p:plain

ダウンロードした 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 にデプロイ。

f:id:kakkotetsu:20180617232625p:plain

f:id:kakkotetsu:20180617232634p:plain

f:id:kakkotetsu:20180617232642p:plain

以下、生情報。

$ 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 に到達できるように適宜 GNS3Cloud とかを使って繋いでおきます。
あとは、起動してコンソールを見ていると Enterprise Agents の初期 URL (DHCPで得た IP アドレスに http で)と初期アカウント・パスワードが出るので、それに従って WebUI でアクセスします。

f:id:kakkotetsu:20180617232716p:plain

ポチポチして、ログインパスワードや Static IP アドレスや DNS サーバや NTP サーバや Default Gateway 情報や ssh ログイン用の公開鍵を設定します。
試していないですが、ssh で入って様子を見ると Ubuntu 16.04 なので、WebUI で出来ない細かい設定も可能かもです。

f:id:kakkotetsu:20180617232741p:plain

Enterprise Agents がインターネット上の SaaS サービスに情報を送ることができていれば、ダッシュボードにこのエージェントが登場します。

f:id:kakkotetsu:20180617232835p:plain

f:id:kakkotetsu:20180617232849p:plain

SNMP + LLDP が動く NW 機器を GNS3 で動かす

何か適当にどうぞ(クソ雑)。
今回は以下の通り Arista の vEOS-lab を 3 個並べておきました。

Enterprise AgentsSNMP manager として情報を取得にくるので、そこの到達性を確保し、SNMP 設定はしておきます。あとは LLDP を動かしておくとトポロジ情報も出してくれるので、動かしておきます。

f:id:kakkotetsu:20180617232918p:plain

ダッシュボードで色々やる

Devices の登録

Enterprise Agents から情報収集する対象(Devices というらしい)の NW 装置を設定してみます。

まずは Device Credentials として収集対象の SNMP Community 設定を登録して

f:id:kakkotetsu:20180617233027p:plain

Device Settings にて Find New Devices で対象の IP アドレスや先の Device Credentials や、どの Enterprise Agents を manager として使うか、を入力すると

f:id:kakkotetsu:20180617233055p:plain

以下のように登録されます。

f:id:kakkotetsu:20180617233120p:plain

情報収集対象の Interface を選択するときには Monitored Interfaces 設定のチェックボックスをポチポチと。
interval5 minutes から変えられないっぽいですね......。

Views で Device Layer を眺める

これで何となくそれっぽいのが出力されている筈なので、ダラダラと様子を見ていきます。

Topology として LLDP ベースで勝手にそれらしい絵が出来ています(今回は vEOS 側で管理インターフェースである Ma1 の LLDP を無効化してみた)。良いねー。
なお、上の時間軸を弄ることで特定時刻の Topology や Interface Metrics の様子を見ることができます。

f:id:kakkotetsu:20180617233143p:plain

Topology はノードを DD で動かすこともできます。

2 本接続して ECMP させているリンクは、こんな風にもなるし

f:id:kakkotetsu:20180617233159p:plain

ワンクリックでバラして見ることもできます。

f:id:kakkotetsu:20180617233229p:plain

今回、仮想環境だから Et インターフェースの speed 値を取得できていなかったので、まともに動かせたのは Ma1 だけだったのですが、Highlighting で「この時間帯に帯域の XX % 以上使っていたリンクを赤くする」ってのを出来ます。まあ、interval は 5 minutes なんですが......。

Interface Metric の様子...は、まあ単に標準 ifMib 見ているだけですね。無課金なので。

f:id:kakkotetsu:20180617233245p:plain

f:id:kakkotetsu:20180617233258p:plain

通知系設定

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 での設定も出来るようです。

f:id:kakkotetsu:20180617233316p:plain

検知を WebUI で参照した例

f:id:kakkotetsu:20180617233432p:plain

で、View の Device Layer でも Alert は時系列でオレンジ色で出してくれています。

f:id:kakkotetsu:20180617233443p:plain

おしまい

以下ダラダラ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 上で Python スクリプトを動かせるやつ
    • NX-OS 内には謹製ライブラリが仕込まれている(多分上記の NX-API CLI を内部的には使っている感じ)
    • なお動作環境は Python 2.7

参考資料

試用する

環境情報と準備

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 には clicli_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 現在の様子ですが。

ちなみに napalm で NX-OS を数分弄った感触は以下の通りです。

おしまい

感想を好きに述べます。

  • NX-API REST
    • DN が最新バージョンで変わっていたりして、まだまだ開発中ではありそう
    • それゆえにかライブラリはまだ無さげ (欲しい)
    • データモデル的は Telemetry と共通の DME で、そのドキュメントは「よー分からんところもあるから試行錯誤」が必要なところもあった
    • 参照・設定に関しては良くて、その他のオペレーション(ファイル操作・再起動 etc)に関しては不足している感
  • NX-API CLI

    • とりあえずこれを使えば何でも出来そう
      • 「この処理は API で出来ないから expect で...」というケースは避けられる
    • CLI で NW 装置を扱っているネットワーク屋にも扱いやすい気がする
    • ARISTA eAPI と使用感がほぼ同じなので...感想も一緒
  • ライブラリ

    • Junos でいうところの PyEZ みたいに特定 NOS に特化した良い感じに機能が揃ったやつはなさげ
    • 例えば ARISTA EOS の pyeapi なんかは、(インストール手順を見ると)リモート制御用サーバと EOS 側とどちらで動かせるようになっているので
      • 現状 On-box library として NX-OS 側に入っているライブラリを、リモート制御用サーバに入れて使うような展開もありうるかも??
    • パブリックな情報を眺めていると、公式的には ACI 方面の開発優先に見える
    • でもまあ HTTP Client としての最低限処理だけなら、ライブラリがなくとも...

まあ色々と言いましたが、選択肢が多くて驚きですね。
最後に、もう 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 編)) のまま、完全シングルホーム環境。

なお、後述のシナリオのために torsw101atorsw201aEt 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 の参考資料も以下に載せておきます。

f:id:kakkotetsu:20171022220554p:plain

簡易動作確認

シナリオ

こんな風に様子を見ます。

  1. EVPN PE である torsw101atorsw201a それぞれの Et 1/3 (VNI 10100 にマッピングされる VLAN を割当済)に、同一 Src MAC address(11:11:11:11:11:11) の ARP トラフィックを流し続ける
  2. 以下を見る
    • torsw101atorsw201a 間に流れるパケット(主にEVPN 周りの UPDATE)
    • torsw101atorsw201a での EVPN MAC アドレス学習情報 遷移

結果

MAC Mobility Extended Community シーケンス

拾ったパケットを追っていくと、以下の感じ (thanks WebSequenceDiagrams)

f:id:kakkotetsu:20171022220617p:plain

今回 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 を双方が出さなくなりました。

f:id:kakkotetsu:20171023000406p:plain

これはどうやら「180s の間に 5 回 move が発生したら 30s の hold timer を発動する(カスタマイズ可能)」というデフォルト値動作によるものみたいです。(以下の本いわく)

Building Data Centers with VXLAN BGP EVPN: A Cisco NX-OS Perspective (Networking Technology)

Building Data Centers with VXLAN BGP EVPN: A Cisco NX-OS Perspective (Networking Technology)

こんな 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 吐いているパケットをピックアップすると以下の感じ

f:id:kakkotetsu:20171022220736p:plain

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 がリセットする間もなくブリバリカウントアップしていくような環境を今回は作れずでしたが、まぁそんな恐ろしい環境には関わりたくないお気持ちでございます。

Nexus9000v で Telemetry

最初に

やること/サマリ

  • NX-OS の Telemetry 機能を軽く様子見
    • 細かいカスタマイズをシコシコやっていると、まるで盆栽のように終わりがなかったので、ほんの触り
    • 送信側は以下のように動かす
      • 送信プロトコル gRPC
        • 選択肢としては HTTP や TCPUDP も可能らしい
      • エンコード方式 GPB(Google protocol buffer)
        • 選択肢としては JSON も可能らしい
      • データコレクタタイプ DME(Data Management Engine)
        • メーカがプレ定義したスキーマに従う (netconf schema やら SNMP MIB やら...を思い浮かべて頂ければと)
        • show コマンドの結果を NX-OS API を使って出すこともできるらしい
    • 受信側は以下のように動かす
      • gRPC サーバ、GPB デコード、Elasticsearch への転送をしてくれる Cisco の Receiver を使う
      • Elasticsearch にデータを溜め込み、Kibana で可視化(クエリの定義などシコシコシコシコ...) これも Cisco が用意しているものをベースにする

今回の完成図はこんなところまで

f:id:kakkotetsu:20171009140610p:plain

構成

前回の構成 と一緒
BGP + VxLAN + EVPN あたりが動いている torSW101a を送信側として使います。

なお、receiver 側は各スイッチの management ポートと通信可能なところに、適当なサーバを置いておきます。

参考資料

機器側からの送信に関してはマニュアルとデータ構造(詳細仕様までは分からないが...)が揃っています。
受信に関しては、インストールしてノンカスタマイズで綺麗に見えるようなメーカ謹製系のやつは見当たらなかったので、メーカが提供している Docker Hub 上のそれっぽいのを。

環境情報

受信側サーバ (前述の Cisco Docker image を動かす)

Docker インストールは Get Docker CE for Ubuntu あたりを参考に済ませているものとして。

$ uname -a
Linux receiver 4.4.0-96-generic #119-Ubuntu SMP Tue Sep 12 14:59:54 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.3 LTS"

$ docker --version
Docker version 17.09.0-ce, build afdb6d4

$ docker-compose --version
docker-compose version 1.16.1, build 6d1ac21

NX-OS

例によって 4GB メモリで限界を狙う。

torsw101a# show version

...

NX-OSv9K is a demo version of the Nexus Operating System

Software
  BIOS: version
  NXOS: version 7.0(3)I6(1)
  BIOS compile time:
  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]


Hardware
  cisco NX-OSv Chassis
   with 4037916 kB of memory.
  Processor Board ID 90SNLUQJ25I

  Device name: torsw101a
  bootflash:    3509454 kB
Kernel uptime is 28 day(s), 7 hour(s), 12 minute(s), 9 second(s)

構築~動作確認

Telemetry Receiver 初期設定

以下、どちらも同じサーバ上で動かします。
何度も立ち上げなおしたり恒久的に動かすものでもないので docker-compose は使ってないです。

dockercisco/elklat インストール~サービス起動

まずは Docker Hub の公式手順 ままで pull ~ run ~ 初期設定

$ docker pull dockercisco/elklat
$ docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
dockercisco/elklat              latest              826e2f062fc4        10 months ago       6.54GB


$ docker run -d -p 5601:5601 -p 9200:9200 -p 5044:5044 -it 826e2f062fc bash

$ docker exec -it  cranky_sinoussi service elasticsearch start
$ docker exec -it  cranky_sinoussi service elasticsearch status
 * elasticsearch is running

$ docker exec -it  cranky_sinoussi service kibana start
$ docker exec -it  cranky_sinoussi service kibana status
 * kibana is running

これで http://<Docker母艦IPアドレス>:5601/ で Kibana の画面を見られる筈です。

Elasticsearch の mapping 設定(最低限)

Kibana で各 _source@timestamp を拾うにあたって、Elasticsearch に格納されているどのフィールドを使うか...という設定をするのですが。
この後入れる telemetryreceiver が送り付けてくるデータ構造は、postDate というフィールドが何故か Unix Time 形式になってしまっています。
このまま Elasticsearch で自動的に mapping が生成されると postDate が Kibana 上で String やら Integer やらとして解釈されてしまいます。
仕方ないので、ここだけは手動で mapping を作ってやります。

まず、既に過去のデータが telemetry という index に格納されているので、これを掃除して

$ curl -XDELETE <Docker母艦IPアドレス>:9200/telemetry

そのうえで以下のように mapping を定義

$ curl -X PUT -H "Content-Type: application/json" -d @- <<EOT http://<Docker母艦IPアドレス>:9200/_template/tmpl_telemetry
{
  "template": "telemetry*",
  "mappings": {
    "modify": {
      "properties": {
          "postDate": {
            "type": "date",
            "format": "strict_date_optional_time||epoch_millis"
          }
      }
    }
  }
}
EOT

dockercisco/telemetryreceiver インストール

2017/10/08 時点で見たところ latest の更新日時が 2017/10/06 になっていて、絶賛開発中の模様です。
で、なんとなく嫌な予感がして一世代前の v4 を入れました。(深い理由はないです)

$ docker pull dockercisco/telemetryreceiver:v4

$ docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
dockercisco/telemetryreceiver   latest              fbbc29139f5b        2 days ago          1.1GB
dockercisco/telemetryreceiver   v4                  454c1e98fcbb        7 weeks ago         1.1GB
dockercisco/elklat              latest              826e2f062fc4        10 months ago       6.54GB

$ docker run -d -p 50001:50001 -it fbbc29139f5b bash
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                                                                    NAMES
a70cce08a261        fbbc29139f5b        "bash"              46 seconds ago      Up 45 seconds       0.0.0.0:50001->50001/tcp                                                 dreamy_lamarr
68a5f661093b        826e2f062fc         "bash"              6 minutes ago       Up 6 minutes        0.0.0.0:5044->5044/tcp, 0.0.0.0:5601->5601/tcp, 0.0.0.0:9200->9200/tcp   cranky_sinoussi

$ docker exec -it  vigilant_benz /grpc/telemetry/src/telemetry_receiver 50001 <Docker母艦IPアドレス> 9200 1
Server listening on 0.0.0.0:50001

最後の receiver プログラムを動かすやつは、公式手順では 末尾 "&" でバックグラウンド動作させています。
フォアグラウンドで動かしておくと、標準出力で以下のように NX-OS からの受信状況をリアルタイムで見るのに便利なので、そうしただけ。

Received GPB RPC with Data size is: 237550 Total RPC count:4926
Received GPB RPC with Data size is: 9043 Total RPC count:4927
Received GPB RPC with Data size is: 4627 Total RPC count:4928
Received GPB RPC with Data size is: 237550 Total RPC count:4929
Received GPB RPC with Data size is: 9043 Total RPC count:4930
Received GPB RPC with Data size is: 4627 Total RPC count:4931
Received GPB RPC with Data size is: 237550 Total RPC count:4932

Telemetry Sender 設定

NX-OS 側の設定を。
7.0(3)I6(1) では、エージェントインストールなど特にする必要なく、単純に CLI で feature 有効化~送信設定をするだけです。

torsw101a# show run section telemetry
show running-config | section telemetry

feature telemetry
telemetry
  destination-group 100
    ip address <Docker母艦IPアドレス> port 50001 protocol gRPC encoding GPB
  sensor-group 100
    path sys/bgp depth unbounded
    path sys/bd depth unbounded
    path sys/epId-1/nws depth unbounded
  subscription 600
    dst-grp 100
    snsr-grp 100 sample-interval 10000

sensor-group にて Cisco 公式 / Cisco Nexus 3000 and 9000 Series Telemetry Sources や実際に送信されるデータ内容を見比べながら、シコシコシコシコとカスタマイズをしていくことになります。

Kibana 初期設定(index)

ここまでで「NX-OS がデータを送信して、telemetry_receiverがデコード~Elasticsearchに格納」ってところまではできている筈です。
まあ必要に応じて、Elasticsearch に $ curl -XGET <Docker母艦IPアドレス>:9200/telemetry/_search -d '{"query" : { "match_all" : {} }}' | python3 -m json.tool とかすれば、格納されている情報がザッと(デフォルトは10件まで)見える。

なので、今度は Kibana の設定をば。
telemetry_receiver はデフォルトで telemetry という名前で index 作っているので、それを拾う設定をします。
以下のように、既に届いているデータ構造の中から Time-field name として postDate が選択肢に現れるので、それを選択します。(先の mapping 設定 at Elasticsearch がちゃんとしていれば)

f:id:kakkotetsu:20171009010547p:plain

これで画面上側の Discover タブを選んで、左上の index 選択で telemetry を選んでやれば、ザーッと右側にデータが並びます。

f:id:kakkotetsu:20171009010617p:plain

Kibana / NX-OS でカスタマイズ

ここからは、以下のようなカスタマイズをひたすらに繰り返していくことになります。

  • どんな情報を NX-OS から送信して
  • どんな情報をどんな条件で Kibana で Visualize して
  • どんな風に Kibana で Dashboard を見るか

既に Kibana の画面上で Settings > Objects を見ると各種 VisualizationDashboard が並んでいます。
が、これは別にそのまま使えるわけではなく(何かのデモで使ったものをベースにしているのか、開発中のものなのか)、少なくとも以下のようなカスタマイズが必要です。

  • KibanaVisualizations
    • indextelemetry 以外が指定されていたりするので、適宜変更
      • なお telemetry_receiverlatest では index も自分で指定できるようになっていたので、もう少しカスタマイズがきく筈
    • node_id_str という Field (要はNX-OS側のホスト名)がハードコードされていたりするので、適宜変更
    • 送信側である程度条件を絞っている、という前提がある(ものもある)ようで、送信側を雑に設定した時には期待通りの値を得られないので適宜変更
    • 仮想版では正常に得られないような情報もありそうなので適宜修正
    • etc etc
  • NX-OSsensor-group path
    • 送信したい情報を選択
      • どの階層にどの情報があるのか、マニュアルだけだとちゃんと分からないので試行錯誤

それでも、受信側をゼロから全てやっていくよりは大分マシだと思いますが。

例えば、以下のように事前定義されている Visualizations Object が並んでいますが

f:id:kakkotetsu:20171009010644p:plain

その一つを編集画面はこんな感じで、「あー、この情報を NX-OS 側から送信するのね」とかやっていきます。

f:id:kakkotetsu:20171009010718p:plain

んで、自分向けのカスタマイズをして Visualizations の一つが最低限の正しい情報が見られるようになって...

f:id:kakkotetsu:20171009010756p:plain

それを繰り返していけば、デフォルトの Dashboard も少しずつ情報が埋まっていくし、自分好みのものも作れるでしょう。(折角なので時系列なやつも見られるようにしたいですよね)

f:id:kakkotetsu:20171009010819p:plain

おしまい ~ここからが本当の地獄だ...!!~

最後に述べたような盆栽カスタマイズをしていて、メーカ謹製の受信側アプリケーションがあると楽が出来るのかなーと。(SNMPの時代から変わらないですが)
Kibana も Elasticsearch も大規模環境でお守りをしていく...ことを考えると、なかなかしんどい。この辺はまあ、自分で頑張るか金で何とかするかの話ですね...。
あと、特に受信側はコンピュータリソースをかなり食うので、得たい・得るべき情報とそのコストを天秤にかけて運用に乗せるには結構手をかけねばな、という感触です。

はい、グダグダ言ってないで盆栽弄りに戻ります...。

Nexus9000v で VxLAN+EVPN (anycast gateway 編) Appendix. IPv6エンドノード通信確認

最初に

やること/サマリ

タイトルの通り、前回記事のオマケ

構成

前回の構成 と一緒で、以下のように IPv6 セグメントを足します。

f:id:kakkotetsu:20170917230919p:plain

参考資料

前回までの Nexus9000v 設定

スタート時点の設定として、関係個所のみ show run 結果をペタリ

  • torsw101a
version 7.0(3)I6(1)
hostname torsw101a

nv overlay evpn
feature ospf
feature bgp
feature interface-vlan
feature vn-segment-vlan-based
feature lldp
clock timezone JST 9 0
feature nv overlay

vlan 1,100,300,3901
fabric forwarding anycast-gateway-mac 2020.0000.00aa
vlan 100
  vn-segment 10100
vlan 300
  vn-segment 10300
vlan 3901
  vn-segment 50001

vrf context VRF001
  vni 50001
  rd auto
  address-family ipv4 unicast
    route-target both auto
    route-target both auto evpn
vrf context management

interface Vlan1

interface Vlan100
  no shutdown
  vrf member VRF001
  no ip redirects
  ip address 192.168.1.254/24
  fabric forwarding mode anycast-gateway

interface Vlan300
  no shutdown
  vrf member VRF001
  no ip redirects
  ip address 192.168.3.254/24
  fabric forwarding mode anycast-gateway

interface Vlan3901
  no shutdown
  vrf member VRF001
  ip forward

interface nve1
  no shutdown
  source-interface loopback1
  host-reachability protocol bgp
  member vni 10001-10300
    ingress-replication protocol bgp
  member vni 50001 associate-vrf

interface Ethernet1/1
  description DEV=node11 IF=ens4
  switchport access vlan 100

interface Ethernet1/2
  description DEV=node13 IF=ens4
  switchport access vlan 300

interface Ethernet1/8
  description DEV=spine001 IF=Eth1/1
  no switchport
  mtu 9216
  ip address 192.0.2.1/31
  ip ospf network point-to-point
  ip router ospf OSPF_UNDERLAY area 0.0.0.0
  no shutdown

interface loopback0
  ip address 172.16.1.1/32
  ip router ospf OSPF_UNDERLAY area 0.0.0.0

interface loopback1
  ip address 198.18.1.11/32
  ip router ospf OSPF_UNDERLAY area 0.0.0.0

router ospf OSPF_UNDERLAY
  router-id 172.16.1.1
router bgp 64512
  neighbor 172.31.0.1
    remote-as 64512
    update-source loopback0
    address-family l2vpn evpn
      send-community
      send-community extended
  vrf VRF001
    address-family ipv4 unicast
      advertise l2vpn evpn
evpn
  vni 10100 l2
    rd auto
    route-target import auto
    route-target export auto
  vni 10300 l2
    rd auto
    route-target import auto
    route-target export auto
  • torsw201a
version 7.0(3)I6(1)
hostname torsw201a

nv overlay evpn
feature ospf
feature bgp
feature interface-vlan
feature vn-segment-vlan-based
feature lldp
clock timezone JST 9 0
feature nv overlay

vlan 1,100,200,3901
fabric forwarding anycast-gateway-mac 2020.0000.00aa
vlan 100
  vn-segment 10100
vlan 200
  vn-segment 10200
vlan 3901
  vn-segment 50001

vrf context VRF001
  vni 50001
  rd auto
  address-family ipv4 unicast
    route-target both auto
    route-target both auto evpn
vrf context management

interface Vlan1

interface Vlan100
  no shutdown
  vrf member VRF001
  no ip redirects
  ip address 192.168.1.254/24
  fabric forwarding mode anycast-gateway

interface Vlan200
  no shutdown
  vrf member VRF001
  no ip redirects
  ip address 192.168.2.254/24
  fabric forwarding mode anycast-gateway

interface Vlan3901
  no shutdown
  vrf member VRF001
  ip forward

interface nve1
  no shutdown
  source-interface loopback1
  host-reachability protocol bgp
  member vni 10001-10300
    ingress-replication protocol bgp
  member vni 50001 associate-vrf

interface Ethernet1/1
  description DEV=node21 IF=ens4
  switchport access vlan 100

interface Ethernet1/2
  description DEV=node22 IF=ens4
  switchport access vlan 200

interface Ethernet1/8
  description DEV=spine001 IF=Eth1/2
  no switchport
  mtu 9216
  ip address 192.0.2.3/31
  ip ospf network point-to-point
  ip router ospf OSPF_UNDERLAY area 0.0.0.0
  no shutdown

interface loopback0
  ip address 172.16.2.1/32
  ip router ospf OSPF_UNDERLAY area 0.0.0.0

interface loopback1
  ip address 198.18.1.21/32
  ip router ospf OSPF_UNDERLAY area 0.0.0.0

router ospf OSPF_UNDERLAY
  router-id 172.16.2.1
router bgp 64512
  neighbor 172.31.0.1
    remote-as 64512
    update-source loopback0
    address-family l2vpn evpn
      send-community
      send-community extended
  vrf VRF001
    address-family ipv4 unicast
      advertise l2vpn evpn
evpn
  vni 10100 l2
    rd auto
    route-target import auto
    route-target export auto
  vni 10200 l2
    rd auto
    route-target import auto
    route-target export auto
  • swpine001
version 7.0(3)I6(1)
hostname spine001

nv overlay evpn
feature ospf
feature bgp
feature lldp
clock timezone JST 9 0

interface Ethernet1/1
  description DEV=torsw101a IF=Eth1/8
  no switchport
  mtu 9216
  ip address 192.0.2.0/31
  ip ospf network point-to-point
  ip router ospf OSPF_UNDERLAY area 0.0.0.0
  no shutdown

interface Ethernet1/2
  description DEV=torsw201a IF=Eth1/8
  no switchport
  mtu 9216
  ip address 192.0.2.2/31
  ip ospf network point-to-point
  ip router ospf OSPF_UNDERLAY area 0.0.0.0
  no shutdown

interface loopback0
  ip address 172.31.0.1/32
  ip router ospf OSPF_UNDERLAY area 0.0.0.

router ospf OSPF_UNDERLAY
  router-id 172.31.0.1
router bgp 64512
  neighbor 172.16.1.1
    remote-as 64512
    update-source loopback0
    address-family l2vpn evpn
      send-community
      send-community extended
      route-reflector-client
  neighbor 172.16.2.1
    remote-as 64512
    update-source loopback0
    address-family l2vpn evpn
      send-community
      send-community extended
      route-reflector-client

構築

node 群の設定

通信確認用ノード群の関連設定を貼っておきます。

  • node11
kotetsu@node11:~$ ip a show dev ens4
3: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:21:96:9a:03:01 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.1/24 brd 192.168.1.255 scope global ens4
       valid_lft forever preferred_lft forever
    inet6 fd00:0:0:1::1/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::221:96ff:fe9a:301/64 scope link
       valid_lft forever preferred_lft forever

kotetsu@node11:~$ ip -6 r show dev ens4
fd00:0:0:1::/64  proto kernel  metric 256  pref medium
fe80::/64  proto kernel  metric 256  pref medium
default via fd00:0:0:1::fe  metric 1024  pref medium
  • node13
kotetsu@node13:~$ ip a show dev ens4
3: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:21:96:3d:6e:01 brd ff:ff:ff:ff:ff:ff
    inet 192.168.3.1/24 brd 192.168.3.255 scope global ens4
       valid_lft forever preferred_lft forever
    inet6 fd00:0:0:3::1/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::221:96ff:fe3d:6e01/64 scope link
       valid_lft forever preferred_lft forever

kotetsu@node13:~$ ip -6 r show dev ens4
fd00:0:0:3::/64  proto kernel  metric 256  pref medium
fe80::/64  proto kernel  metric 256  pref medium
default via fd00:0:0:3::fe  metric 1024  pref medium
  • node21
kotetsu@node21:~$ ip a show dev ens4
3: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:21:96:9f:c7:01 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.2/24 brd 192.168.1.255 scope global ens4
       valid_lft forever preferred_lft forever
    inet6 fd00:0:0:1::2/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::221:96ff:fe9f:c701/64 scope link
       valid_lft forever preferred_lft forever

kotetsu@node21:~$ ip -6 r show dev ens4
fd00:0:0:1::/64  proto kernel  metric 256  pref medium
fe80::/64  proto kernel  metric 256  pref medium
default via fd00:0:0:1::fe  metric 1024  pref medium
  • node22
kotetsu@node22:~$ ip a show dev ens4
3: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:21:96:42:5f:01 brd ff:ff:ff:ff:ff:ff
    inet 192.168.2.1/24 brd 192.168.2.255 scope global ens4
       valid_lft forever preferred_lft forever
    inet6 fd00:0:0:2::1/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::221:96ff:fe42:5f01/64 scope link
       valid_lft forever preferred_lft forever

kotetsu@node22:~$ ip -6 r show dev ens4
fd00:0:0:2::/64  proto kernel  metric 256  pref medium
fe80::/64  proto kernel  metric 256  pref medium
default via fd00:0:0:2::fe  metric 1024  pref medium

Nexus9000v 追加設定

IPv6 関係の設定追加内容は以下の通り
interface vlan 3901 という L3VNI 用の SVI でも ipv6 forward 設定をしておかないと、受信した VxLAN パケットをノード側に転送してくれないです。

  • torsw101a
interface vlan 100
 ipv6 address fd00:0:0:1::fe/64

interface vlan 300
 ipv6 address fd00:0:0:3::fe/64

interface vlan 3901
 ipv6 forward

vrf context VRF001
  address-family ipv6 unicast
    route-target both auto
    route-target both auto evpn
  • torsw201a
interface vlan 100
 ipv6 address fd00:0:0:1::fe/64

interface vlan 200
 ipv6 address fd00:0:0:2::fe/64

interface vlan 3901
 ipv6 forward

vrf context VRF001
  address-family ipv6 unicast
    route-target both auto
    route-target both auto evpn

Nexus9000v 各種テーブル確認

ノード間がフルメッシュで IPv6 での通信が可能になったので、通信確認後の Nexus9000v テーブル情報を。

EVPN 学習経路情報

torsw101a# show bgp l2vpn evpn
BGP routing table information for VRF default, address family L2VPN EVPN
BGP table version is 15946, local router ID is 172.16.1.1
Status: s-suppressed, x-deleted, S-stale, d-dampened, h-history, *-valid, >-best
Path type: i-internal, e-external, c-confed, l-local, a-aggregate, r-redist, I-injected
Origin codes: i - IGP, e - EGP, ? - incomplete, | - multipath, & - backup

   Network            Next Hop            Metric     LocPrf     Weight Path
Route Distinguisher: 172.16.1.1:32867    (L2VNI 10100)
*>l[2]:[0]:[0]:[48]:[0021.969a.0301]:[0]:[0.0.0.0]/216
                      198.18.1.11                       100      32768 i
*>i[2]:[0]:[0]:[48]:[0021.969f.c701]:[0]:[0.0.0.0]/216
                      198.18.1.21                       100          0 i
*>l[2]:[0]:[0]:[48]:[0021.969a.0301]:[32]:[192.168.1.1]/272
                      198.18.1.11                       100      32768 i
*>i[2]:[0]:[0]:[48]:[0021.969f.c701]:[32]:[192.168.1.2]/272
                      198.18.1.21                       100          0 i
*>l[2]:[0]:[0]:[48]:[0021.969a.0301]:[128]:[fd00:0:0:1::1]/368
                      198.18.1.11                       100      32768 i
*>i[2]:[0]:[0]:[48]:[0021.969f.c701]:[128]:[fd00:0:0:1::2]/368
                      198.18.1.21                       100          0 i
*>l[3]:[0]:[32]:[198.18.1.11]/88
                      198.18.1.11                       100      32768 i
*>i[3]:[0]:[32]:[198.18.1.21]/88
                      198.18.1.21                       100          0 i

Route Distinguisher: 172.16.1.1:33067    (L2VNI 10300)
*>l[2]:[0]:[0]:[48]:[0021.963d.6e01]:[0]:[0.0.0.0]/216
                      198.18.1.11                       100      32768 i
*>l[2]:[0]:[0]:[48]:[0021.963d.6e01]:[32]:[192.168.3.1]/272
                      198.18.1.11                       100      32768 i
*>l[2]:[0]:[0]:[48]:[0021.963d.6e01]:[128]:[fd00:0:0:3::1]/368
                      198.18.1.11                       100      32768 i
*>l[3]:[0]:[32]:[198.18.1.11]/88
                      198.18.1.11                       100      32768 i

Route Distinguisher: 172.16.2.1:32867
*>i[2]:[0]:[0]:[48]:[0021.969f.c701]:[0]:[0.0.0.0]/216
                      198.18.1.21                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.969f.c701]:[32]:[192.168.1.2]/272
                      198.18.1.21                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.969f.c701]:[128]:[fd00:0:0:1::2]/368
                      198.18.1.21                       100          0 i
*>i[3]:[0]:[32]:[198.18.1.21]/88
                      198.18.1.21                       100          0 i

Route Distinguisher: 172.16.2.1:32967
*>i[2]:[0]:[0]:[48]:[0021.9642.5f01]:[32]:[192.168.2.1]/272
                      198.18.1.21                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.9642.5f01]:[128]:[fd00:0:0:2::1]/368
                      198.18.1.21                       100          0 i

Route Distinguisher: 172.16.1.1:3    (L3VNI 50001)
*>i[2]:[0]:[0]:[48]:[0021.9642.5f01]:[32]:[192.168.2.1]/272
                      198.18.1.21                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.969f.c701]:[32]:[192.168.1.2]/272
                      198.18.1.21                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.9642.5f01]:[128]:[fd00:0:0:2::1]/368
                      198.18.1.21                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.969f.c701]:[128]:[fd00:0:0:1::2]/368
                      198.18.1.21                       100          0 i
torsw201a# show bgp l2vpn evpn
BGP routing table information for VRF default, address family L2VPN EVPN
BGP table version is 16191, local router ID is 172.16.2.1
Status: s-suppressed, x-deleted, S-stale, d-dampened, h-history, *-valid, >-best
Path type: i-internal, e-external, c-confed, l-local, a-aggregate, r-redist, I-injected
Origin codes: i - IGP, e - EGP, ? - incomplete, | - multipath, & - backup

   Network            Next Hop            Metric     LocPrf     Weight Path
Route Distinguisher: 172.16.1.1:32867
*>i[2]:[0]:[0]:[48]:[0021.969a.0301]:[0]:[0.0.0.0]/216
                      198.18.1.11                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.969a.0301]:[32]:[192.168.1.1]/272
                      198.18.1.11                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.969a.0301]:[128]:[fd00:0:0:1::1]/368
                      198.18.1.11                       100          0 i
*>i[3]:[0]:[32]:[198.18.1.11]/88
                      198.18.1.11                       100          0 i

Route Distinguisher: 172.16.1.1:33067
*>i[2]:[0]:[0]:[48]:[0021.963d.6e01]:[32]:[192.168.3.1]/272
                      198.18.1.11                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.963d.6e01]:[128]:[fd00:0:0:3::1]/368
                      198.18.1.11                       100          0 i

Route Distinguisher: 172.16.2.1:32867    (L2VNI 10100)
*>i[2]:[0]:[0]:[48]:[0021.969a.0301]:[0]:[0.0.0.0]/216
                      198.18.1.11                       100          0 i
*>l[2]:[0]:[0]:[48]:[0021.969f.c701]:[0]:[0.0.0.0]/216
                      198.18.1.21                       100      32768 i
*>i[2]:[0]:[0]:[48]:[0021.969a.0301]:[32]:[192.168.1.1]/272
                      198.18.1.11                       100          0 i
*>l[2]:[0]:[0]:[48]:[0021.969f.c701]:[32]:[192.168.1.2]/272
                      198.18.1.21                       100      32768 i
*>i[2]:[0]:[0]:[48]:[0021.969a.0301]:[128]:[fd00:0:0:1::1]/368
                      198.18.1.11                       100          0 i
*>l[2]:[0]:[0]:[48]:[0021.969f.c701]:[128]:[fd00:0:0:1::2]/368
                      198.18.1.21                       100      32768 i
*>i[3]:[0]:[32]:[198.18.1.11]/88
                      198.18.1.11                       100          0 i
*>l[3]:[0]:[32]:[198.18.1.21]/88
                      198.18.1.21                       100      32768 i

Route Distinguisher: 172.16.2.1:32967    (L2VNI 10200)
*>l[2]:[0]:[0]:[48]:[0021.9642.5f01]:[0]:[0.0.0.0]/216
                      198.18.1.21                       100      32768 i
*>l[2]:[0]:[0]:[48]:[0021.9642.5f01]:[32]:[192.168.2.1]/272
                      198.18.1.21                       100      32768 i
*>l[2]:[0]:[0]:[48]:[0021.9642.5f01]:[128]:[fd00:0:0:2::1]/368
                      198.18.1.21                       100      32768 i
*>l[3]:[0]:[32]:[198.18.1.21]/88
                      198.18.1.21                       100      32768 i

Route Distinguisher: 172.16.2.1:3    (L3VNI 50001)
*>i[2]:[0]:[0]:[48]:[0021.963d.6e01]:[32]:[192.168.3.1]/272
                      198.18.1.11                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.969a.0301]:[32]:[192.168.1.1]/272
                      198.18.1.11                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.963d.6e01]:[128]:[fd00:0:0:3::1]/368
                      198.18.1.11                       100          0 i
*>i[2]:[0]:[0]:[48]:[0021.969a.0301]:[128]:[fd00:0:0:1::1]/368
                      198.18.1.11                       100          0 i

ドリルダウンして、特定経路の詳細を見るとこんな感じ。
出力情報の解説は Cisco 公式 / Cisco Programmable Fabric with VXLAN BGP EVPN Configuration Guide / Chapter: Unicast Forwarding の下の方をご参照くださいませ。

torsw101anode22 (torsw201a 配下) の情報を見たものです。

torsw101a# show bgp l2vpn evpn fd00:0:0:2::1
BGP routing table information for VRF default, address family L2VPN EVPN
Route Distinguisher: 172.16.2.1:32967
BGP routing table entry for [2]:[0]:[0]:[48]:[0021.9642.5f01]:[128]:[fd00:0:0:2::1]/368, version 15909
Paths: (1 available, best #1)
Flags: (0x000202) on xmit-list, is not in l2rib/evpn, is not in HW, is locked

  Advertised path-id 1
  Path type: internal, path is valid, is best path, no labeled nexthop
  AS-Path: NONE, path sourced internal to AS
    198.18.1.21 (metric 81) from 172.31.0.1 (172.31.0.1)
      Origin IGP, MED not set, localpref 100, weight 0
      Received label 10200 50001
      Extcommunity:  RT:64512:10200 RT:64512:50001 ENCAP:8 Router MAC:0021.9643.7607
      Originator: 172.16.2.1 Cluster list: 172.31.0.1

  Path-id 1 not advertised to any peer

Route Distinguisher: 172.16.1.1:3    (L3VNI 50001)
BGP routing table entry for [2]:[0]:[0]:[48]:[0021.9642.5f01]:[128]:[fd00:0:0:2::1]/368, version 15912
Paths: (1 available, best #1)
Flags: (0x000202) on xmit-list, is not in l2rib/evpn, is not in HW

  Advertised path-id 1
  Path type: internal, path is valid, is best path, no labeled nexthop
             Imported from 172.16.2.1:32967:[2]:[0]:[0]:[48]:[0021.9642.5f01]:[128]:[fd00:0:0:2::1]/240
  AS-Path: NONE, path sourced internal to AS
    198.18.1.21 (metric 81) from 172.31.0.1 (172.31.0.1)
      Origin IGP, MED not set, localpref 100, weight 0
      Received label 10200 50001
      Extcommunity:  RT:64512:10200 RT:64512:50001 ENCAP:8 Router MAC:0021.9643.7607
      Originator: 172.16.2.1 Cluster list: 172.31.0.1

  Path-id 1 not advertised to any peer

VRF のルーティングテーブル(IPv6)

torsw101a# show ipv6 route vrf VRF001
IPv6 Routing Table for VRF "VRF001"
'*' denotes best ucast next-hop
'**' denotes best mcast next-hop
'[x/y]' denotes [preference/metric]

fd00:0:0:1::/64, ubest/mbest: 1/0, attached
    *via fd00:0:0:1::fe, Vlan100, [0/0], 01:27:22, direct,
fd00:0:0:1::1/128, ubest/mbest: 1/0, attached
    *via fd00:0:0:1::1, Vlan100, [190/0], 01:24:44, hmm
fd00:0:0:1::2/128, ubest/mbest: 1/0
    *via ::ffff:198.18.1.21%default:IPv4, [200/0], 01:14:49, bgp-64512, internal, tag 64512 (evpn) segid 50001 tunnel: 0xc6120115 encap: VXLAN

fd00:0:0:1::fe/128, ubest/mbest: 1/0, attached
    *via fd00:0:0:1::fe, Vlan100, [0/0], 01:27:22, local
fd00:0:0:2::1/128, ubest/mbest: 1/0
    *via ::ffff:198.18.1.21%default:IPv4, [200/0], 01:14:49, bgp-64512, internal, tag 64512 (evpn) segid 50001 tunnel: 0xc6120115 encap: VXLAN

fd00:0:0:3::/64, ubest/mbest: 1/0, attached
    *via fd00:0:0:3::fe, Vlan300, [0/0], 01:26:13, direct,
fd00:0:0:3::1/128, ubest/mbest: 1/0, attached
    *via fd00:0:0:3::1, Vlan300, [190/0], 01:24:45, hmm
fd00:0:0:3::fe/128, ubest/mbest: 1/0, attached
    *via fd00:0:0:3::fe, Vlan300, [0/0], 01:26:13, local
torsw201a# show ipv6 route vrf VRF001
IPv6 Routing Table for VRF "VRF001"
'*' denotes best ucast next-hop
'**' denotes best mcast next-hop
'[x/y]' denotes [preference/metric]

fd00:0:0:1::/64, ubest/mbest: 1/0, attached
    *via fd00:0:0:1::fe, Vlan100, [0/0], 01:27:51, direct,
fd00:0:0:1::1/128, ubest/mbest: 1/0
    *via ::ffff:198.18.1.11%default:IPv4, [200/0], 01:15:31, bgp-64512, internal, tag 64512 (evpn) segid 50001 tunnel: 0xc612010b encap: VXLAN

fd00:0:0:1::2/128, ubest/mbest: 1/0, attached
    *via fd00:0:0:1::2, Vlan100, [190/0], 01:17:26, hmm
fd00:0:0:1::fe/128, ubest/mbest: 1/0, attached
    *via fd00:0:0:1::fe, Vlan100, [0/0], 01:27:51, local
fd00:0:0:2::/64, ubest/mbest: 1/0, attached
    *via fd00:0:0:2::fe, Vlan200, [0/0], 01:26:04, direct,
fd00:0:0:2::1/128, ubest/mbest: 1/0, attached
    *via fd00:0:0:2::1, Vlan200, [190/0], 01:17:27, hmm
fd00:0:0:2::fe/128, ubest/mbest: 1/0, attached
    *via fd00:0:0:2::fe, Vlan200, [0/0], 01:26:04, local
fd00:0:0:3::1/128, ubest/mbest: 1/0
    *via ::ffff:198.18.1.11%default:IPv4, [200/0], 01:15:31, bgp-64512, internal, tag 64512 (evpn) segid 50001 tunnel: 0xc612010b encap: VXLAN

VRF のND テーブル

torsw101a# show ipv6 neighbor vrf VRF001

Flags: # - Adjacencies Throttled for Glean
       G - Adjacencies of vPC peer with G/W bit
       R - Adjacencies learnt remotely
       CP - Added via L2RIB, Control plane Adjacencies
       PS - Added via L2RIB, Peer Sync
       RO - Dervied from L2RIB Peer Sync Entry

IPv6 Adjacency Table for VRF VRF001
Total number of entries: 4
Address         Age       MAC Address     Pref Source     Interface
fd00:0:0:3::1   07:50:57  0021.963d.6e01  50   icmpv6     Vlan300
fe80::221:96ff:fe3d:6e01
                07:50:52  0021.963d.6e01  50   icmpv6     Vlan300
fd00:0:0:1::1   07:50:56  0021.969a.0301  50   icmpv6     Vlan100
fe80::221:96ff:fe9a:301
                07:50:57  0021.969a.0301  50   icmpv6     Vlan100
torsw201a# show ipv6 neighbor vrf VRF001

Flags: # - Adjacencies Throttled for Glean
       G - Adjacencies of vPC peer with G/W bit
       R - Adjacencies learnt remotely
       CP - Added via L2RIB, Control plane Adjacencies
       PS - Added via L2RIB, Peer Sync
       RO - Dervied from L2RIB Peer Sync Entry

IPv6 Adjacency Table for VRF VRF001
Total number of entries: 4
Address         Age       MAC Address     Pref Source     Interface
fd00:0:0:1::2   07:43:00  0021.969f.c701  50   icmpv6     Vlan100
fe80::221:96ff:fe9f:c701
                07:43:01  0021.969f.c701  50   icmpv6     Vlan100
fd00:0:0:2::1   07:43:01  0021.9642.5f01  50   icmpv6     Vlan200
fe80::221:96ff:fe42:5f01
                07:42:56  0021.9642.5f01  50   icmpv6     Vlan200

おしまい

Overlay 側は、別に IPv4 だろうが IPv6 だろうが変わりないですね、というだけの話でした。