Arista の REST(っぽい) API を ruby や Ansible で突いてみよう (original : 2014/12/14)
この記事は某所で 2014/12/14
に書いたもののコピーです。
そのため 2017/05/13
時点ではやや古い情報も含まれています。(以下一例)
Ansible
のバージョン1.8
系、今となっては古い- 今は
Arista
公式がRuby
用のライブラリを公開している(当時はなかった) - 本記事でとりあげた
Arista
公式のAnsible
モジュールは、今は「Ansible の Core Module に入っている方を使ってね」と言っている
.
概要
もう expect して正規表現でシコシコしないで良いんですか!?やった~!!
本項でやること
Arista の REST API である eAPI を触ります。
http or https で REST 叩けてリクエストやレスポンスの JSON を弄れれば、クライアント実装は何でも良いですが以下を試します。
- arista-eapi (ruby の gem)
- arista.eos (Arista 公式が出している Ansible モジュール)
いずれも「Arista EOS の REST API(eAPI) を操作することに特化」しており、これを使うと Arista の操作が楽々だ~というのを見ます。
両方で「現在の設定情報を取得して、それを基に設定投入」という操作を試します。
vEOS や ruby や ansible 本体のセットアップについては、記載しません。
Arista EOS?
この本をオススメしときますね。
Arista Warrior: A Real-World Guide to Understanding Arista Switches and EOS
- 作者: Gary A. Donahue
- 出版社/メーカー: O'Reilly Media
- 発売日: 2012/10/27
- メディア: ペーパーバック
- この商品を含むブログを見る
eAPI とは?
公式 "Introduction to Managing EOS Devices – Setting up Management" の記載から抜粋します。
Arista EOS provides multiple APIs, of which Extensible API (eAPI) is a RESTFUL programmatic interface based on the JSON structured format via HTTP or HTTPS, to simplify interactions with any Arista EOS.
情報取得や設定投入が出来ます。
リクエスト内に CLI コマンドをそのまま埋め込んで投げると、JSON で構造化されたレスポンスを得られます。
環境情報など
構成図・環境
以下の構成でやります。
Arista(vEOS) 含め全て仮想 OS です。
物理?構成は一般的なデータセンタネットワークのそれで、Arista の Multi-Chassis Link Aggregation (MLAG)を使っています。
本項では MLAG の詳細は説明しません & 設定内容もベストプラクティスではないです。
ruby 環境は nwman というノードで、Ansible 環境は ansible というノードで動かしますが、分けているのは単に元々あった環境の都合です。
ホスト環境
- ホスト:Windows7 + VirtualBox 4.3.18
ruby 環境
- OS:CentOS release 6.5
- ruby:ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-linux]
- gem:arista-eapi (0.11.1)
- gem:json (1.8.1)
- gem:rest-client (1.7.2)
Ansible 環境
Arista 環境
- vEOS Aboot-veos:Aboot-veos-2.1.0
- vEOS OS:vEOS-4.14.2F
- vEOS CPU:1core
- vEOS Memory:1024MB
eAPI 操作内容、利用 API
今回は上記構成にて「VLAN 30 と同じトポロジで VLAN 31 を作る」という操作をします。つまり
- 対象機の VLAN 状態を取得して「VLAN 30 が存在するか」・「存在する場合、VLAN 30 をどこの物理 IF に trunk で通しているか」を判断
- 1 で VLAN 30 が存在する場合、VLAN 31 を作る
- 1 で VLAN 30 が存在する場合、VLAN 30 を通している全物理 IF に VLAN 31 を追加で通す
- 2-3 を実施した場合、メモリに設定保存
てな流れです。
同じことを ruby と Ansible 両方で試します。
1 では "show vlan" をリクエストに埋め込んで状態取得します。
レスポンスの例は以下の感じです。(後述の "Exploler" で実行したコピペ)
{ "jsonrpc": "2.0", "result": [ { "sourceDetail": "", "_meta": { "execDuration": 0.04, "execStartTime": 1417437611.14 }, "vlans": { "1": { "status": "active", "name": "default", "interfaces": { "Ethernet2": { "annotation": "port channel configuration", "privatePromoted": false }, "Port-Channel1": { "privatePromoted": false }, "Ethernet1": { "annotation": "port channel configuration", "privatePromoted": false } }, "dynamic": false } } } ], "id": "CapiExplorer-123" }
事前準備
vEOS x4 (spine001/002, leaf001/002)のデプロイ
以下リンクを参考に作っておきます。公式に、各種ハイパーバイザごとの導入手順詳細とかもあります。
vEOS x4 で eAPI 有効化
有効化設定
公式 "Introduction to Managing EOS Devices – Setting up Management" の "1.10 eAPI" 通りで。
本項ではプロトコルに HTTP を使います。(HTTPS で vEOS 自己証明書の対応するのが面倒ってだけの理由です。)
デフォルトは HTTPS で、以下は HTTP で動かす例です。
leaf001#conf t leaf001(config)#management api http-commands leaf001(config-mgmt-api-http-cmds)#no protocol https leaf001(config-mgmt-api-http-cmds)#protocol http leaf001(config-mgmt-api-http-cmds)#no shutdown
http が動いていることを確認します。
leaf002#show management api http-commands Enabled: Yes HTTPS server: shutdown, set to use port 443 HTTP server: running, set to use port 80 VRF: default Hits: 0 Last hit: never Bytes in: 0 Bytes out: 0 Requests: 0 Commands: 0 Duration: 0.000 seconds URLs -------------------------------------- Management1 : http://192.168.101.53:80
ブラウザで eAPI 簡易動作確認
vEOS の Management に疎通可能な端末のブラウザで http[s]://<ip_address_switch>/
にアクセスします。
認証は vEOS に作ってあるアカウント/パスワードで。
"Command Documentation" では参照(show)系 API の仕様が見られます。設定系はなかったです。
"Explorer" ではリクエストのパラメータを少し入力するだけで簡易に試せます。(ブラウザの RestClient アドオン的な)
"Overview" ではエラーコードの説明などが見られます。スタートアップガイド的な。
2014/12/14 現在、eAPI ドキュメントは Web 上やマニュアルとしては整備されていなくて、ここで見られる情報が一番豊富だと思います。
しかし公式の QA (Supported Commands for eAPI? ) を見ると、今後マニュアルが整備されるのかも知れません。
vEOS セットアップ(その他)~実行前 config
詳細は省略しますが、VLAN や物理IF などの設定を構成図のようにセットアップしました。
ruby 版も Ansible 版も、これが実行前 config になります。
- spine001
! Command: show running-config ! device: spine001 (vEOS, EOS-4.14.2F) ! ! boot system flash:/vEOS.swi ! alias ztpprep bash sudo /mnt/flash/ztpprep ! transceiver qsfp default-mode 4x10G ! lldp timer 5 ! hostname spine001 ! spanning-tree mode mstp no spanning-tree vlan 4094 ! no aaa root ! username kotetsu secret 5 $1$H02U1Fnf$JeNAkt5krejSEFwYo7ymu1 ! clock timezone Japan ! vlan 30,4094 ! interface Port-Channel1 description DEV=leaf001 IF=Po1 switchport trunk allowed vlan 30 switchport mode trunk mlag 1 ! interface Port-Channel2 description DEV=leaf002 IF=Po1 mlag 2 ! interface Port-Channel100 description DEV=spine002 IF=Po100 switchport trunk allowed vlan 30,4094 switchport mode trunk ! interface Ethernet1 description DEV=leaf001 IF=Eth1 channel-group 1 mode active ! interface Ethernet2 description DEV=leaf002 IF=Eth1 channel-group 2 mode active ! interface Ethernet3 description DEV=spine002 IF=Eth3 channel-group 100 mode active ! interface Ethernet4 description DEV=spine002 IF=Eth4 channel-group 100 mode active ! interface Management1 ip address 192.168.101.50/24 ! interface Vlan4094 description MC-LAG dedicated PeerLink no autostate ip address 192.0.2.1/30 ! no ip routing ! mlag configuration domain-id DOMAIN_MLAG heartbeat-interval 2500 local-interface Vlan4094 peer-address 192.0.2.2 peer-link Port-Channel100 reload-delay 150 ! management api http-commands no protocol https protocol http no shutdown ! ! end
- spine002
! Command: show running-config ! device: spine002 (vEOS, EOS-4.14.2F) ! ! boot system flash:/vEOS.swi ! transceiver qsfp default-mode 4x10G ! lldp timer 5 ! hostname spine002 ! spanning-tree mode mstp no spanning-tree vlan 4094 ! no aaa root ! username kotetsu secret 5 $1$HDrZK8m8$A13QQaIqjLdrvik2.3cm9. ! clock timezone Japan ! vlan 30,4094 ! interface Port-Channel1 description DEV=leaf001 IF=Po1 switchport trunk allowed vlan 30 switchport mode trunk mlag 1 ! interface Port-Channel2 description DEV=leaf002 IF=Po1 mlag 2 ! interface Port-Channel100 description DEV=spine001 IF=Po100 switchport trunk allowed vlan 30,4094 switchport mode trunk ! interface Ethernet1 description DEV=leaf001 IF=Eth2 channel-group 1 mode active ! interface Ethernet2 description DEV=leaf002 IF=Eth2 channel-group 2 mode active ! interface Ethernet3 description DEV=spine001 IF=Eth3 channel-group 100 mode active ! interface Ethernet4 description DEV=spine001 IF=Eth4 channel-group 100 mode active ! interface Management1 ip address 192.168.101.51/24 ! interface Vlan4094 description MC-LAG dedicated PeerLink no autostate ip address 192.0.2.2/30 ! no ip routing ! mlag configuration domain-id DOMAIN_MLAG heartbeat-interval 2500 local-interface Vlan4094 peer-address 192.0.2.1 peer-link Port-Channel100 reload-delay 150 ! management api http-commands no protocol https protocol http no shutdown ! ! end
- leaf001
! Command: show running-config ! device: leaf001 (vEOS, EOS-4.14.2F) ! ! boot system flash:/vEOS.swi ! alias ztpprep bash sudo /mnt/flash/ztpprep ! transceiver qsfp default-mode 4x10G ! lldp timer 5 ! hostname leaf001 ! spanning-tree mode mstp ! no aaa root ! username kotetsu secret 5 $1$HDrZK8m8$A13QQaIqjLdrvik2.3cm9. ! clock timezone Japan ! vlan 30 ! interface Port-Channel1 description DEV=spine001_002 IF=mlag1 switchport trunk allowed vlan 30 switchport mode trunk ! interface Ethernet1 description DEV=spine001 IF=Eth1 channel-group 1 mode active ! interface Ethernet2 description DEV=spine002 IF=Eth1 channel-group 1 mode active ! interface Ethernet3 ! interface Ethernet4 switchport access vlan 30 switchport trunk allowed vlan 30 switchport mode trunk ! interface Management1 ip address 192.168.101.52/24 ! no ip routing ! management api http-commands no protocol https protocol http no shutdown ! ! end
- leaf002
! Command: show running-config ! device: leaf002 (vEOS, EOS-4.14.2F) ! ! boot system flash:/vEOS.swi ! alias ztpprep bash sudo /mnt/flash/ztpprep ! transceiver qsfp default-mode 4x10G ! lldp timer 5 ! hostname leaf002 ! spanning-tree mode mstp ! no aaa root ! username kotetsu secret 5 $1$HDrZK8m8$A13QQaIqjLdrvik2.3cm9. ! clock timezone Japan ! interface Port-Channel1 description DEV=spine001_002 IF=mlag2 ! interface Ethernet1 description DEV=spine001 IF=Eth2 channel-group 1 mode active ! interface Ethernet2 description DEV=spine002 IF=Eth2 channel-group 1 mode active ! interface Management1 ip address 192.168.101.53/24 ! no ip routing ! management api http-commands no protocol https protocol http no shutdown ! ! end
ruby (arista-eapi gem) 版
どんな gem?
Arista 公式が出しているものではないです。
特徴は以下の感じです。
- REST 周りの処理、リクエスト生成、JSON レスポンスのパースをしてくれる (ので、ユーザがやりたいことだけコーディングできる)
- リクエストのコマンド群やレスポンスは Array で表現される
- レスポンスの JSON は symbolize された Hash で取得できる
- 任意のコマンドを実行できる run メソッドと、コマンドを書かずに簡易に情報取得可能なメソッド(2014/12 現在は version だけ)がある
run メソッドさえあれば困ることはないので、本項ではそれでやります。
インストール
普通に gem install
で入れました。
# gem install arista-eapi # gem list *** LOCAL GEMS *** arista-eapi (0.11.1) json (1.8.1, 1.7.7) mime-types (2.4.3) netrc (0.8.0) rest-client (1.7.2)
スクリプト
- sample_eapi.rb
#!/usr/bin/env ruby require 'arista/eapi' require 'stringio' require 'io/console' device_list = Array.new() File.open(ARGV[0]) do |f| f.each_line do |line| device_list << line end end print ("username :") username = $stdin.gets.to_s.chomp! print ("password :") password = STDIN.noecho(&:gets).to_s.chomp! device_list.flatten.each do |target| target.chomp! puts "\n--------------------- Start #{target} ---------------------\n" array_interfaces = Array.new() device = Arista::EAPI::Switch.new(target, username, password, protocol = 'http') results = device.run(["show vlan"]) unless (results.nil? || results[0].nil? || results[0][:vlans].nil? || results[0][:vlans][:"30"].nil?) then results[0][:vlans][:"30"][:interfaces].each_key do |interface_name| array_interfaces << interface_name.to_s end # create vlan & set interface switchport unless array_interfaces.nil? then results << device.run(["enable", "configure", "vlan 31"]) puts "Created vlan 31..." array_interfaces.each do |interface| results << device.run(["enable", "configure", "interface #{interface}", "switchport trunk allowed vlan add 31"]) puts "Added trunk vlan 31 to #{interface}" end results << device.run(["enable", "write memory"]) puts "wrote memory!!" end end puts "\n--------------------- End #{target} ---------------------\n" end
device_list.list
はこんな感じで。
192.168.101.50 192.168.101.51 192.168.101.52 192.168.101.53
内容補足
require 'arista/eapi'
で arista-eapi をインポート- 実行対象は device_list ファイルに外出し、スクリプト実行時に引数で渡す (処理はシーケンシャル)
- eAPI 認証用の username と password は標準入力で interactive に
- 結果は標準出力のみ
- 設定投入時など、エラーハンドリングは割と適当 (results という Array に格納しているだけ)
実行~状態確認
$ ruby eapi_sample.rb device_list_arista.list username :kotetsu password : --------------------- Start 192.168.101.50 --------------------- Created vlan 31... Added trunk vlan 31 to port-channel1 Added trunk vlan 31 to port-channel100 wrote memory!! --------------------- End 192.168.101.50 --------------------- --------------------- Start 192.168.101.51 --------------------- Created vlan 31... Added trunk vlan 31 to port-channel1 Added trunk vlan 31 to port-channel100 wrote memory!! --------------------- End 192.168.101.51 --------------------- --------------------- Start 192.168.101.52 --------------------- Created vlan 31... Added trunk vlan 31 to port-channel1 Added trunk vlan 31 to ethernet4 wrote memory!! --------------------- End 192.168.101.52 --------------------- --------------------- Start 192.168.101.53 --------------------- --------------------- End 192.168.101.53 ---------------------
vEOS で VLAN 状態や log から、想定通り設定されていることを確認します。
spine001#show vlan VLAN Name Status Ports ----- -------------------------------- --------- ------------------------------- 1 default active Po2 30 VLAN0030 active Po1, Po100 31 VLAN0031 active Po1, Po100 4094 VLAN4094 active Cpu, Po100 pine002#show vlan VLAN Name Status Ports ----- -------------------------------- --------- ------------------------------- 1 default active Po2 30 VLAN0030 active Po1, Po100 31 VLAN0031 active Po1, Po100 4094 VLAN4094 active Cpu, Po100 leaf001#show vlan VLAN Name Status Ports ----- -------------------------------- --------- ------------------------------- 1 default active Et3 30 VLAN0030 active Et4, Po1 31 VLAN0031 active Et4, Po1 leaf002#show vlan VLAN Name Status Ports ----- -------------------------------- --------- ------------------------------- 1 default active
spine001#show logging last 4 minutes Dec 9 00:00:37 spine001 Capi: %SYS-5-CONFIG_E: Enter configuration mode from console by kotetsu on command-api (192.168.101.20) Dec 9 00:00:38 spine001 Capi: %SYS-5-CONFIG_I: Configured from console by kotetsu on command-api (192.168.101.20) Dec 9 00:00:38 spine001 Capi: %SYS-5-CONFIG_E: Enter configuration mode from console by kotetsu on command-api (192.168.101.20) Dec 9 00:00:38 spine001 Capi: %SYS-5-CONFIG_I: Configured from console by kotetsu on command-api (192.168.101.20) Dec 9 00:00:38 spine001 Capi: %SYS-5-CONFIG_E: Enter configuration mode from console by kotetsu on command-api (192.168.101.20) Dec 9 00:00:38 spine001 Capi: %SYS-5-CONFIG_I: Configured from console by kotetsu on command-api (192.168.101.20) Dec 9 00:00:39 spine001 Capi: %SYS-5-CONFIG_STARTUP: Startup config saved from system:/running-config by kotetsu on command-api (192.168.101.20).
Ansible (arista.eos Role) 版
Ansible 初心者なので、Ansibler の皆様からの突っ込み大歓迎です。
どんな Role?
Arista が公式で出している EOS(vEOS でも良い)用の Ansible role です。
github の README を見ると手っ取り早いですが、特徴は以下。
eos_command
という任意のコマンドを実行できるモジュールと、eos_<機能名>
という抽象化されてコマンドを直接書く必要ないモジュール(限定的ではある)が含まれている- Ansible リモート実行仕組み(Ansible quickstart のスライド16が分かり易い)を前提としていて、EOS 側で python スクリプトが実行されるので eAPI のクライアントも EOS 側になる(127.0.0.1 にリクエスト)
本項では汎用性が高い eos_command
のみを使います。
セットアップ
vEOS に ssh 公開鍵をバラまく
公式 README に従います。
Ansible では interactive にパスワードを入力させる仕組みもあるようなので、それを使う場合には本手順は不要だと思います。
実行対象全台に実行します。
今回は手動で実行しましたが、Ansible で鍵をバラまく方法もあったり(未試行)、Arista の場合 ZTP でやってしまう手もあるかと。
まずは、vEOS で SCP 転送用のユーザ(ansible)とディレクトリを作ります。
spine001>en spine001#bash Arista Networks EOS shell [kotetsu@spine001 ~]$ [kotetsu@spine001 ~]$ sudo useradd -d /persist/local/ansible -G eosadmin ansible [kotetsu@spine001 ~]$ echo password | sudo passwd --stdin ansible Changing password for user ansible. passwd: all authentication tokens updated successfully. [kotetsu@spine001 ~]$ sudo mkdir /persist/local/ansible/.ssh [kotetsu@spine001 ~]$ sudo chmod 700 /persist/local/ansible/.ssh [kotetsu@spine001 ~]$ sudo chown ansible:eosadmin /persist/local/ansible/.ssh [kotetsu@spine001 ~]$ sudo ls -lah /persist/local/ansible total 16K drwx------ 3 ansible ansible 160 Dec 13 00:24 . drwxrwxrwx 3 root root 60 Dec 13 00:24 .. -rw-r--r-- 1 ansible ansible 17 Sep 24 02:58 .bash_logout -rw-r--r-- 1 ansible ansible 17 Sep 24 02:58 .bash_logout.Eos -rw-r--r-- 1 ansible ansible 176 Jun 22 2011 .bash_profile -rw-r--r-- 1 ansible ansible 124 Jun 22 2011 .bashrc -rw-r--r-- 1 ansible ansible 0 Sep 24 02:58 .dircolors drwx------ 2 ansible eosadmin 40 Dec 13 00:24 .ssh [kotetsu@spine001 ~]$ $ logout spine001# spine001# spine001#exit
ansible 側から、ansible 実行時に使う公開鍵を SCP で vEOS 全台に転送します。
再起動時に ansible ユーザが作られる(& SCP 転送で使った一時的なパスワードはなくなる)ように、各 vEOS の /mnt/flash/rc.eos
を作成します。
$ scp /home/kotetsu/.ssh/id_rsa.pub ansible@192.168.101.50:.ssh/authorized_keys Password: $ ssh ansible@192.168.101.50 Arista Networks EOS shell [ansible@spine001 ~]$ vi /mnt/flash/rc.eos #!/bin/sh useradd -d /persist/local/ansible -G eosadmin ansible [ansible@spine001 ~]$ sudo reboot [ansible@spine001 ~]$ Broadcast message from ansible@spine001 (/dev/pts/3) at 0:28 ... The system is going down for reboot NOW! $ ssh ansible@192.168.101.50 Arista Networks EOS shell [ansible@spine001 ~]$
ansible に Role インストール
Ansible Galaxy に登録されているので、そこから取ってこれます。
$ sudo ansible-galaxy install arista.eos - downloading role 'eos', owned by arista - downloading role from https://github.com/arista-eosplus/ansible-eos/archive/v0.1.2.tar.gz - extracting arista.eos to /etc/ansible/roles/arista.eos - arista.eos was installed successfully
/etc/ansible/roles/arista.eos/
配下に落ちてきています。
$ ls -alh /etc/ansible/roles/arista.eos/ total 60K drwxr-xr-x 9 root root 4.0K Dec 11 00:43 . drwxr-xr-x 3 root root 4.0K Dec 11 00:43 .. -rw-rw-r-- 1 root root 687 Aug 28 10:18 CHANGELOG.md drwxr-xr-x 2 root root 4.0K Dec 11 00:43 defaults drwxr-xr-x 2 root root 4.0K Dec 11 00:43 files -rw-rw-r-- 1 root root 544 Aug 28 10:18 .gitignore drwxr-xr-x 2 root root 4.0K Dec 11 00:43 handlers drwxr-xr-x 2 root root 4.0K Dec 11 00:43 library -rw-rw-r-- 1 root root 1.5K Aug 28 10:18 LICENSE drwxr-xr-x 2 root root 4.0K Dec 11 00:43 meta -rw-rw-r-- 1 root root 11K Aug 28 10:18 README.md drwxr-xr-x 2 root root 4.0K Dec 11 00:43 tasks drwxr-xr-x 2 root root 4.0K Dec 11 00:43 vars
ansible 用 hosts ファイル作成
ベストプラクティス はガン無視して、適当な work ディレクトリに作りました。
なんか Ansible オレオレベストプラクティス とかもあるし、使い方によってディレクトリ構成のベストプラクティスも変りそうですね?
[spine] 192.168.101.[50:51] [leaf] 192.168.101.[52:53] [arista_all] spine leaf
ping pong
ansible から sftp で vEOS に疎通確認します。
$ ansible -i hosts 192.168.101.50 -u ansible -m ping 192.168.101.50 | success >> { "changed": false, "ping": "pong" }
Playbook
hosts ファイルと同じディレクトリに playbook_eapi_vlan.yml
として突っ込みました。
- name: eos nodes hosts: arista_all gather_facts: yes sudo: true vars: eapi_username: kotetsu eapi_password: kotetsu eapi_protocol: http roles: - role: arista.eos tasks: - name: get vlans action: eos_command args: { commands: [ "show vlan" ], eapi_username: "{{ eapi_username }}", eapi_password: "{{ eapi_password }}", eapi_protocol: "{{ eapi_protocol }}" } register: vlans - name: vlan30.exist? set_fact: interfaces: "{{ vlans.output[0].response.vlans[\"30\"].interfaces }}" ignore_errors: True - name: create vlan 31 action: eos_command args: { commands: [ "enable", "configure", "vlan 31", ], eapi_username: "{{ eapi_username }}", eapi_password: "{{ eapi_password }}", eapi_protocol: "{{ eapi_protocol }}" } - name: allowed vlan add 31 to interfaces action: eos_command args: { commands: [ "enable", "configure", "interface {{ item.key }}", "switchport trunk allowed vlan add 31" ], eapi_username: "{{ eapi_username }}", eapi_password: "{{ eapi_password }}", eapi_protocol: "{{ eapi_protocol }}" } with_dict: interfaces register: showint - name: save config action: eos_command args: { commands: [ "enable", "write memory" ], eapi_username: "{{ eapi_username }}", eapi_password: "{{ eapi_password }}", eapi_protocol: "{{ eapi_protocol }}" }
内容補足
- eAPI 認証用の username, password は何と
vars:
にベタ書き!! roles:
で今回セットアップしたarista.eos
を指定hosts:
で実行対象をhosts
に書いたarista_all
つまり全台指定vlan30.exist?
という task で leaf002 は Failure になり、それ以降の設定系 task は実行されない。途中で止まらないように、当該 task でignore_errors: True
にしている。(when
とか使って頑張ろうとしたがモゴモゴ)
Playbook 実行~状態確認
$ ansible-playbook playbook_eapi_vlan.yml -f 10 -u ansible -i hosts PLAY [eos nodes] ************************************************************** GATHERING FACTS *************************************************************** ok: [192.168.101.50] ok: [192.168.101.53] ok: [192.168.101.51] ok: [192.168.101.52] TASK: [arista.eos | check if running on eos node] ***************************** ok: [192.168.101.53] ok: [192.168.101.52] ok: [192.168.101.51] ok: [192.168.101.50] TASK: [arista.eos | collect eos facts] **************************************** ok: [192.168.101.52] ok: [192.168.101.51] ok: [192.168.101.53] ok: [192.168.101.50] TASK: [arista.eos | include eos variables] ************************************ ok: [192.168.101.50] ok: [192.168.101.51] ok: [192.168.101.53] ok: [192.168.101.52] TASK: [arista.eos | check for working directory] ****************************** ok: [192.168.101.52] ok: [192.168.101.50] ok: [192.168.101.51] ok: [192.168.101.53] TASK: [arista.eos | create source] ******************************************** skipping: [192.168.101.50] skipping: [192.168.101.51] skipping: [192.168.101.52] skipping: [192.168.101.53] TASK: [arista.eos | check if pip is installed] ******************************** ok: [192.168.101.50] ok: [192.168.101.53] ok: [192.168.101.52] ok: [192.168.101.51] TASK: [arista.eos | copy pip extension to node] ******************************* skipping: [192.168.101.50] skipping: [192.168.101.51] skipping: [192.168.101.53] skipping: [192.168.101.52] TASK: [arista.eos | create tmp config file to load pip] *********************** skipping: [192.168.101.50] skipping: [192.168.101.51] skipping: [192.168.101.52] skipping: [192.168.101.53] TASK: [arista.eos | load pip eos extension] *********************************** skipping: [192.168.101.51] skipping: [192.168.101.50] skipping: [192.168.101.53] skipping: [192.168.101.52] TASK: [arista.eos | copy required libraries to node] ************************** ok: [192.168.101.50] => (item=eapilib-0.1.0.tar.gz) ok: [192.168.101.51] => (item=eapilib-0.1.0.tar.gz) ok: [192.168.101.53] => (item=eapilib-0.1.0.tar.gz) ok: [192.168.101.52] => (item=eapilib-0.1.0.tar.gz) TASK: [arista.eos | install required libraries] ******************************* ok: [192.168.101.53] => (item=eapilib-0.1.0.tar.gz) ok: [192.168.101.52] => (item=eapilib-0.1.0.tar.gz) ok: [192.168.101.51] => (item=eapilib-0.1.0.tar.gz) ok: [192.168.101.50] => (item=eapilib-0.1.0.tar.gz) TASK: [arista.eos | install jsonrpclib] *************************************** skipping: [192.168.101.50] skipping: [192.168.101.53] skipping: [192.168.101.51] skipping: [192.168.101.52] TASK: [arista.eos | install required libraries and dependencies] ************** skipping: [192.168.101.50] => (item=eapilib-0.1.0.tar.gz) skipping: [192.168.101.52] => (item=eapilib-0.1.0.tar.gz) skipping: [192.168.101.51] => (item=eapilib-0.1.0.tar.gz) skipping: [192.168.101.53] => (item=eapilib-0.1.0.tar.gz) TASK: [get vlans] ************************************************************* ok: [192.168.101.52] ok: [192.168.101.50] ok: [192.168.101.51] ok: [192.168.101.53] TASK: [vlan30.exist?] ********************************************************* ok: [192.168.101.50] ok: [192.168.101.52] ok: [192.168.101.51] fatal: [192.168.101.53] => One or more undefined variables: 'dict object' has no attribute '30' TASK: [create vlan 31] ******************************************************** ok: [192.168.101.52] ok: [192.168.101.50] ok: [192.168.101.51] TASK: [allowed vlan add 31 to interfaces] ************************************* ok: [192.168.101.50] => (item={'key': u'Port-Channel1', 'value': {u'privatePromoted': False}}) ok: [192.168.101.52] => (item={'key': u'Port-Channel1', 'value': {u'privatePromoted': False}}) ok: [192.168.101.51] => (item={'key': u'Port-Channel1', 'value': {u'privatePromoted': False}}) ok: [192.168.101.50] => (item={'key': u'Port-Channel100', 'value': {u'privatePromoted': False}}) ok: [192.168.101.52] => (item={'key': u'Ethernet4', 'value': {u'privatePromoted': False}}) ok: [192.168.101.51] => (item={'key': u'Port-Channel100', 'value': {u'privatePromoted': False}}) TASK: [save config] *********************************************************** ok: [192.168.101.52] ok: [192.168.101.50] ok: [192.168.101.51] PLAY RECAP ******************************************************************** to retry, use: --limit @/home/kotetsu/playbook_eapi_vlan.retry 192.168.101.50 : ok=14 changed=0 unreachable=0 failed=0 192.168.101.51 : ok=14 changed=0 unreachable=0 failed=0 192.168.101.52 : ok=14 changed=0 unreachable=0 failed=0 192.168.101.53 : ok=10 changed=0 unreachable=1 failed=0
上記には残っていませんが、初めて ansible で vEOS に playbook を流した時には library のインストールとかの task も動いて changed
もカウントされます。
vEOS 実機を見ると以下の感じです。ログの設定次第で、もう少し細かい logging 情報が見られるかもです。
leaf001#show vlan VLAN Name Status Ports ----- -------------------------------- --------- ------------------------------- 1 default active Et3 30 VLAN0030 active Et4, Po1 31 VLAN0031 active Et4, Po1 leaf001#show logging last 5 minutes Dec 14 00:44:03 leaf001 Capi: %SYS-5-CONFIG_E: Enter configuration mode from console by kotetsu on command-api (127.0.0.1) Dec 14 00:44:03 leaf001 Capi: %SYS-5-CONFIG_I: Configured from console by kotetsu on command-api (127.0.0.1) Dec 14 00:44:04 leaf001 Capi: %SYS-5-CONFIG_E: Enter configuration mode from console by kotetsu on command-api (127.0.0.1) Dec 14 00:44:04 leaf001 Capi: %SYS-5-CONFIG_I: Configured from console by kotetsu on command-api (127.0.0.1) Dec 14 00:44:05 leaf001 Capi: %SYS-5-CONFIG_E: Enter configuration mode from console by kotetsu on command-api (127.0.0.1) Dec 14 00:44:05 leaf001 Capi: %SYS-5-CONFIG_I: Configured from console by kotetsu on command-api (127.0.0.1) Dec 14 00:44:06 leaf001 Capi: %SYS-5-CONFIG_STARTUP: Startup config saved from system:/running-config by kotetsu on command-api (127.0.0.1).
おしまい
REST と netconf のメーカ実装動向(2014/12/14 時点)
最近はようやく構造化された情報取得が出来るアレコレを実装した NW 機器が増えてきて、利用者としては嬉しいことです。
自分が抑えている範囲では、以下の感じです。(SNMP や OpenFlow は除外)
- Juniper JUNOS:netconf と最近は一部ハイエンド機器で REST
- Brocade NOS:netconf と最近は REST
- Cisco NX-OS:netconf
- Arista EOS:REST
- F5 BIG-IP:SOAP(iControl) と最近は REST
- 日立電線 Apresia Aeos:最近は netconf (ただし、parse しにくい CLI 出力がそのまま埋め込まれているレスポンスなので、正規表現で頑張れ)
- Vyos:REST 実装予定らしい
- 故 Vyatta Core:有償版では REST 対応していたらしい
何かしら統一されるとベターですけどねー。
それと、今回紹介した 2 つはいずれも ShowNet2014 活動レポート (pdf) のスライド 62 で言うところの py-junos-eznc と同じ位置づけ(特定メーカのNetwork OS に特化して便利な機能を提供するライブラリ)ですね。
雑感
- リクエストに CLI コマンドそのまま埋め込む実装は、敬遠されそうな一方で「API 未実装だからこの機能は使えない…自分で追加実装しないと」っていう面倒ごとは起き難くて(CLI で出来ることは大抵出来る)、悪くないと思います。
- レスポンスが構造化されていると、走査が楽で良いですねー。ただ、深い階層のデータをスマートに弄るのはムズいので修行しましょう。
- 構造化されたレスポンスで…ってなると、netconf とかも良いですね。snmp はちょっと…。ただ、XML より JSON の方が人間にとっては可読性も高いし、扱い易いかも。
- 自動化系ツールでいうと、最近は chef や puppet に対応する NW 機器もポロポロ(Juniper QFX とか Arista とか)ありますね。ただ、エージェント必須型のやつは昔ながらの NW 機器で使えないし、事前準備が大変すぎて、個人的にはちょっと…。Ansible も今回とりあげた範囲では「Python 2.6 以上必須」になる動きですが、やり方次第ではそれも不要になるので許容範囲です。
- ruby の方はスクリプトでちょちょいと繰り返しや条件指定出来て良いですね。Ansible(& jinja2) はそこの表現が初心者には敷居が高かったです。
- とはいえ、システム全体を統合管理することを考えると、Ansible の Role,Module 型の作りは良いと思います。最近は何でもプラグインで追加実装できる、ってのが当然のようになってきてますよね。
- これが俺の SDN だ (きっぱり