kakkotetsu

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] の下の方をご参照くださいませ。(https://www.cisco.com/c/en/us/td/docs/switches/datacenter/pf/configuration/guide/b-pf-configuration/Unicast-Forwarding.html)

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 だろうが変わりないですね、というだけの話でした。

Nexus9000v で VxLAN+EVPN (anycast gateway 編)

最初に

本項でやること / 概要構成図

以前 Juniper vQFX で似たようなことを試した のですが、その Nexus9000v 版です。
実装の違いにより、完全に同じではないですが。

構成としてはこんな感じで

f:id:kakkotetsu:20170917231256p:plain

肝になる EVPN 周りの動作はこんな感じで

f:id:kakkotetsu:20170911235700p:plain

テナントから見るとこんな風かな、という絵

f:id:kakkotetsu:20170911235733p:plain

参考資料

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)

環境情報

KVM 母艦と GNS3 は以下の感じで(前回 から Ubuntu と GNS3 のバージョンアップしているので一応)

$ uname -a
Linux kvm01 4.4.0-93-generic #116-Ubuntu SMP Fri Aug 11 21:17:51 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"

$ virsh -v
1.3.1

$ qemu-system-x86_64 --version
QEMU emulator version 2.5.0 (Debian 1:2.5+dfsg-5ubuntu10.14), Copyright (c) 2003-2008 Fabrice Bellard

$ gns3 --version
2.0.3

Nexus9000v2017/08/16 現在ダウンロード可能な最新の nxosv-final.7.0.3.I6.1.qcow2OVMF2016/08/13 時点のビルド版ぽい(MARKETPLACE で降ってきたのを使っただけ)

構築

Nexus9000v デプロイ

前回の記事 の感じで、ポチポチとデプロイしていきます。
今回、メモリは全て最低要件を狙って 4096MB としてあります。(Memory Usage Warning みたいなのは Syslog に出ていたけれど…)

同様に、疎通確認用のノードもいくらか用意しておきます。

f:id:kakkotetsu:20170912000052p:plain

Nexus9000v 物理IF 設定

Nexus 同士の部分をば。VxLAN 渡すので MTU は大きめに。
あと、NXOS の特徴的なところで feature なにがし で有効化しないと設定コマンドの候補も出てこないので、使う機能は feature コマンドでまず有効化する必要ありです。

  • spine001
feature lldp

interface Ethernet1/1
  description DEV=torsw101a IF=Eth1/8
  no switchport
  mtu 9216
  ip address 192.0.2.0/31
  no shutdown

interface Ethernet1/2
  description DEV=torsw201a IF=Eth1/8
  no switchport
  mtu 9216
  ip address 192.0.2.2/31
  no shutdown
  • torsw101a
feature lldp

interface Ethernet1/8
  description DEV=spine001 IF=Eth1/1
  no switchport
  mtu 9216
  ip address 192.0.2.1/31
  no shutdown
  • torsw201a
feature lldp

interface Ethernet1/8
  description DEV=spine001 IF=Eth1/2
  no switchport
  mtu 9216
  ip address 192.0.2.3/31
  no shutdown

Nexus9000v Underlay 設定

Cisco 公式 / Cisco Programmable Fabric with VXLAN BGP EVPN Configuration Guide / Chapter: IP Fabric Underlay あたりを参考に。

IP Unnumbered も動くみたいですが ip unnumbered loopback0 は コマンドが通らなかったので、前章の通り普通に IP アドレス降ってます。(何かの feature を有効化する必要あるのか、仮想版の機能制約かは未調査)

プロトコル選択肢としては IS-ISeBGP なども使えるようですが、「ちゃんとテストしているのは OSPF と IS-IS だ」って書いてあったので、あまり考えずに OSPF 使ってます。
これまでの記事(vQFX や Cumulus Linux)では大体 eBGP 使っていましたが、NXOS の設定体系で BGP に Underlay と Overlay を混ぜ込むと(自分的に)分かりが良くなかったというのもありますが。

f:id:kakkotetsu:20170912000111p:plain

設定

  • spine01
feature ospf

interface loopback0
  ip address 172.31.0.1/32

router ospf OSPF_UNDERLAY
  router-id 172.31.0.1

interface Ethernet1/1
  ip ospf network point-to-point
  ip router ospf OSPF_UNDERLAY area 0

interface Ethernet1/2
  ip ospf network point-to-point
  ip router ospf OSPF_UNDERLAY area 0

interface loopback0
  ip router ospf OSPF_UNDERLAY area 0.0.0.0
  • torsw101a
feature ospf

interface loopback0
  ip address 172.16.1.1/32

router ospf OSPF_UNDERLAY
  router-id 172.16.1.1

interface Ethernet1/8
  ip ospf network point-to-point
  ip router ospf OSPF_UNDERLAY area 0

interface loopback0
  ip router ospf OSPF_UNDERLAY area 0
  • torsw201a
feature ospf

interface loopback0
  ip address 172.16.2.1/32

router ospf OSPF_UNDERLAY
  router-id 172.16.2.1

interface Ethernet1/8
  ip ospf network point-to-point
  ip router ospf OSPF_UNDERLAY area 0

interface loopback0
  ip router ospf OSPF_UNDERLAY area 0

エリア0オンリーでやってますが、大規模環境ではエリア分けも検討するのが良いでしょうかね。

簡易動作確認

1台分をチラ見。

spine001# show ip route
IP Route Table for VRF "default"
'*' denotes best ucast next-hop
'**' denotes best mcast next-hop
'[x/y]' denotes [preference/metric]
'%<string>' in via output denotes VRF <string>

172.16.1.1/32, ubest/mbest: 1/0
    *via 192.0.2.1, Eth1/1, [110/41], 00:12:23, ospf-OSPF_UNDERLAY, intra
172.16.2.1/32, ubest/mbest: 1/0
    *via 192.0.2.3, Eth1/2, [110/41], 00:03:39, ospf-OSPF_UNDERLAY, intra
172.31.0.1/32, ubest/mbest: 2/0, attached
    *via 172.31.0.1, Lo0, [0/0], 00:35:13, local
    *via 172.31.0.1, Lo0, [0/0], 00:35:13, direct
192.0.2.0/31, ubest/mbest: 1/0, attached
    *via 192.0.2.0, Eth1/1, [0/0], 00:22:47, direct
192.0.2.0/32, ubest/mbest: 1/0, attached
    *via 192.0.2.0, Eth1/1, [0/0], 00:22:47, local
192.0.2.2/31, ubest/mbest: 1/0, attached
    *via 192.0.2.2, Eth1/2, [0/0], 00:22:37, direct
192.0.2.2/32, ubest/mbest: 1/0, attached
    *via 192.0.2.2, Eth1/2, [0/0], 00:22:37, local


spine001# show ip ospf neighbors
 OSPF Process ID OSPF_UNDERLAY VRF default
 Total number of neighbors: 2
 Neighbor ID     Pri State            Up Time  Address         Interface
 172.16.1.1        1 FULL/ -          00:21:16 192.0.2.1       Eth1/1
 172.16.2.1        1 FULL/ -          00:12:01 192.0.2.3       Eth1/2

Nexus9000v Overlay 設定

Cisco 公式 / Cisco Programmable Fabric with VXLAN BGP EVPN Configuration Guide / Chapter: Forwarding Configurations / Cisco Nexus 9000 Series switch configuration あたりを参考に。

f:id:kakkotetsu:20170912000126p:plain

  • spine001
feature bgp
nv overlay evpn

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
  • torsw101a
feature bgp
nv overlay evpn

router bgp 64512
  neighbor 172.31.0.1
    remote-as 64512
    update-source loopback0
    address-family l2vpn evpn
      send-community
      send-community extended
  • torsw201a
feature bgp
nv overlay evpn

router bgp 64512
  neighbor 172.31.0.1
    remote-as 64512
    update-source loopback0
    address-family l2vpn evpn
      send-community
      send-community extended

Nexus9000v VxLAN + EVPN 設定

設定の羅列を。
! で軽くポイントをコメント入れておきます…(torsw101atorsw201a はほぼ一緒の設定なので、torsw101a 側のみに)。

なお、spine001 は Control Plane としては MP-BGP の RR 動作しますがその設定は済んでおり、Data Plane としては VxLAN 通信 の土管にしかならないので、本項では何も追加設定なしです。

  • torsw101a
! anycast gateway の仮想 MAC アドレス
! fabric forwarding mode anycast-gateway 設定した SVI 全てで使われる Global 設定
fabric forwarding anycast-gateway-mac 20:20:00:00:00:AA

feature vn-segment-vlan-based
! L2 VNI と VLAN ID の mapping
vlan 100
 vn-segment 10100
vlan 300
 vn-segment 10300

! VTEP 間 VxLAN 通信の Src/Dst IP アドレスとなる loopback (先に設定した loopback0 は EVPN Signaling 用 iBGP 用)
interface loopback1
  ip address 198.18.1.11/32
  ip router ospf OSPF_UNDERLAY area 0.0.0.0

feature nv overlay
! Network Virtualization Endpoint (VTEP 用デバイス?インターフェース? 適切な言葉が...)
interface nve1
  source-interface loopback1
  host-reachability protocol bgp

! テナント向け VRF
! ちなみにデフォルトで mgmt は management VRF に所属している
vrf context VRF001
  ! 本 VRF 専用の L3VNI
  vni 50001
  rd auto
  address-family ipv4 unicast
    route-target both auto
    route-target both auto evpn

interface nve1
  ! - や , で指定可能
  ! ただし上限数があって 10001-14094 とVLAN 4094 分やろうとしたらエラーになった
  member vni 10001-10300
    ! BUM トラフィック処理方法(VTEP 間) は Ingress Replication でユニキャスト通信させる
    ! マルチキャストも選択可能だが、そもそもこんな調査・検証している理由のひとつに「マルチキャストルーティング使いたくない」もあるので
    ingress-replication protocol bgp
  member vni 50001 associate-vrf
  no shutdown

router bgp 64512
  vrf VRF001
    address-family ipv4 unicast
      advertise l2vpn evpn

! テナント用 SVI 群
! SVI 200 は torsw201a 側のみ、SVI 300 は torsw101a 側のみで OK
feature interface-vlan
interface Vlan100
  no shutdown
  vrf member VRF001
  no ip redirects
  ip address 192.168.1.254/24
  ! 本セグメントで anycast gateway を使う
  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

! テナント エンドノード収容物理 IF 設定
! trunk VLAN でも OK
interface Ethernet1/1
  switchport access vlan 100
  description DEV=node11 IF=ens4
interface Ethernet1/2
  switchport access vlan 300
  description DEV=node13 IF=ens4

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

! テナント VRF の L3 VNI につきひとつ、VLAN と SVI が必要...
! なお、VLAN は 4094 全て使えるわけでなく 39XX までしかいけない
vlan 3901
  vn-segment 50001

interface Vlan3901
  no shutdown
  vrf member VRF001
  ip forward
  • torsw201a
fabric forwarding anycast-gateway-mac 20:20:00:00:00:AA

feature vn-segment-vlan-based
vlan 100
 vn-segment 10100
vlan 200
 vn-segment 10200

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

feature nv overlay
interface nve1
  source-interface loopback1
  host-reachability protocol bgp

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

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

router bgp 64512
  vrf VRF001
    address-family ipv4 unicast
      advertise l2vpn evpn

feature interface-vlan
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 Ethernet1/1
  switchport access vlan 100
  description DEV=node21 IF=ens4

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

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

vlan 3901
  vn-segment 50001

interface Vlan3901
  no shutdown
  vrf member VRF001
  ip forward

動作確認

Nexus9000v 各種テーブル確認

これで 4 つの node が相互通信可能になったわけですが、その通信確認後の Nexus9000v のテーブルを見ていきます。

VTEP 同士の peer 状態 / 自身の NVE 状態

torsw101a# show nve peers detail
Details of nve Peers:
----------------------------------------
Peer-Ip: 198.18.1.21
    NVE Interface       : nve1
    Peer State          : Up
    Peer Uptime         : 05:40:49
    Router-Mac          : 0021.9643.7607
    Peer First VNI      : 50001
    Time since Create   : 05:40:50
    Configured VNIs     : 10001-10300,50001
    Provision State     : add-complete
    Route-Update        : Yes
    Peer Flags          : RmacL2Rib, TunnelPD, DisableLearn
    Learnt CP VNIs      : 10100,50001
    Peer-ifindex-resp   : Yes
----------------------------------------


torsw101a# show nve internal platform interface nve 1 detail
Printing Interface ifindex 0x49000001 detail
|======|=========================|===============|===============|=====|=====|
|Intf  |State                    |PriIP          |SecIP          |Vnis |Peers|
|======|=========================|===============|===============|=====|=====|
|nve1  |UP                       |198.18.1.11    |0.0.0.0        |3    |1    |
|======|=========================|===============|===============|=====|=====|

SW_BD/VNIs of interface nve1:
================================================
|======|======|=========================|======|====|======|========
|Sw BD |Vni   |State                    |Intf  |Type|Vrf-ID|Notified
|======|======|=========================|======|====|======|========
|100   |10100 |UP                       |nve1  |CP  |0     |Yes
|300   |10300 |UP                       |nve1  |CP  |0     |Yes
|3901  |50001 |UP                       |nve1  |CP  |3     |Yes
|======|======|=========================|======|====|======|========

Peers of interface nve1:
============================================

Peer_ip: 198.18.1.21
  Peer-ID   : 1
  State     : UP
  Learning  : Disabled
  TunnelID  : 0xc6120115
  MAC       : 0021.9643.7607
  Table-ID  : 0x1
  Encap     : 0x1
torsw201a# show nve peers detail
Details of nve Peers:
----------------------------------------
Peer-Ip: 198.18.1.11
    NVE Interface       : nve1
    Peer State          : Up
    Peer Uptime         : 05:40:33
    Router-Mac          : 0021.960f.f307
    Peer First VNI      : 10100
    Time since Create   : 05:40:33
    Configured VNIs     : 10001-10300,50001
    Provision State     : add-complete
    Route-Update        : Yes
    Peer Flags          : RmacL2Rib, TunnelPD, DisableLearn
    Learnt CP VNIs      : 10100,50001
    Peer-ifindex-resp   : Yes
----------------------------------------


torsw201a# show nve internal platform interface nve 1 detail
Printing Interface ifindex 0x49000001 detail
|======|=========================|===============|===============|=====|=====|
|Intf  |State                    |PriIP          |SecIP          |Vnis |Peers|
|======|=========================|===============|===============|=====|=====|
|nve1  |UP                       |198.18.1.21    |0.0.0.0        |3    |1    |
|======|=========================|===============|===============|=====|=====|

SW_BD/VNIs of interface nve1:
================================================
|======|======|=========================|======|====|======|========
|Sw BD |Vni   |State                    |Intf  |Type|Vrf-ID|Notified
|======|======|=========================|======|====|======|========
|100   |10100 |UP                       |nve1  |CP  |0     |Yes
|200   |10200 |UP                       |nve1  |CP  |0     |Yes
|3901  |50001 |UP                       |nve1  |CP  |3     |Yes
|======|======|=========================|======|====|======|========

Peers of interface nve1:
============================================

Peer_ip: 198.18.1.11
  Peer-ID   : 1
  State     : UP
  Learning  : Disabled
  TunnelID  : 0xc612010b
  MAC       : 0021.960f.f307
  Table-ID  : 0x1
  Encap     : 0x1

あと、設定終わったと思いきや何か想定通り動かない…って時に、以下のコマンドを使いました。
以下出力例は「L3 VNI 用の SVI と VLAN が不正(前記設定の 3901 を設定していなかった)」ものですが、他にも mcast-group-or-ingress-rep-not-cfg みたいな割と分かりやすい出力もあります。

torsw101a# show nve internal vni 50001

VNI 50001
  Ready-State         : Not Ready [invalid sw-bd]

MP-BGP for EVPN Signaling Neighbor 情報

torsw101a# show bgp l2vpn evpn neighbors
BGP neighbor is 172.31.0.1, remote AS 64512, ibgp link, Peer index 3
  BGP version 4, remote router ID 172.31.0.1
  BGP state = Established, up for 1d06h
  Using loopback0 as update source for this peer
  Last read 00:00:14, hold time = 180, keepalive interval is 60 seconds
  Last written 00:00:48, keepalive timer expiry due 00:00:11
  Received 2911 messages, 0 notifications, 0 bytes in queue
  Sent 3216 messages, 0 notifications, 0 bytes in queue
  Connections established 1, dropped 0
  Last reset by us never, due to No error
  Last reset by peer never, due to No error

  Neighbor capabilities:
  Dynamic capability: advertised (mp, refresh, gr) received (mp, refresh, gr)
  Dynamic capability (old): advertised received
  Route refresh capability (new): advertised received
  Route refresh capability (old): advertised received
  4-Byte AS capability: advertised received
  Address family L2VPN EVPN: advertised received
  Graceful Restart capability: advertised received

  Graceful Restart Parameters:
  Address families advertised to peer:
    L2VPN EVPN
  Address families received from peer:
    L2VPN EVPN
  Forwarding state preserved by peer for:
  Restart time advertised to peer: 120 seconds
  Stale time for routes advertised by peer: 300 seconds
  Restart time advertised by peer: 120 seconds
  Extended Next Hop Encoding Capability: advertised received
  Receive IPv6 next hop encoding Capability for AF:
    IPv4 Unicast

  Message statistics:
                              Sent               Rcvd
  Opens:                         1                  1
  Notifications:                 0                  0
  Updates:                    1443               1449
  Keepalives:                 1771               1460
  Route Refresh:                 0                  0
  Capability:                    1                  1
  Total:                      3216               2911
  Total bytes:              155694             160466
  Bytes in queue:                0                  0

  For address family: L2VPN EVPN
  BGP table version 2897, neighbor version 2897
  4 accepted paths consume 512 bytes of memory
  6 sent paths
  Community attribute sent to this neighbor
  Extended community attribute sent to this neighbor
  Third-party Nexthop will not be computed.
  Last End-of-RIB received 00:00:01 after session start

  Local host: 172.16.1.1, Local port: 19618
  Foreign host: 172.31.0.1, Foreign port: 179
  fd = 76

EVPN 学習経路情報

Cumulus Linux と似た出力フォーマットですね。

torsw101a# show bgp l2vpn evpn
BGP routing table information for VRF default, address family L2VPN EVPN
BGP table version is 2893, 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[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[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[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

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

MAC アドレステーブル

Cisco 公式 / NX-OSv 9000 Guide / NX-OSv 9000 Software Functionality の下の方に NX-OSv 9000 Feature UI/CLI Difference From Hardware Platform ってのがあって、show mac addr とかその辺は代わりにこのコマンドを使え、とあったので仮想版ではこれで。

anycast gateway(20:20:00:00:00:aa) のエントリ表示がおかしいのはご愛敬ということで。

torsw101a# show system internal l2fwder mac
Legend:
        * - primary entry, G - Gateway MAC, (R) - Routed MAC, O - Overlay MAC
        age - seconds since last seen,+ - primary entry using vPC Peer-Link,
        (T) - True, (F) - False, C - ControlPlane MAC
   VLAN     MAC Address      Type      age     Secure NTFY Ports
---------+-----------------+--------+---------+------+----+------------------
G  3901    0021.960f.f307    static   -          F     F   sup-eth1(R)
G   100    0021.960f.f307    static   -          F     F   sup-eth1(R)
*   100    0021.969f.c701    static   -          F     F  (0x47000001) nve-peer1 198.18.
G   300    0021.960f.f307    static   -          F     F   sup-eth1(R)
*   100    0021.969a.0301   dynamic   00:02:55   F     F     Eth1/1
*   300    0021.963d.6e01   dynamic   00:03:06   F     F     Eth1/2
    1           1         -20:20:00:00:00:aa         -             1
torsw201a# show system internal l2fwder mac
Legend:
        * - primary entry, G - Gateway MAC, (R) - Routed MAC, O - Overlay MAC
        age - seconds since last seen,+ - primary entry using vPC Peer-Link,
        (T) - True, (F) - False, C - ControlPlane MAC
   VLAN     MAC Address      Type      age     Secure NTFY Ports
---------+-----------------+--------+---------+------+----+------------------
G   100    0021.9643.7607    static   -          F     F   sup-eth1(R)
*   200    0021.9642.5f01   dynamic   00:01:03   F     F     Eth1/2
*   100    0021.969f.c701   dynamic   00:03:40   F     F     Eth1/1
G   200    0021.9643.7607    static   -          F     F   sup-eth1(R)
*   100    0021.969a.0301    static   -          F     F  (0x47000001) nve-peer1 198.18.
    1           1         -20:20:00:00:00:aa         -             1

MAC アドレステーブル(EVPN 学習観点)

Seq No があるってことは MAC Mobility Extended Community が使えるんじゃないのか!?(説明放棄)

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         0021.969a.0301 Local  L,            0          Eth1/1
100         0021.969f.c701 BGP    SplRcv        0          198.18.1.21
300         0021.963d.6e01 Local  L,            0          Eth1/2
3901        0021.9643.7607 VXLAN  Rmac          0          198.18.1.21
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         0021.969a.0301 BGP    SplRcv        0          198.18.1.11
100         0021.969f.c701 Local  L,            0          Eth1/1
200         0021.9642.5f01 Local  L,            0          Eth1/2
3901        0021.960f.f307 VXLAN  Rmac          0          198.18.1.11

VRF の ARP テーブル

VxLAN や EVPN は関係ないですが。
なお AgeOut しそうになると、自発的に Nexus9000v が ARP request をノードに投げて、reply があったら AgeOut させないという(割とよくある)動きをしていました。
また、自身の配下にあるノードの分しか見えません。

torsw101a# show ip arp vrf VRF001

Flags: * - Adjacencies learnt on non-active FHRP router
       + - Adjacencies synced via CFSoE
       # - Adjacencies Throttled for Glean
       CP - Added via L2RIB, Control plane Adjacencies
       PS - Added via L2RIB, Peer Sync
       RO - Dervied from L2RIB Peer Sync Entry
       D - Static Adjacencies attached to down interface

IP ARP Table for context VRF001
Total number of entries: 2
Address         Age       MAC Address     Interface       Flags
192.168.3.1     00:02:36  0021.963d.6e01  Vlan300
192.168.1.1     00:02:24  0021.969a.0301  Vlan100
torsw201a# show ip arp vrf VRF001

Flags: * - Adjacencies learnt on non-active FHRP router
       + - Adjacencies synced via CFSoE
       # - Adjacencies Throttled for Glean
       CP - Added via L2RIB, Control plane Adjacencies
       PS - Added via L2RIB, Peer Sync
       RO - Dervied from L2RIB Peer Sync Entry
       D - Static Adjacencies attached to down interface

IP ARP Table for context VRF001
Total number of entries: 2
Address         Age       MAC Address     Interface       Flags
192.168.1.2     00:00:07  0021.969f.c701  Vlan100
192.168.2.1     00:02:31  0021.9642.5f01  Vlan200

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

EVPN の MAC-IP を NLRI Type 2 でやりとりしているので、ホスト単位の経路情報になってます。

torsw101a# show ip route vrf VRF001
IP Route Table for VRF "VRF001"
'*' denotes best ucast next-hop
'**' denotes best mcast next-hop
'[x/y]' denotes [preference/metric]
'%<string>' in via output denotes VRF <string>

192.168.1.0/24, ubest/mbest: 1/0, attached
    *via 192.168.1.254, Vlan100, [0/0], 1d06h, direct
192.168.1.1/32, ubest/mbest: 1/0, attached
    *via 192.168.1.1, Vlan100, [190/0], 1d06h, hmm
192.168.1.2/32, ubest/mbest: 1/0
    *via 198.18.1.21%default, [200/0], 1d06h, bgp-64512, internal, tag 64512 (evpn) segid: 50001 tunnelid: 0xc6
120115 encap: VXLAN

192.168.1.254/32, ubest/mbest: 1/0, attached
    *via 192.168.1.254, Vlan100, [0/0], 1d06h, local
192.168.2.1/32, ubest/mbest: 1/0
    *via 198.18.1.21%default, [200/0], 1d06h, bgp-64512, internal, tag 64512 (evpn) segid: 50001 tunnelid: 0xc6
120115 encap: VXLAN

192.168.3.0/24, ubest/mbest: 1/0, attached
    *via 192.168.3.254, Vlan300, [0/0], 1d06h, direct
192.168.3.1/32, ubest/mbest: 1/0, attached
    *via 192.168.3.1, Vlan300, [190/0], 1d06h, hmm
192.168.3.254/32, ubest/mbest: 1/0, attached
    *via 192.168.3.254, Vlan300, [0/0], 1d06h, local
torsw201a# show ip route vrf VRF001
IP Route Table for VRF "VRF001"
'*' denotes best ucast next-hop
'**' denotes best mcast next-hop
'[x/y]' denotes [preference/metric]
'%<string>' in via output denotes VRF <string>

192.168.1.0/24, ubest/mbest: 1/0, attached
    *via 192.168.1.254, Vlan100, [0/0], 1d21h, direct
192.168.1.1/32, ubest/mbest: 1/0
    *via 198.18.1.11%default, [200/0], 1d06h, bgp-64512, internal, tag 64512 (evpn) segid: 50001 tunnelid
: 0xc612010b encap: VXLAN

192.168.1.2/32, ubest/mbest: 1/0, attached
    *via 192.168.1.2, Vlan100, [190/0], 1d11h, hmm
192.168.1.254/32, ubest/mbest: 1/0, attached
    *via 192.168.1.254, Vlan100, [0/0], 1d21h, local
192.168.2.0/24, ubest/mbest: 1/0, attached
    *via 192.168.2.254, Vlan200, [0/0], 1d09h, direct
192.168.2.1/32, ubest/mbest: 1/0, attached
    *via 192.168.2.1, Vlan200, [190/0], 1d06h, hmm
192.168.2.254/32, ubest/mbest: 1/0, attached
    *via 192.168.2.254, Vlan200, [0/0], 1d09h, local
192.168.3.1/32, ubest/mbest: 1/0
    *via 198.18.1.11%default, [200/0], 1d06h, bgp-64512, internal, tag 64512 (evpn) segid: 50001 tunnelid
: 0xc612010b encap: VXLAN

通信・パケット確認

Cisco 公式 / Cisco Programmable Fabric with VXLAN BGP EVPN Configuration Guide / Chapter: Unicast Forwarding に色々なパターンのフォワーディング動作が書いてあるので、それと見比べながら。

node11(VLAN 100) node21(VLAN 100) 通信 (via L2VNI)

各ノードで $ sudo ip n flush dev ens4ARP テーブル flush した上で

kotetsu@node11:~$ ping 192.168.1.2
PING 192.168.1.2 (192.168.1.2) 56(84) bytes of data.
64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=24.5 ms
64 bytes from 192.168.1.2: icmp_seq=2 ttl=64 time=7.11 ms
64 bytes from 192.168.1.2: icmp_seq=3 ttl=64 time=7.05 ms
kotetsu@node11:~$ ip n show dev ens4
192.168.1.2 lladdr 00:21:96:9f:c7:01 STALE
192.168.1.254 lladdr 20:20:00:00:00:aa STALE
kotetsu@node21:~$ ip n show dev ens4
192.168.1.1 lladdr 00:21:96:9a:03:01 STALE
192.168.1.254 lladdr 20:20:00:00:00:aa STALE

node11 からの ARP request は torsw101a が L2 VNI 10100 で VxLAN カプセル化して Ingress Replication (外側の IP src/dst は torsw[12]01a の loopback1)

f:id:kakkotetsu:20170912000201p:plain

node11 からの ICMP Echo Request は単に L2 VNI 10100 で VxLAN カプセル化されたやつ

f:id:kakkotetsu:20170912000213p:plain

node11(VLAN 100) node22(VLAN 200) 通信 (via L3VNI)

各ノードで $ sudo ip n flush dev ens4ARP テーブル flush した上で

kotetsu@node11:~$ ping 192.168.2.1
PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
64 bytes from 192.168.2.1: icmp_seq=1 ttl=62 time=22.1 ms
64 bytes from 192.168.2.1: icmp_seq=2 ttl=62 time=9.69 ms
64 bytes from 192.168.2.1: icmp_seq=3 ttl=62 time=12.3 ms
64 bytes from 192.168.2.1: icmp_seq=4 ttl=62 time=8.81 ms
kotetsu@node11:~$ ip n show dev ens4
192.168.1.2  FAILED
192.168.1.254 lladdr 20:20:00:00:00:aa STALE
kotetsu@node22:~$ ip n show dev ens4
192.168.2.254 lladdr 20:20:00:00:00:aa STALE

ARP Req は torsw101a から torsw201aVNI 10100 で転送されるが、torsw201a 側が ARP Reply はしないでくれるので Dup った ARP Reply が node11 に戻ることはないです。(結果的には良いのですが、torsw201a はどうやって巧く判断しているんだろう…)

f:id:kakkotetsu:20170912000228p:plain

node11 からの ICMP Echo Request は torsw101a が VRF 間の L3 VNI 50001 でカプセル化して転送

f:id:kakkotetsu:20170912000302p:plain

Control Plane パケット

かなり見飽きてきた感はありますが EVPN NLRI Type2 MAC/IP Advertisement route) Update を一つ見てみます。
今回、inter subnet 通信のために SVI を作っていますので、MAC アドレスだけでなくだけでなく IP アドレスもアドバタイズされています。

f:id:kakkotetsu:20170912000313p:plain

おしまい

  • Nexus9000v は仮想版なのに VxLAN + EVPN がそれなりに動いてくれる良い奴です
    • anycast gateway が動きました
      • 仮想 MAC アドレスで通信してくれるし、ICMP Echo Request にも応えてくれる良い奴です
    • ARP supression が動かせないのが残念
      • 「要はただの Proxy ARP だろうが!」なんて野暮なことは言わないで下さい
  • Nexus といえば最近は ACI 推しなイメージですが
    • 用途がマッチすれば ACI を使うことで、こういう基盤の細かい L2, L3 周りを隠ぺいしてくれるものと思われます (使ったことないからマーケティング公開情報ベースの想像)
    • ACI がマッチしない用途でも、Nexus を普通のイーサネットスイッチとして独立動作させることもできます (本項のような)
    • 何を言いたいかって「選択肢があるって良いことですね」「Nexus 使ったからって皆が (物理ネットワークを気にしてこんな設定をしないと | ACI を使わないと) いけないわけではないですよ」ということです

Cisco Nexus9000v を KVM+GNS3 で動かす

最初に

やること/サマリ

本項では以下の話をします。

  • KVM+GNS3 環境で Nexus9000v を動かすところまで

話のポイントはこんなところ

  • GNS3MARKETPLACE を使ったアプライアンスデプロイが楽
  • Cisco Nexus 9000 シリーズの仮想版であるところの Nexus9000v2017/08/16 現在、誰でもダウンロード可能なので NX-OS 素振りが出来る
    • SW 版固有の機能制約はもちろんあり
    • メモリ要件は Minimum 4GB, Recommended 8GB といういつも通りなやつ

まー Nexus9000v のデプロイで UEFI 必須でタルかったので MARKETPLACE に逃げた、というのが正直なところですが。

参考資料

思うに、こんなページをわざわざ見に来る人は、このリンクを見れば自分で問題なく出来そうだよね。

環境情報

KVM 母艦と GNS3 は以下の感じで

$ uname -a
Linux kvm01 4.4.0-79-generic #100-Ubuntu SMP Wed May 17 19:58:14 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.2 LTS"

$ virsh -v
1.3.1

$ qemu-system-x86_64 --version
QEMU emulator version 2.5.0 (Debian 1:2.5+dfsg-5ubuntu10.14), Copyright (c) 2003-2008 Fabrice Bellard

$ gns3 --version
2.0.2

Nexus9000v2017/08/16 現在ダウンロード可能な最新の nxosv-final.7.0.3.I6.1.qcow2OVMF2016/08/13 時点のビルド版ぽい(MARKETPLACE で降ってきたのを使っただけ)

Nexus9000v デプロイ

Cisco サイトで OS ファイル取得

まあこの後のデプロイ手順は、ここで取得したファイルは使わないのですが…「俺は正当にこのファイルをダウンロードして使える権限を持っているんだな」って確認はしておきたいじゃないですか。

上記から qcow2 ファイルを取得(紐付いているのはハンペンAP1台だけの個人アカウントでも問題なく)して、チェックサムを確認

$ ll nxosv-final.7.0.3.I6.1.qcow2
-rw-r--r-- 1 kotetsu kotetsu 780402688 Aug 16 23:07 nxosv-final.7.0.3.I6.1.qcow2

$ sha512sum nxosv-final.7.0.3.I6.1.qcow2
93f2ffdcb230b3a0bcba4a120db4fcc752a1f91adfd911c9d11d3a725f0bbba7df40509ce896dcea45d33cbeacfa9fad9054678f91169ea1d684db8aadaa04cb  nxosv-final.7.0.3.I6.1.qcow2

以下は 2017/08/16 時点でのダウンロード時画面キャプチャ

f:id:kakkotetsu:20170817224740p:plain

f:id:kakkotetsu:20170817224758p:plain

GNS3 の MARKETPLACE から Nexus9000v 用の テンプレートファイル取得

以下のリンクから DOWNLOAD TEMPLATE をポチッて cisco-nxosv9k.gns3a を取得します(完)

テンプレートファイルをインポート

いつも通り GNS3 の適当なプロジェクトを開いたら、先ほど取得した cisco-nxosv9k.gns3a をインポートします。
GNS3File -> Import appliance を押して後は流れで…って感じですが、必要に応じて GNS3 公式 / Import GNS3 appliance に画面キャプチャ付の公式手順もございますので、そちらも合わせてご参照くださいませ。

f:id:kakkotetsu:20170817224850p:plain

f:id:kakkotetsu:20170817224900p:plain

f:id:kakkotetsu:20170817224910p:plain

f:id:kakkotetsu:20170817224924p:plain

f:id:kakkotetsu:20170817224934p:plain

これで GNS3 のテンプレートとして All devicesCisco NX-OSv 9000 7.0.3.I6.1 ができあがります。

手動でやる時には、以下あたりと睨めっこしながら試行錯誤して…というのがお決まりの流れだと思うんですが、そこをサボッたのがこの手順です。

ちゃんと動くお手本パラメータとして使う、ってのもありかもですね。

テンプレートからデプロイ~起動

いつも通りにテンプレート(all devices)からD&Dでデプロイします。
なお、GNS3 MARKETPLACE から取得したテンプレートは前述の公式パラメータに完全に沿ったものになっていますので、メモリを 4GB に減らすなり用途に応じたチューニングはお好きなように。

$ ps aux | grep [N]X-OS
root     17924 12.7  1.1 9240844 367620 pts/12 Sl+  02:26   0:05 /usr/bin/qemu-system-x86_64 -name CiscoNX-OSv90007.0.3.I6.1-1 -m 8096M -smp cpus=2 -enable-kvm -machine smm=off -boot order=c -bios /home/kotetsu/GNS3/images/QEMU/OVMF-20160813.fd -device ahci,id=ahci0,bus=pci.0 -drive file=/home/kotetsu/GNS3/projects/700/project-files/qemu/f8111e6a-9fd1-4f1b-9fd6-a3c9a42b0ff3/hda_disk.qcow2,if=none,id=drive-sata-disk0,index=0,media=disk -device ide-drive,drive=drive-sata-disk0,bus=ahci0.0,id=drive-sata-disk0 -uuid f8111e6a-9fd1-4f1b-9fd6-a3c9a42b0ff3 -serial telnet:127.0.0.1:5004,server,nowait -monitor tcp:127.0.0.1:54313,server,nowait -net none -device e1000,mac=00:21:96:0f:f3:00,netdev=gns3-0 -netdev socket,id=gns3-0,udp=127.0.0.1:10019,localaddr=0.0.0.0:10018 -device e1000,mac=00:21:96:0f:f3:01,netdev=gns3-1 -netdev socket,id=gns3-1,udp=127.0.0.1:10021,localaddr=0.0.0.0:10020 -device e1000,mac=00:21:96:0f:f3:02,netdev=gns3-2 -netdev socket,id=gns3-2,udp=127.0.0.1:10023,localaddr=0.0.0.0:10022 -device e1000,mac=00:21:96:0f:f3:03,netdev=gns3-3 -netdev socket,id=gns3-3,udp=127.0.0.1:10025,localaddr=0.0.0.0:10024 -device e1000,mac=00:21:96:0f:f3:04,netdev=gns3-4 -netdev socket,id=gns3-4,udp=127.0.0.1:10027,localaddr=0.0.0.0:10026 -device e1000,mac=00:21:96:0f:f3:05,netdev=gns3-5 -netdev socket,id=gns3-5,udp=127.0.0.1:10029,localaddr=0.0.0.0:10028 -device e1000,mac=00:21:96:0f:f3:06,netdev=gns3-6 -netdev socket,id=gns3-6,udp=127.0.0.1:10031,localaddr=0.0.0.0:10030 -device e1000,mac=00:21:96:0f:f3:07,netdev=gns3-7 -netdev socket,id=gns3-7,udp=127.0.0.1:10033,localaddr=0.0.0.0:10032 -device e1000,mac=00:21:96:0f:f3:08,netdev=gns3-8 -netdev socket,id=gns3-8,udp=127.0.0.1:10035,localaddr=0.0.0.0:10034 -device e1000,mac=00:21:96:0f:f3:09,netdev=gns3-9 -netdev socket,id=gns3-9,udp=127.0.0.1:10037,localaddr=0.0.0.0:10036 -nographic

f:id:kakkotetsu:20170817225311p:plain

f:id:kakkotetsu:20170817225323p:plain

f:id:kakkotetsu:20170817225335p:plain

f:id:kakkotetsu:20170817225346p:plain

んで、起動してコンソールを見守ると、以下のように ZTP ライクなものが走りだします

2017 Aug 16 17:29:15 switch %$ VDC-1 %$ %POAP-2-POAP_INFO: [90SNLUQJ25I-00:21:96:0F:F3:07] - USB Initializing Success
2017 Aug 16 17:29:15 switch %$ VDC-1 %$ %POAP-2-POAP_INFO: [90SNLUQJ25I-00:21:96:0F:F3:07] - USB disk not detected
2017 Aug 16 17:29:15 switch %$ VDC-1 %$ last message repeated 1 time
2017 Aug 16 17:29:15 switch %$ VDC-1 %$ %POAP-2-POAP_DHCP_DISCOVER_START: [90SNLUQJ25I-00:21:96:0F:F3:07] - POAP DHCP Discover phase started
2017 Aug 16 17:29:16 switch %$ VDC-1 %$ %POAP-2-POAP_INFO: [90SNLUQJ25I-00:21:96:0F:F3:07] - Invalid DHCP OFFER from 0.0.0.0: Missing Script Server information
2017 Aug 16 17:29:16 switch %$ VDC-1 %$ %POAP-2-POAP_INFO: [90SNLUQJ25I-00:21:96:0F:F3:07] - Invalid DHCP OFFER from 0.0.0.0: Missing Script Name
2017 Aug 16 17:29:20 switch %$ VDC-1 %$ %ASCII-CFG-2-CONF_CONTROL: System ready
2017 Aug 16 17:29:20 switch %$ VDC-1 %$ %POAP-2-POAP_INFO: [90SNLUQJ25I-00:21:96:0F:F3:07] - Invalid DHCP OFFER from 0.0.0.0: Missing Script Server information
2017 Aug 16 17:29:20 switch %$ VDC-1 %$ %POAP-2-POAP_INFO: [90SNLUQJ25I-00:21:96:0F:F3:07] - Invalid DHCP OFFER from 0.0.0.0: Missing Script Name
2017 Aug 16 17:29:26 switch %$ VDC-1 %$ %POAP-2-POAP_FAILURE: [90SNLUQJ25I-00:21:96:0F:F3:07] - POAP DHCP discover phase failed
2017 Aug 16 17:29:38 switch %$ VDC-1 %$ %POAP-2-POAP_INFO: [90SNLUQJ25I-00:21:96:0F:F3:07] - USB Initializing Success
2017 Aug 16 17:29:38 switch %$ VDC-1 %$ %POAP-2-POAP_INFO: [90SNLUQJ25I-00:21:96:0F:F3:07] - USB disk not detected

ひたすらループするので、以下の出力で y して止めて

Abort Auto Provisioning and continue with normal setup ?(yes/no)[n]: y
Disabling POAP

admin ユーザ用のパスワード設定とかを対話でやると、初期ログイン可能になります。

Abort Auto Provisioning and continue with normal setup ?(yes/no)[n]: y
Disabling POAP



         ---- System Admin Account Setup ----


Do you want to enforce secure password standard (yes/no) [y]:

  Enter the password for "admin":
    Wrong Password, Reason:
       [Length should be at least 8 characters]
    Invalid admin password.

    Enter the password for "admin":
  Confirm the password for "admin":

         ---- Basic System Configuration Dialog VDC: 1 ----

This setup utility will guide you through the basic configuration of
the system. Setup configures only enough connectivity for management
of the system.

Please register Cisco Nexus9000 Family devices promptly with your
supplier. Failure to register may affect response times for initial
service calls. Nexus9000 devices must be registered to receive
entitled support services.

Press Enter at anytime to skip a dialog. Use ctrl-c at anytime
to skip the remaining dialogs.

 Would you like to enter the basic configuration dialog (yes/no):

2017 Aug 16 17:33:45 switch %$ VDC-1 %$ %ACLQOS-SLOT1-2-ACLQOS_FAILED: ACLQOS failure: TCAM region is not configured for feature QoS class IPv4 direction ingress. Please configure TCAM region Ingress COPP [copp] and retry the command.

Error: There was an error executing atleast one of the command
Please verify the following log for the command execution errors.
TCAM region is not configured. Please configure TCAM region and retry the command




User Access Verification
User Access Verification
 login: admin
Password:

Cisco NX-OS Software
Copyright (c) 2002-2017, Cisco Systems, Inc. All rights reserved.
NX-OSv9K software ("NX-OSv9K Software") and related documentation,
files or other reference materials ("Documentation") are
the proprietary property and confidential information of Cisco
Systems, Inc. ("Cisco") and are protected, without limitation,
pursuant to United States and International copyright and trademark
laws in the applicable jurisdiction which provide civil and criminal
penalties for copying or distribution without Cisco's authorization.

Any use or disclosure, in whole or in part, of the NX-OSv9K Software
or Documentation to any third party for any purposes is expressly
prohibited except as otherwise authorized by Cisco in writing.
The copyrights to certain works contained herein are owned by other
third parties and are used and distributed under license. Some parts
of this software may be covered under the GNU Public License or the
GNU Lesser General Public License. A copy of each such license is
available at
http://www.gnu.org/licenses/gpl.html and
http://www.gnu.org/licenses/lgpl.html
***************************************************************************
*  NX-OSv9K is strictly limited to use for evaluation, demonstration      *
*  and NX-OS education. Any use or disclosure, in whole or in part of     *
*  the NX-OSv9K Software or Documentation to any third party for any      *
*  purposes is expressly prohibited except as otherwise authorized by     *
*  Cisco in writing.                                                      *
***************************************************************************
switch#
switch# show ver
Cisco Nexus Operating System (NX-OS) Software
TAC support: http://www.cisco.com/tac
Documents: http://www.cisco.com/en/US/products/ps9372/tsd_products_support_serie
s_home.html
Copyright (c) 2002-2017, Cisco Systems, Inc. All rights reserved.
The copyrights to certain works contained herein are owned by
other third parties and are used and distributed under license.
Some parts of this software are covered under the GNU Public
License. A copy of the license is available at
http://www.gnu.org/licenses/gpl.html.

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 06:21:28]


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

  Device name: switch
  bootflash:    3509454 kB
Kernel uptime is 0 day(s), 19 hour(s), 48 minute(s), 5 second(s)

Last reset
  Reason: Unknown
  System version:
  Service:

plugin
  Core Plugin, Ethernet Plugin

Active Package(s):

ほい、お疲れ様でしたー。

bootイメージ設定

Cisco 公式 / Troubleshooting the NX-OSv 9000 / How to prevent VM from dropping into “loader >” prompt にあるように、起動イメージを忘れずに設定しないと次回起動時に loader で止まってしまいますよ、って話

初期状態は以下なので

switch# show boot
Current Boot Variables:

sup-1
NXOS variable not set
No module boot variable set

Boot Variables on next reload:

sup-1
NXOS variable not set
No module boot variable set


switch# dir bootflash:
       4096    Aug 16 17:27:42 2017  .rpmstore/
       4096    Aug 16 17:27:53 2017  .swtam/
      38575    Aug 16 17:32:16 2017  20170816_172847_poap_26000_init.log
  759941120    May 17 06:46:25 2017  nxos.7.0.3.I6.1.bin
          0    Aug 16 17:35:04 2017  platform-sdk.cmd
       4096    Aug 16 17:28:44 2017  scripts/
       4096    Aug 16 17:28:45 2017  virt_strg_pool_bf_vdc_1/
       4096    Aug 16 17:28:06 2017  virtual-instance/
         59    Aug 16 17:27:57 2017  virtual-instance.conf

Usage for bootflash://sup-local
 1158098944 bytes used
 2379120640 bytes free
 3537219584 bytes total

以下のように boot イメージを指定しておきましょう。

switch# configure terminal
Enter configuration commands, one per line. End with CNTL/Z.
switch(config)#
switch(config)# boot nxos nxos.7.0.3.I6.1.bin
Performing image verification and compatibility check, please wait....
switch(config)#
switch(config)# end
switch#
switch# copy running-config startup-config
[########################################] 100%
Copy complete.

そーいや copy run start はその内なくなる、みたいな話を IOS 時代に見たような気もするんですが、どうなんでしたっけ…。

ともかく、これで再起動してもちゃんと OS が読み込まれます。reload でも実行して、動作確認しておきましょう。

switch# show boot
Current Boot Variables:

sup-1
NXOS variable = bootflash:/nxos.7.0.3.I6.1.bin
No module boot variable set

Boot Variables on next reload:

sup-1
NXOS variable = bootflash:/nxos.7.0.3.I6.1.bin
No module boot variable set

お手軽手順に逃げてインストールしただけなので、別に…
仮想版をユーザ制限なくダウンロードさせてもらったから言うわけじゃないですが、Nexus9000 はデータセンタスイッチとしてはなかなか良さげですよ!!

ブログサービス移行

はてなブログに引っ越してきました。
「インフラ屋さんなら自前鯖でWebサービスくらい公開せーや、ブログサービスなんて甘え」ってのはごもっともですが…。

移行元はプログラミング情報共有サービス的な感じだったんですが、僕のプログラミング要素が薄すぎて…。
2014/11-2017/03 の期間に公開した記事で、今となっては内容が古すぎる記事も多いのですが、その旨の注記を添えてこちらに移行しておきました。
移行元も先も Markdown 形式でコピペするだけだったので、とっても楽でした。(図だけはこちらにアップしなおしましたが)

ちなみに、プログラミング要素が大変うっすいアカウントの投稿内訳はこちら↓

f:id:kakkotetsu:20170513221643p:plain

f:id:kakkotetsu:20170513223655p:plain

NW 機器の自前構成情報管理 (ruby-nmap+SNMP で Discovery ~ Ansible Dynamic Inventory 連携) (original : 2015/12/15)

この記事は某所で 2015/12/15 に書いたもののコピーです。
そのため 2017/05/13 時点ではやや古い情報も含まれています。(以下一例)

  • Ansible のバージョン 1.8 系、今となっては古い

.

概要

NetOpsCoding Advent Calendar 2015 の 2015/12/15 分エントリです。 Ansible Dynamic Inventory で検索してこれにぶつかった人、ごめんなさい。そこはオマケ程度です。

本項でやること

  • Nmap と SNMPruby で操作して、手っ取り早く NW 機器の構成情報を取得~ JSON 形式で吐き出す
    • 最後に書いているが、自前で書かなくても OSS で同じことは出来るものはある
    • Nmap の ruby 版ライブラリ紹介
  • 上記ファイルを構成情報マスタとみなして、Ansible の dynamic inventory と連携する

モチベーション

  • 仮想マシンの構成管理ソフトは割とあるけれど、物理含めた NW 機器も統合的に…となると選択肢は少ない…
  • Nmap の fingerprint (OS検出)みたいなことも NW 機器相手に高精度でやりたい
  • 色々なミドルウェアで hosts ファイル的な異なるフォーマットの設定ファイルを求めてくるが、手っ取り早く生成したい
  • 大袈裟な仕組みは使いたくない
  • 2015/11/19 に Nmap 7.0 がリリースされたので使わなきゃ(使命感)

前段

Nmap

言わずと知れた OSS ツールで、以下のような機能があります。超優れもの!

  • Host Discovery | 公式
    • 対象 NW を ARG で食わせたり、テキストファイルに羅列して食わせたり
    • 出力は xml ファイルや stdout
    • ついでに名前解決(逆引き)までしてくれる(hosts ファイルでも DNS サーバでも resolve 設定に従う感じで)
  • OS Detection(fingerprint) | 公式
    • ポートスキャンの結果などから、対象機器の OS を類推する
  • etc etc

まあ 公式ドキュメントのイントロ を見れば分かるでしょう。
インフラ屋さんだと、ファイアウォールやら iptables やらのテストや、簡易な脆弱性診断で使ったりで馴染み深いのでは。
監視ソフトの Discovery 機能なんかでも、内部的に Nmap を呼んでいるものがあります。  

基本的にはシェルから扱うのですが、GUI ツールであるZenmapもあります。

ruby-nmap

上記公式からの抜粋↓

A Rubyful interface to the Nmap exploration tool and security / port scanner.

以下のようなことができる Nmap の ruby 版ライブラリです。(作者は異なるものの、同じような機能を持つ Python 版や Perl 版ライブラリも存在する)

  • Host Discovery のオプション群を分かりやすい表現で書ける
  • Host Discovery で生成された xml をパース
    • xml のパースを自前でやらなくて済むってだけで、嬉しくないですか。僕は嬉しい。

公式の Examples を見ると一目瞭然でしょう。

Ruby SNMP

SNMP を扱える ruby 版ライブラリです。(雑)
割と日本語記事も多いし、メジャーなやつでしょう。

Ansible Dynamic Inventory

他の場所に構成管理マスタがあり、Ansible の hosts ファイルを静的に書くと二重管理になるような環境で、構成管理マスタから hosts 情報をとってくる…という機能。

Discovery 環境準備

Discovery サーバ環境情報

Discovery を動かすサーバは以下です。

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"

$ ruby -v
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-linux]

$ gem list

*** LOCAL GEMS ***

bigdecimal (1.2.6)
bundler (1.10.6)
io-console (0.4.3)
json (1.8.1)
minitest (5.4.3)
power_assert (0.2.2)
psych (2.0.8)
rake (10.4.2)
rdoc (4.2.0)
test-unit (3.0.8)

あと、このサーバから network reachable な NW 機器をいくらか動かしておきます。(本項では同一セグメント)

Nmap 7.0 インストール

以下公式手順に従います。

Download は以下から。

こんな感じでいけるでしょう。

$ sudo -E apt-get install libssl-dev
$ cd /var/tmp/
$ wget https://nmap.org/dist/nmap-7.00.tar.bz2
$ bzip2 -cd nmap-7.00.tar.bz2 | tar xvf -

$ ls -al
total 8724
drwxrwxrwt  3 root  root     4096 Dec  5 14:38 .
drwxr-xr-x 11 root  root     4096 Nov  7 17:42 ..
drwxr-xr-x 22 kotetsu kotetsu  4096 Nov 20 01:19 nmap-7.00
-rw-rw-r--  1 kotetsu kotetsu 8918906 Nov 20 05:23 nmap-7.00.tar.bz2

$ cd nmap-7.00/
$ ./configure
$ make
$ sudo -E make install

(略)

NMAP SUCCESSFULLY INSTALLED

$ nmap --version

Nmap version 7.00 ( https://nmap.org )
Platform: x86_64-unknown-linux-gnu
Compiled with: nmap-liblua-5.2.3 openssl-1.0.1f nmap-libpcre-7.6 nmap-libpcap-1.7.3 nmap-libdnet-1.12 ipv6
Compiled without:
Available nsock engines: epoll poll select

ruby-nmap / snmp の gem インストール

どちらも gem install 一発で入ります。
ruby-nmapxml を扱う関係上、依存関係に Nokogiri があります。なので、インストールで躓いたらググって解決して下さい。

以下は bundler を使った場合の手順です。

$ cd
$ mkdir discovery
$ cd discovery/
$ bundle init
Writing new Gemfile to /home/kotetsu/discovery/Gemfile

Gemfile を以下のように編集

$ cat Gemfile
# A sample Gemfile
source "https://rubygems.org"

#
gem 'ruby-nmap'
gem `snmp`


$ bundle install --path vendor/bundle
$ cat Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
  mini_portile2 (2.0.0)
  nokogiri (1.6.7)
    mini_portile2 (~> 2.0.0.rc2)
  rprogram (0.3.2)
  ruby-nmap (0.8.0)
    nokogiri (~> 1.3)
    rprogram (~> 0.3)
  snmp (1.2.0)

PLATFORMS
  ruby

DEPENDENCIES
  ruby-nmap
  snmp

BUNDLED WITH
   1.10.6

NW 機器側の SNMP 設定

今回、OS検知もどきとして SNMP Get で NW 機器の sysDescr を取得します。
機種依存なく NW 機器の OS を知りたいならば、まあこれしかないでしょう、ってことで。

あと、ラック情報とかも纏めて取れるようにしておくと良いよね、ってことでそれは sysLocation に設定しておきます(本項では全部仮想環境なのでアレな感じですが)。

何機種分か設定例を。

  • JUNOS 例

snmp description とかを設定していると、OS 情報(デフォルト値)をとれなくなってしまうので注意。

kotetsu@vsrx01> show configuration | display set | match snmp
set snmp location KotetsuNoteVB
set snmp community KOTETSU_NW
  • Arista 例
vEOS-spine001#show run | inc snmp
snmp-server location KotetsuNoteVB
snmp-server community KOTETSU_NW ro
  • VyOS 例
kotetsu@vtep-vyos01:~$ show configuration commands | match snmp
set service snmp community 'KOTETSU_NW'
set service snmp location 'KotetsuNoteVB'

Discovery スクリプト作成~配置

ディレクト

以下のように適当なディレクトリを用意して、

$ mkdir scripts
$ cd scripts/

後述の 3 つのファイルを配置します。

スクリプト本体

ruby-nmapRuby SNMP を使って NW 機器の構成情報を収集して、JSON ファイルに吐き出すサンプルスクリプトです。

# encoding: utf-8

#
# Usage:
#  bundle exec ruby discovery_nwdevs.rb <setting.yml>
#

require 'nmap/program'
require 'nmap/xml'
require 'snmp'
require 'yaml'
require 'json'


#
# SNMP Get method
#
def snmp_get_system(target_ip, snmp_params)
  manager = SNMP::Manager.new(
    :host => target_ip,
    :port => 161,
    :version => :SNMPv2c,
    :community => snmp_params["community"],
    :timeout => 1,
    :retries => 1
  )

  # sysName にはホスト名
  # sysDescr には機種やバージョン情報
  # sysLocation は(設定していれば)設置場所
  # が入っている筈なので、それをとる
  ret = Hash::new
  ret[:sysname] = manager.get_value(snmp_params["oid_sysname"]).to_s rescue ""
  ret[:sysdesc] = manager.get_value(snmp_params["oid_sysdesc"]).to_s rescue ""
  ret[:syslocation] = manager.get_value(snmp_params["oid_syslocation"]).to_s rescue ""
  manager.close

  return ret
end



#
# main
#
begin

  # 外出ししている情報 YAML ファイルをロード
  params = YAML.load_file(ARGV[0])

  # Nmap の Host Discovery をして xml ファイルに書き出す
  # ポートスキャンは UDP/161 のみに限定して、SNMP Get で情報が取得可能かどうかを見ておく
  # NW 機器なんて Nmap の fingerprint 機能に頼るよりは、SNMP Get で見た方が確実なので、fingerprint もしない
  Nmap::Program.scan do |nmap|
    nmap.syn_scan = false
    nmap.udp_scan = true
    nmap.service_scan = false
    nmap.os_fingerprint = false

    nmap.xml = params["nmap_params"]["xml_file"]
    nmap.verbose = true

    nmap.ports = []
    unless params["nmap_params"]["scan_udp_ports"].nil? then
      params["nmap_params"]["scan_udp_ports"].each do |port|
        nmap.ports << port["port"]
      end
    end

    # Discovery の対象とする NW アドレスを羅列したファイル
    nmap.target_file = params["nmap_params"]["scan_target_files"]["targetfile"]
  end


  # 反応があったノード(up host)の情報(Hash)を格納
  array_hosts = []

  # Nmap の Host Discovery で生成された xml ファイルを走査
  # 反応があったノード(up host)を取得し、
  # UDP/161 が Open 判定されていたら SNMP で追加情報取得する
  Nmap::XML.new(params["nmap_params"]["xml_file"]) do |xml|
    xml.each_up_host do |host|
      hash_host = {}
      hash_snmp_info = {}

      hash_host[:ipaddr] = host.ipv4
      hash_host[:ptr] = host.hostname
      hash_host[:sysname] = ""
      hash_host[:sysdesc] = ""
      hash_host[:syslocation] = ""

      host.each_port do |port|
        if (port.number == 161) && (port.state == :open) then
          hash_snmp_info = snmp_get_system(host.ipv4, params["snmp_params"])
          unless hash_snmp_info.nil? then
            hash_host[:sysname] = hash_snmp_info[:sysname]
            hash_host[:sysdesc] = hash_snmp_info[:sysdesc]
            hash_host[:syslocation] = hash_snmp_info[:syslocation]
          end
        end
      end
      array_hosts << hash_host
    end
  end


  # 収集したノードの情報を JSON 形式ファイルで吐き出す
  unless array_hosts.nil? then
    dir_result = File.expand_path(params["nmap_params"]["output_directory"])

    json_hosts = JSON.pretty_unparse(array_hosts)
    File.open("#{dir_result}/list_nwdev.json","w") do |file|
      file.write(json_hosts)
    end
  end


# なんか適切にエラーハンドリングしてください
rescue SNMP::RequestTimeout => e
  #
rescue StandardError => e
  puts e
  puts e.backtrace
ensure
  #
end

スクリプトの設定ファイル

前述のスクリプトに食わせる設定ファイルです。
別環境でスクリプトに手を加えず、設定ファイルだけ書き換えて使う…みたいなことを考えていたのですが…「標準 MIB の OID が変わるっていうのか?」なんて突っ込みは不可。

setting_discovery_nwdevs.yml

nmap_params :
  # nmap の -iL オプションで渡す discovery 対象を記載したファイル
  scan_target_files :
    targetfile : './target_nw.txt'
  # nmap の -PU -p オプションで渡す UDP ポートスキャン対象
  scan_udp_ports :
    - port : 161
  # nmap の -oX オプションで渡す、Host Discovery の output である xml ファイル
  xml_file : './scan.xml'
  # スクリプトの最終 output である JSON ファイルを生成するディレクトリ(nmap に渡すオプションではないので、nmap_params 配下にいるのは違和感ありますね…)
  output_directory : './'
snmp_params :
  # 動かす環境の NW 機器に設定してある SNMP community
  # 同じ環境の community ならきっと統一されているだろう、という前提のもと…
  community       : 'KOTETSU_NW'
  # SNMP Get 対象の OID たち
  oid_sysname     : '1.3.6.1.2.1.1.5.0'
  oid_sysdesc     : '1.3.6.1.2.1.1.1.0'
  oid_syslocation : '1.3.6.1.2.1.1.6.0'

Nmap input file

Nmap で Host Discovery する対象を羅列したファイル。
ruby-nmap は内部的に nmap に -iL オプションで食わせているだけなので、記法は Target Specification | Nmap 公式 を参照。

target_nw.txt

192.168.101.0/24  # mgmt NW

Discovery 実行~出力確認

スクリプト実行

こんな感じで。
Nmap でポートスキャンするには基本的には root 権限が必要なので、sudo しています。(以下公式の参考資料)

Discoveryスクリプト実行

$ sudo -E bundle exec ruby discovery_nwdevs.rb setting_discovery_nwdevs.yml

Starting Nmap 7.00 ( https://nmap.org ) at 2015-12-05 14:11 JST
Initiating ARP Ping Scan at 14:11
Scanning 255 hosts [1 port/host]
Completed ARP Ping Scan at 14:11, 1.64s elapsed (255 total hosts)

Initiating Parallel DNS resolution of 255 hosts. at 14:11
Completed Parallel DNS resolution of 255 hosts. at 14:11, 0.04s elapsed

(略)

Read data files from: /usr/local/bin/../share/nmap
Nmap done: 256 IP addresses (5 hosts up) scanned in 2.14 seconds
           Raw packets sent: 513 (14.784KB) | Rcvd: 11 (836B)

生成ファイル確認

生成されたファイル

Nmap が生成した scan.xml と、スクリプトが生成した list_nwdev.jsonroot:root で吐き出されています。

$ ls -al
total 60
drwxrwxr-x 2 kotetsu kotetsu  4096 Dec  6 14:11 .
drwxrwxr-x 5 kotetsu kotetsu  4096 Dec  6 14:08 ..
-rw-r--r-- 1 kotetsu kotetsu  3129 Dec  6 14:04 discovery_nwdevs.rb
-rw-r--r-- 1 root    root      643 Dec  6 14:11 list_nwdev.json
-rw-r--r-- 1 root    root    34690 Dec  6 14:11 scan.xml
-rw-r--r-- 1 kotetsu kotetsu   281 Dec  6 14:01 setting_discovery_nwdevs.yml
-rw-rw-r-- 1 kotetsu kotetsu    28 Dec  6 11:07 target_nw.txt

Nmap が生成した xml ファイル

scan.xml の中身を軽く見ていきます。(この辺は、Nmap を使っている人ならよく知っているのでは)

  • 最初のほうで、実際に ruby-nmap が実行したコマンドオプションや Nmap の実行バージョン、実行時刻などが分かる
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE nmaprun>
<?xml-stylesheet href="file:///usr/local/bin/../share/nmap/nmap.xsl" type="text/xsl"?>
<!-- Nmap 7.00 scan initiated Wed Dec  6 14:11:37 2015 as: /usr/local/bin/nmap -sU -oX ./scan.xml -v -p 161 -iL ./target_nw.txt -->
<nmaprun scanner="nmap" args="/usr/local/bin/nmap -sU -oX ./scan.xml -v -p 161 -iL ./target_nw.txt" start="1449637897" startstr="Sat Dec  6 14:11:37 2015" version="7.00" xmloutputversion="1.04">
<scaninfo type="udp" protocol="udp" numservices="1" services="161"/>
<verbose level="1"/>
<debugging level="0"/>
<taskbegin task="ARP Ping Scan" time="1449378697"/>
<taskend task="ARP Ping Scan" time="1449378699" extrainfo="255 total hosts"/>
<taskbegin task="Parallel DNS resolution of 255 hosts." time="1449378699"/>
<taskend task="Parallel DNS resolution of 255 hosts." time="1449378699"/>
  • Down しているホストの情報が続く
<host><status state="down" reason="no-response" reason_ttl="0"/>
<address addr="192.168.101.3" addrtype="ipv4"/>
</host>
<host><status state="down" reason="no-response" reason_ttl="0"/>
<address addr="192.168.101.4" addrtype="ipv4"/>
</host>
  • Up しているホストの情報が続く
<host starttime="1449378697" endtime="1449378699"><status state="up" reason="arp-response" reason_ttl="0"/>
<address addr="192.168.101.30" addrtype="ipv4"/>
<address addr="52:54:00:76:24:2C" addrtype="mac" vendor="QEMU virtual NIC"/>
<hostnames>
<hostname name="vsrx01" type="PTR"/>
</hostnames>
<ports><port protocol="udp" portid="161"><state state="open" reason="udp-response" reason_ttl="64"/><service name="snmp" method="table" conf="3"/></port>
</ports>
<times srtt="24054" rttvar="34787" to="163202"/>
</host>

スクリプトが生成した JSON ファイル

  • Nmap が名前解決出来たもの(以下例では 192.168.101.30 のみ名前登録してあった)は xmlhostnameptr に格納
  • sysnamesysdescsyslocationSNMP Get で取得した情報
  • Nmap で UDP 161 が Open していないと判断した 192.168.101.172 に関しては、ほとんど情報は取得できていない
    • まあ、その IP アドレスが何らかの機器に使われている、ということくらい

list_nwdev.json

[
  {
    "ipaddr": "192.168.101.30",
    "ptr": "vsrx01",
    "sysname": "vsrx01",
    "sysdesc": "Juniper Networks, Inc. vsrx internet router, kernel JUNOS 15.1X49-D15.4, Build date: 2015-07-31 03:30:01 UTC Copyright (c) 1996-2015 Juniper Networks, Inc.",
    "syslocation": "KotetsuNoteVB"
  },
  {
    "ipaddr": "192.168.101.50",
    "ptr": null,
    "sysname": "vEOS-spine001",
    "sysdesc": "Arista Networks EOS version 4.14.8M running on an Arista Networks vEOS",
    "syslocation": "KotetsuNoteVB"
  },
  {
    "ipaddr": "192.168.101.52",
    "ptr": null,
    "sysname": "vEOS-leaf001",
    "sysdesc": "Arista Networks EOS version 4.14.8M running on an Arista Networks vEOS",
    "syslocation": "KotetsuNoteVB"
  },
  {
    "ipaddr": "192.168.101.71",
    "ptr": null,
    "sysname": "vtep-vyos01",
    "sysdesc": "Vyatta VyOS 1.1.1",
    "syslocation": "KotetsuNoteVB"
  },
  {
    "ipaddr": "192.168.101.172",
    "ptr": null,
    "sysname": "",
    "sysdesc": "",
    "syslocation": ""
  }
]

連携例 ~Ansible の Dynamic Inventory~

やること

先に生成した JSON ファイルを「構成情報マスタ」とみなして連携する例として Ansible の Dynamic Inventory で使ってみます。
NW 機器の中から Arista だけを抜き出して、Arista 用の Role を使った Playbook を実行するです。

環境

横着して、昔(2014/12)作った以下環境を流用して Arista をターゲットに使います。

2015/12 現在だと、Ansible は 2.1.0 とかまで出ているし、ansible-eos (Arista 用の Role)もかなり更新が入っていますが…。

Dynamic Inventory サンプルスクリプト

先に作った JSON ファイルをパースして、最低限動くだけのサンプルです。   要するに sysDescr で機種を仕分けているだけ。

dynamic_nwdevs.rb

#! /usr/bin/env ruby
# encoding: utf-8

require 'json'

FILE_INVENTORY = '/home/kotetsu/discovery/scripts/list_nwdev.json'


begin
  
  if (ARGV[0] && ARGV[0] == '--list') then
    ret = {}
    
    list_junos = []
    list_arista = []
    list_vyos = []

    JSON.load(File.open(FILE_INVENTORY).read).each do |nwdev|
      case nwdev["sysdesc"]
      when /^Juniper.*JUNOS.*/
        list_junos << nwdev["ipaddr"]
      when /^Arista Networks EOS.*/
        list_arista << nwdev["ipaddr"]
      when /^Vyatta VyOS/
        list_vyos << nwdev["ipaddr"]
      else
        #
      end
    end

    ret["junos_all"] = list_junos.dup unless list_junos.size == 0
    ret["arista_all"] = list_arista.dup unless list_arista.size == 0
    ret["vyos_all"] = list_vyos.dup unless list_vyos.size == 0
    puts JSON.pretty_unparse(ret)

  elsif (ARGV[1] && ARGV[0] == "--host") then
    JSON.load(File.open(FILE_INVENTORY).read).each do |nwdev|
      if (
        nwdev["sysname"] =~ Regexp.new(ARGV[1]) ||
        nwdev["ipaddr"] =~ Regexp.new(ARGV[1]) ||
        nwdev["ptr"] =~ Regexp.new(ARGV[1])
      ) then
        puts JSON.pretty_unparse(nwdev)
      end
    end
  end


rescue Exception => e
  puts e
  puts e.backtrace
end

公式のサンプルスクリプトでは Python が圧倒的に多いですが、よくある「所定の引数をつけて実行した時に、所定のOutputを返せば良い」系のやつなので、言語は何で書いても良いです。(この程度ならシェルスクリプトでも)

で、これを実行すると以下の感じ。

$ ./dynamic_nwdevs.rb --list
{
  "junos_all": [
    "192.168.101.30"
  ],
  "arista_all": [
    "192.168.101.50",
    "192.168.101.52"
  ],
  "vyos_all": [
    "192.168.101.71"
  ]
}

$ ./dynamic_nwdevs.rb --host vEOS-spine001
{
  "ipaddr": "192.168.101.50",
  "ptr": null,
  "sysname": "vEOS-spine001",
  "sysdesc": "Arista Networks EOS version 4.14.8M running on an Arista Networks vEOS",
  "syslocation": "KotetsuNoteVB"
}

_meta を出さないので、無駄な処理が走るのですがね。まあローカルのファイル読んでいるテスト環境なので…(モゴモゴ)
その辺は Ansible meetuptokyo 2015 Dynamic Inventory | SlideShare のスライド 10~12 を見ましょう。

Arista 用のサンプル Playbook

  • role で Arista 公式の Role を呼んでいる完全に Arista 専用の Playbook
  • hosts では先の ./dynamic_nwdevs.rb --list で出力された arista_all という Arista 全台グループを指定
  • show version して stdout するだけ

playbook_sample_arista.yml

- name: eos nodes
  hosts: arista_all
  gather_facts: no
  sudo: true

  vars:
    eapi_username: kotetsu
    eapi_password: kotetsu
    eapi_protocol: http

  roles:
    - role: arista.eos

  tasks:
    - name: show version
      action: eos_command
      args: {
         commands: [
           "show version"
         ],
         eapi_username: "{{ eapi_username }}",
         eapi_password: "{{ eapi_password }}",
         eapi_protocol: "{{ eapi_protocol }}"
      }
      register: output_version

    - debug: var=output_version

Playbook 実行

レッツゴー
(-i で呼んでいるのが hosts ファイルではなくて、先の dynamic_nwdevs.rb なのがポイント)

DynamicInventoryでPlaybook実行

$ ansible-playbook playbook_sample_arista.yml -f 10 -u ansible -i dynamic_nwdevs.rb

PLAY [eos nodes] **************************************************************

TASK: [arista.eos | check if running on eos node] *****************************
ok: [192.168.101.50]
ok: [192.168.101.52]

TASK: [arista.eos | collect eos facts] ****************************************
ok: [192.168.101.50]
ok: [192.168.101.52]

TASK: [arista.eos | include eos variables] ************************************
ok: [192.168.101.50]
ok: [192.168.101.52]

TASK: [arista.eos | check for working directory] ******************************
ok: [192.168.101.52]
ok: [192.168.101.50]

TASK: [arista.eos | create source] ********************************************
skipping: [192.168.101.52]
skipping: [192.168.101.50]

TASK: [arista.eos | check if pip is installed] ********************************
ok: [192.168.101.50]
ok: [192.168.101.52]

TASK: [arista.eos | copy pip extension to node] *******************************
skipping: [192.168.101.50]
skipping: [192.168.101.52]

TASK: [arista.eos | create tmp config file to load pip] ***********************
skipping: [192.168.101.50]
skipping: [192.168.101.52]

TASK: [arista.eos | load pip eos extension] ***********************************
skipping: [192.168.101.52]
skipping: [192.168.101.50]

TASK: [arista.eos | copy required libraries to node] **************************
ok: [192.168.101.50] => (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.50] => (item=eapilib-0.1.0.tar.gz)
ok: [192.168.101.52] => (item=eapilib-0.1.0.tar.gz)

TASK: [arista.eos | install jsonrpclib] ***************************************
skipping: [192.168.101.50]
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)

TASK: [show version] **********************************************************
ok: [192.168.101.52]
ok: [192.168.101.50]

TASK: [debug var=output_version] **********************************************
ok: [192.168.101.50] => {
    "output_version": {
        "changed": false,
        "invocation": {
            "module_args": "",
            "module_name": "eos_command"
        },
        "output": [
            {
                "command": "show version",
                "response": {
                    "architecture": "i386",
                    "bootupTimestamp": 1449902847.6,
                    "hardwareRevision": "",
                    "internalBuildId": "a6bbeeb3-95b7-42bc-9721-266f9bff424e",
                    "internalVersion": "4.14.8M-2475814.4148M",
                    "memFree": 20516,
                    "memTotal": 996140,
                    "modelName": "vEOS",
                    "serialNumber": "",
                    "systemMacAddress": "08:00:27:31:60:65",
                    "version": "4.14.8M"
                }
            }
        ]
    }
}
ok: [192.168.101.52] => {
    "output_version": {
        "changed": false,
        "invocation": {
            "module_args": "",
            "module_name": "eos_command"
        },
        "output": [
            {
                "command": "show version",
                "response": {
                    "architecture": "i386",
                    "bootupTimestamp": 1449902847.71,
                    "hardwareRevision": "",
                    "internalBuildId": "a6bbeeb3-95b7-42bc-9721-266f9bff424e",
                    "internalVersion": "4.14.8M-2475814.4148M",
                    "memFree": 46872,
                    "memTotal": 996140,
                    "modelName": "vEOS",
                    "serialNumber": "",
                    "systemMacAddress": "08:00:27:e2:d0:f4",
                    "version": "4.14.8M"
                }
            }
        ]
    }
}

PLAY RECAP ********************************************************************
192.168.101.50             : ok=10   changed=0    unreachable=0    failed=0
192.168.101.52             : ok=10   changed=0    unreachable=0    failed=0

Arista だけに処理が走りました。

おしまい

plus one

今回は Discovery との連携例として Ansible の Dynamic Inventory を使いましたが、Discovery は Nmap/SNMP だけの超単純な仕組みなので、他の仕組みとの連携も面倒なくいけるんですよな。
例えば、こんなのはよくやっているのではないでしょうか。

  • ここで生成した JSON ファイルをロードして
    • sysDescrcase で分けて、機種に応じた処理
      • 詳細な inventory 情報取得(シリアル番号とかラインカード/SFP構成とか)
        • expect で show inventory とか netconf の <get-inventory> とか SNMP Get とか独自 API とか
          • 必要に応じて事前にポートスキャンで TCP/830 とか TCP/22 もしておくとか
          • たまにシリアル番号を CLI でしか取得できないポンコツ箱があるんだよなぁ…
      • config 取得 (後述の rancid 方式でもよい)
        • discovery ~ config 取得 ~ バージョン管理コマンドも cron で回しておくと、増えた機器が勝手にバックアップ・バージョン管理される
      • 各種ミドルウェアの設定ファイル生成(サンプルスクリプトのアウトプットを JSON でなく、設定ファイル形式にするでも)
        • rancid
        • Ansible
          • hosts ファイル
          • Dynamic Inventory と連携
            • 本項で軽くやったやつ
        • 監視ソフト(auto discovery 的な機能がない)
          • 機種に応じた自前の plugin を指定して…とかも自動でやれる
      • 特定バージョンを対象とした
        • OS ファイル転送
        • 設定撒き
    • ptr が空で sysname を取得できたものに関して、内部 DNS コンテンツの設定生成(~追加)
    • sysDescsysname の組み合わせで条件付けて、待機系機器のみ云々
  • JSON ファイルに書き出した情報を SQL に放り込んで、WebUI なり API なりを提供
    • 前述の内容を実現するのに、踏み台サーバや監視サーバからも inventory 情報を取得したい、って思ったり
      • 別に rsync とかで撒いてもよいが
    • 実機から取得できる情報には限界があるし、人間が入力・更新したい情報もあるのでは(商用環境だと保守期限とか)
    • ありもの製品の WebUI は実運用要件を充たすようにカスタマイズできないから、自前で簡易で運用要件を充たすものを立てたほうが楽、とか
      • そういう思いから自前で作り込まれた社内システムとかには、文句たらたらで「ありもののパッケージ使えよ」とか言うのにね

所感

  • まあ、なんというか「何をマスタ情報にして、他コンポーネントとどういう連携をするか」は環境次第なので、こんな方法もあるよ、ってだけですな
  • 超メジャーな Nmap を使った Inventory の生成、なんてのは割とポピュラーな手法で、以下のようなものを使えば同じこと+アルファが出来ます(いずれも内部的には Nmap を使っている模様)
    • Open-AudIT
      • JSON, xlsx, pdf 形式などで情報をエクスポートできるらしい
      • 本項の Discovery レベルの話は全部できそう
    • ローレベルディスカバリ | Zabbix 2.2
      • SNMP 連携できるディスカバリって点では、物理 NW 機器を一元管理する場合には、これがマッチする気がする
  • ただまあ、もっと手軽に…とか、アウトプットの書式を任意に作りたい(他のミドルウェアで使う設定ファイル生成とか)とかのケースでは、使えるのではないでしょうかね
    • 生で Nmap 叩いて xml をサクッとパースすればライブラリさえ使う必要ないのですが…
  • Nmap 7.0 の新機能を一切触ってないじゃねぇか!

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

Arista Warrior

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 の詳細は説明しません & 設定内容もベストプラクティスではないです。

f:id:kakkotetsu:20170513210542j:plain

ruby 環境は nwman というノードで、Ansible 環境は ansible というノードで動かしますが、分けているのは単に元々あった環境の都合です。

ホスト環境

ruby 環境

  • OS:CentOS release 6.5
  • rubyruby 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 環境

  • OS:Ubuntu-14.04.01-server-amd64
  • ansible:1.8.2
  • ansible-eos:v0.1.2

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 を作る」という操作をします。つまり

  1. 対象機の VLAN 状態を取得して「VLAN 30 が存在するか」・「存在する場合、VLAN 30 をどこの物理 IF に trunk で通しているか」を判断
  2. 1 で VLAN 30 が存在する場合、VLAN 31 を作る
  3. 1 で VLAN 30 が存在する場合、VLAN 30 を通している全物理 IF に VLAN 31 を追加で通す
  4. 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 の仕様が見られます。設定系はなかったです。

f:id:kakkotetsu:20170513212924j:plain

“Explorer” ではリクエストのパラメータを少し入力するだけで簡易に試せます。(ブラウザの RestClient アドオン的な)

f:id:kakkotetsu:20170513212956j:plain

“Overview” ではエラーコードの説明などが見られます。スタートアップガイド的な。

f:id:kakkotetsu:20170513213009j:plain

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 だ (きっぱり

Arista の Zero Touch Provisioning (ZTP) を試す (Dynamic Provisioning 編) (original : 2014/11/29)

この記事は某所で 2014/11/29 に書いたもののコピーです。
そのため 2017/05/13 時点ではやや古い情報も含まれています。

概要

本項でやること

ZTP を使って、Arista vEOS x2台の初期設定投入をしてみます。
対象機器(Arista)の System MAC アドレスや シリアル番号を事前登録する必要なく、クライアントの LLDP 状態で条件付けして初期設定の動的生成やパラメータ払い出し管理をできるよ、ってところを見ます。

  • 方式は Dynamic Provisioning で、対象 vEOS 識別に ztpserver の仕組み(neighbordb)を使う
  • 設定ファイルは ztpserver の仕組み(bootstrap と template と definition)を使って動的に生成
  • パラメータの払い出しは ztpserver の仕組み(Resource pools)を使う

Static Provisioning(System MAC アドレスやシリアル番号を事前登録する方式)は Arista の Zero Touch Provisioning (ZTP) を試す (Static Provisioning 編) を参照。

Arista EOS 自体については、以下の本をオススメしときますね。

Arista Warrior: A Real-World Guide to Understanding Arista Switches and EOS

Arista Warrior: A Real-World Guide to Understanding Arista Switches and EOS

Arista Warrior

Arista Warrior

Resource pools とは?

公式の説明 を読むのが手っ取り早いです。
ノード単位で付与するパラメータをプール化しておき、ZTP 実行時に ztpserver が勝手に空いているところを使って利用済みにしてくれる、簡単なリソース管理までやってくれる方式です。

結果概要

ztpserver v1.1.0 で試行した時点では、課題が残りました。いまいちスッキリしてません。詳細は後述します。

  • クライアント(vEOS)識別に neighbordb で LLDP 状態マッチングは便利だけど、Arista 側の機能制約起因で実用に少し工夫が必要
  • パラメータ払い出しの Resource pools 動作は微妙なので、現時点では実用に結構な工夫が必要

構成図・環境情報

以下の構成でやります。
物理構成は一般的なデータセンタネットワークのそれで、Arista の Multi-Chassis Link Aggregation (MLAG)を使っています。
本項では MLAG の詳細は説明しません & 設定内容もベストプラクティスではないです。

f:id:kakkotetsu:20170513164238j:plain

  • ztpserver と Arisra vEOS x2 台(spine001/002)はセットアップ済
  • Arista vEOS x2 台(leaf001/002)は VirtualBox に突っ込んだだけで何も設定していない

本項の環境は以下の通りです。vEOS は 4 台とも同じスペック。

  • VirtualBox と ztpserver v1.1.0 は これ
  • vEOS Aboot-veos:Aboot-veos-2.1.0
  • vEOS OS:vEOS-4.14.2F
  • vEOS CPU:1core
  • vEOS Memory:1024MB

vEOS のネットワークアダプタは、構成図の通りです。vEOS 同士の接続は、全部 VirtualBox の intnet で。

事前準備

この辺は軽く…。

ztpserver のインストール~初期設定

これで。

vEOS x4 (spine001/002, leaf001/002)のデプロイ

以下リンクを参考に作っておきます。公式に、各種ハイパーバイザごとの導入手順詳細とかもあります。

vEOS(spine001/002) の設定

必要に応じてネットワークアダプタの追加

VirtualBox の場合は、本構成のように5つ目以降のネットワークアダプタGUI でできないかも(調べてませんが、バージョン次第?)なので、コマンドラインで足します。

VBoxManage modifyvm vEOS-spine001 --nic5 intnet
VBoxManage modifyvm vEOS-spine001 --nicpromisc5 allow-all
VBoxManage modifyvm vEOS-spine001 --intnet5 intnet_ar_02
VBoxManage showvminfo "vEOS-spine001"

VBoxManage modifyvm vEOS-spine002 --nic5 intnet
VBoxManage modifyvm vEOS-spine002 --nicpromisc5 allow-all
VBoxManage modifyvm vEOS-spine002 --intnet5 intnet_ar_02
VBoxManage showvminfo "vEOS-spine002"

設定

設定を晒しておきます。
Arista の操作詳細は以下を参考に。MC-LAG の設定内容もマニュアルのサンプルコンフィグほぼそのままです。

spine001#show run
! 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
!
vlan 4094
!
interface Port-Channel1
   description DEV=leaf001 IF=Po1
   mlag 1
!
interface Port-Channel2
   description DEV=leaf002 IF=Po1
   mlag 2
!
interface Port-Channel100
   description DEV=spine002 IF=Po100
   switchport trunk allowed vlan 4094
   switchport mode trunk
!
interface Ethernet1
   description DEV=leaf001 IF=Eth1
!
interface Ethernet2
   description DEV=leaf002 IF=Eth1
!
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 shutdown
!
!
end
  • spine002
spine002#show run
! 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.
!
vlan 4094
!
interface Port-Channel1
   description DEV=leaf001 IF=Po1
   mlag 1
!
interface Port-Channel2
   description DEV=leaf002 IF=Po1
   mlag 2
!
interface Port-Channel100
   description DEV=spine001 IF=Po100
   switchport trunk allowed vlan 4094
   switchport mode trunk
!
interface Ethernet1
   description DEV=leaf001 IF=Eth2
!
interface Ethernet2
   description DEV=leaf002 IF=Eth2
!
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 shutdown
!
!
end

それぞれ、interface Ethernet1-2 設定内に channel-group 設定を入れていません。
これは Dynamic Provisioning で leaf001,002 と LLDP 情報の交換をするために、以下の Arista 仕様を回避するためです。
「Po で LLDP 関係の設定は出来ないから、Eth の方でやってね」って意味だと思っていたのですが、実際に試した限りは Port-Channel 組んでいる物理 IF では LLDP 交換できませんでした。

12.2 LLDP Overview 12.2.4 Guidelines and Limitations

・LLDP is supported only on physical interfaces.

vEOS (leaf001,002) のネットワーク接続

ZTP 実行対象となる 2 台のネットワークアダプタ設定は、事前に VirtualBox でやっておきます。
「配線して電源 ON するだけで初期設定されるよ!」的な ZTP の謳い文句で言うところの配線にあたります。
まあ、仮想環境なんですけどね。

そんなわけで、内部的には仮想スイッチ的なものが挟まるので vEOS 間の linkUp/Down 挙動を単純に再現することは難しいです。

ztpserver 設定手順

基本的に 公式の Configuration公式の Examples を参照しつつ、必要に応じて ztpserver の source を参照して進めていきます。

neigbordb ファイルの編集 (LLDP 条件と実行処理の紐付け)

Dynamic Provisioning では、ztpserver がクライアントから受け取った LLDP 情報に合わせた処理を実行します。
ここでは、その条件と処理へのリンクの紐付けを /usr/share/ztpserver/neighbordb に定義します。

patterns:
  - name: leaf_dynamic
    definition: def_leaf
    interfaces:
      - Ethernet1: spine001:any
      - Ethernet2: spine002:any

name: は、本ファイル内で一意であれば何でも良い筈です。
definition: は、この後作成する definition のファイル名を記載します。マッチングしたときの処理です。
interfaces: は、マッチング条件です。leaf の場合、Ethernet1 を spine001 に、Ethernet2 を spine002 に接続するという設計にして、leaf がいくら増えてもこの定義が適用されるように spine 側のポートは any 指定しています。

definition ファイルの作成

Static Provisioning の時とほぼ同様ですが、今回は node 単位ではなくて前述の条件単位なので /usr/share/ztpserver/definitions/ 配下に作ります。ファイル名は neighbordb 内で指定した def_leaf で。

/usr/share/ztpserver/definitions/def_leaf

---
name: def_leaf
actions:
  -
    action: add_config
    attributes:
      url: files/templates/ma1.template
      variables:
        ipaddress: allocate('mgmt_subnet')
    name: "configure ma1"
    onstart: "Starting to configure ma1"
    onsuccess: "SUCCESS: ma1 configure"
  -
    action: add_config
    attributes:
      url: files/templates/system.template
      variables:
        hostname: allocate('leaf_hostname')
    name: "configure global system"
    onstart: "Starting to add basic system config"
    onsuccess: "SUCCESS: basic config added"
  -
    action: add_config
    attributes:
      url: files/templates/login.template
    name: "configure auth"
  -
    action: add_config
    attributes:
      url: files/templates/ztpprep.template
    name: "configure ztpprep alias"
  -
    action: add_config
    attributes:
      url: files/templates/uplink.template
      variables:
        spine_downlink: allocate('spine_downlink')
    name: "configure uplink"
    onstart: "Starting to configure uplink"
    onsuccess: "SUCCESS: uplink configured"
  -
    action: copy_file
    always_execute: true
    attributes:
      dst_url: /mnt/flash/
      mode: 777
      overwrite: if-missing
      src_url: files/automate/ztpprep
    name: "automate reload"

variables: で各 template ファイルに定義されている変数を Resource pools から払い出されるように allocate('<Resource pools ファイル名>') で書いています。
この後定義する Resource pools のファイル名を指定する必要があります。

各種 template ファイル作成

definition ファイル内で呼び出している各種テンプレートや bash スクリプトを作っておきます。
これは Static Provisioning の時 と同様です。Static/Dynamic の方式問わず共用できます。

  • /usr/share/ztpserver/files/automate/ztpprep
#!/bin/bash
#delete system files and reload the system for ztp
rm -rf /mnt/flash/startup-config
rm -rf /mnt/flash/*extensions*
shutdown -r now
  • /usr/share/ztpserver/files/templates/ma1.template
interface Management1
  ip address $ipaddress
  no shutdown
  • /usr/share/ztpserver/files/templates/system.template
hostname $hostname
!
!
management api http-commands
   no shutdown
!
lldp timer 5
!
  • /usr/share/ztpserver/files/templates/system.template

username も password も kotetsu です。admin ユーザはデフォルトであります。

username kotetsu secret 5 $1$HDrZK8m8$A13QQaIqjLdrvik2.3cm9.
  • /usr/share/ztpserver/files/templates/ztpprep.template
alias ztpprep bash sudo /mnt/flash/ztpprep
  • /usr/share/ztpserver/files/templates/system.template

これは spine001,002 と接続する Uplink の設定です。

interface Ethernet1
   description DEV=spine001 IF=Eth$spine_downlink
   channel-group 1 mode active
!
interface Ethernet2
   description DEV=spine002 IF=Eth$spine_downlink
   channel-group 1 mode active
!
interface Port-Channel1
   description DEV=spine001_002 IF=mlag$spine_downlink

# 最低限の設定を入れたつもりだったが timezone 位は入れておけばよかった…。

Resource pools ファイル作成 (払い出すパラメータ定義)

  • /usr/share/ztpserver/resources/mgmt_subnet
192.168.101.52/24: null
192.168.101.53/24: null
192.168.101.54/24: null
192.168.101.55/24: null
  • /usr/share/ztpserver/resources/leaf_hostname
leaf001: null
leaf002: null
leaf003: null
leaf004: null
  • /usr/share/ztpserver/resources/spine_downlink
1: null
2: null
3: null
4: null

ZTP 動作確認

ztpserver 起動

Dynamic Provisioning でも /usr/share/ztpserver/ 配下にノード単位のディレクトリが生成されるので、せやかて sudo します。

$ sudo ztps --debug
INFO: [app:115] Logging started for ztpserver
INFO: [app:116] Using repository /usr/share/ztpserver
DEBUG: [controller:776] server URL: http://192.168.101.16:8080
Starting server on http://192.168.101.16:8080

vEOS 再起動

leaf001,002 のどちらからでもいいですが。

startup-config を消して再起動します。いつものように reload 実行時に色々聞かれるので Save するかは no を、Confirm は そのまま Enter で対応します。

# write erase
# reload

VirtualBox のコンソールで vEOS 停止~起動を見守ります。ログインプロンプトが出たら、ZTP がはじまります。

ZTP 実行

vEOS 起動後、「startup-config が無いから ZTP はじめるよ」的なメッセージが出て ZTP シーケンスが開始します。画面的には Static Provisioning の時 と代わり映えしないので省略。

ztpserver 側のログを見ると以下の感じ(debug レベルにしているので長い…)

192.168.101.62 - - [27/Nov/2014 01:11:25] "GET /bootstrap HTTP/1.1" 200 35386
192.168.101.62 - - [27/Nov/2014 01:11:27] "GET /bootstrap/config HTTP/1.1" 200 200
DEBUG: [controller:252] POST /nodes HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 339
Content-Type: application/json
Host: 192.168.101.16:8080
User-Agent: python-requests/1.2.3 CPython/2.7.0 Linux/3.4.43.Ar-2083164.4142F

{"neighbors": {"Ethernet2": [{"device": "spine002", "port": "Ethernet1"}], "Management1": [{"device": "spine002", "port": "Management1"}, {"device": "spine001", "port": "Management1"}], "Ethernet1": [{"device": "spine001", "port": "Ethernet1"}]}, "version": "4.14.2F", "systemmac": "08:00:27:e2:d0:f4", "model": "vEOS", "serialnumber": ""}

DEBUG: [topology:278] 080027e2d0f4: creating neighbor spine002:Ethernet1 for interface Ethernet2
DEBUG: [topology:278] 080027e2d0f4: creating neighbor spine002:Management1 for interface Management1
DEBUG: [topology:278] 080027e2d0f4: creating neighbor spine001:Management1 for interface Management1
DEBUG: [topology:278] 080027e2d0f4: creating neighbor spine001:Ethernet1 for interface Ethernet1
DEBUG: [topology:140] 080027e2d0f4: created node object Node(serialnumber=, systemmac=080027e2d0f4, neighbors=OrderedCollection([(u'Ethernet2', [Neighbor(device=u'spine002', interface=u'Ethernet1')]), (u'Management1', [Neighbor(device=u'spine002', interface=u'Management1'), Neighbor(device=u'spine001', interface=u'Management1')]), (u'Ethernet1', [Neighbor(device=u'spine001', interface=u'Ethernet1')])]))
DEBUG: [controller:170] 080027e2d0f4: running node_exists
DEBUG: [controller:170] 080027e2d0f4: running post_config
DEBUG: [controller:170] 080027e2d0f4: running post_node
DEBUG: [validators:80] 080027e2d0f4: running NeighbordbValidator.validate
DEBUG: [validators:96] 080027e2d0f4: running NeighbordbValidator.validate_patterns 
DEBUG: [validators:80] 080027e2d0f4: running PatternValidator.validate
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_attributes for 'leaf_dynamic'
WARNING: [validators:171] 080027e2d0f4: PatternValidator warning: 'leaf_dynamic' is missing optional attribute (node)
WARNING: [validators:171] 080027e2d0f4: PatternValidator warning: 'leaf_dynamic' is missing optional attribute (variables)
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_definition for 'leaf_dynamic'
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_interfaces for 'leaf_dynamic'
DEBUG: [validators:80] 080027e2d0f4: running InterfacePatternValidator.validate
DEBUG: [validators:96] 080027e2d0f4: running InterfacePatternValidator.validate_interface_pattern 
DEBUG: [validators:204] 080027e2d0f4: adding interface pattern '{'Ethernet1': 'spine001:any'}' to valid interface patterns
DEBUG: [validators:80] 080027e2d0f4: running InterfacePatternValidator.validate
DEBUG: [validators:96] 080027e2d0f4: running InterfacePatternValidator.validate_interface_pattern 
DEBUG: [validators:204] 080027e2d0f4: adding interface pattern '{'Ethernet2': 'spine002:any'}' to valid interface patterns
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_name for 'leaf_dynamic'
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_node for 'leaf_dynamic'
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_variables for 'leaf_dynamic'
DEBUG: [validators:140] 080027e2d0f4: adding pattern 'leaf_dynamic' ({'definition': 'def_leaf', 'interfaces': [{'Ethernet1': 'spine001:any'}, {'Ethernet2': 'spine002:any'}], 'name': 'leaf_dynamic'}) to valid patterns
DEBUG: [validators:96] 080027e2d0f4: running NeighbordbValidator.validate_variables 
DEBUG: [validators:315] 080027e2d0f4: NeighbordbValidator validation successful
DEBUG: [topology:472] 080027e2d0f4: checking pattern 'leaf_dynamic' entries for variable substitution
DEBUG: [topology:482] 080027e2d0f4: pattern 'leaf_dynamic' variable substitution complete
DEBUG: [topology:369] 080027e2d0f4: pattern 'Pattern(name='leaf_dynamic')' parsed successfully
DEBUG: [topology:108] 080027e2d0f4: loaded neighbordb: Neighbordb(variables=0, globals=1, nodes=0)
DEBUG: [topology:417] 080027e2d0f4: searching for eligible patterns
DEBUG: [topology:426] 080027e2d0f4: all global patterns are eligible
DEBUG: [topology:437] 080027e2d0f4: attempting to match pattern leaf_dynamic
DEBUG: [topology:559] 080027e2d0f4: pattern 'leaf_dynamic' - attempting to match node ("Node(serialnumber=, systemmac=080027e2d0f4, neighbors=OrderedCollection([(u'Ethernet2', [Neighbor(device=u'spine002', interface=u'Ethernet1')]), (u'Management1', [Neighbor(device=u'spine002', interface=u'Management1'), Neighbor(device=u'spine001', interface=u'Management1')]), (u'Ethernet1', [Neighbor(device=u'spine001', interface=u'Ethernet1')])]))")
DEBUG: [topology:569] 080027e2d0f4: pattern 'leaf_dynamic' - attempting to match interface Ethernet2([Neighbor(device=u'spine002', interface=u'Ethernet1')])
DEBUG: [topology:575] 080027e2d0f4: pattern 'leaf_dynamic' - checking interface pattern for Ethernet2: InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:675] 080027e2d0f4: attempting to match Ethernet2(Neighbor(device=u'spine002', interface=u'Ethernet1')) against interface pattern InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:575] 080027e2d0f4: pattern 'leaf_dynamic' - checking interface pattern for Ethernet2: InterfacePattern(interface=Ethernet2, remote_device=spine002, remote_interface=any)
DEBUG: [topology:675] 080027e2d0f4: attempting to match Ethernet2(Neighbor(device=u'spine002', interface=u'Ethernet1')) against interface pattern InterfacePattern(interface=Ethernet2, remote_device=spine002, remote_interface=any)
DEBUG: [topology:582] 080027e2d0f4: pattern 'leaf_dynamic' - interface pattern match for Ethernet2: InterfacePattern(interface=Ethernet2, remote_device=spine002, remote_interface=any)
DEBUG: [topology:569] 080027e2d0f4: pattern 'leaf_dynamic' - attempting to match interface Management1([Neighbor(device=u'spine002', interface=u'Management1'), Neighbor(device=u'spine001', interface=u'Management1')])
DEBUG: [topology:575] 080027e2d0f4: pattern 'leaf_dynamic' - checking interface pattern for Management1: InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:675] 080027e2d0f4: attempting to match Management1(Neighbor(device=u'spine002', interface=u'Management1')) against interface pattern InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:675] 080027e2d0f4: attempting to match Management1(Neighbor(device=u'spine001', interface=u'Management1')) against interface pattern InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:595] 080027e2d0f4: pattern 'leaf_dynamic' - interface Management1 did not match any interface patterns
DEBUG: [topology:569] 080027e2d0f4: pattern 'leaf_dynamic' - attempting to match interface Ethernet1([Neighbor(device=u'spine001', interface=u'Ethernet1')])
DEBUG: [topology:575] 080027e2d0f4: pattern 'leaf_dynamic' - checking interface pattern for Ethernet1: InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:675] 080027e2d0f4: attempting to match Ethernet1(Neighbor(device=u'spine001', interface=u'Ethernet1')) against interface pattern InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:582] 080027e2d0f4: pattern 'leaf_dynamic' - interface pattern match for Ethernet1: InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:440] 080027e2d0f4: pattern leaf_dynamic matched
DEBUG: [controller:373] 080027e2d0f4: 1 pattern(s) in neihgbordb are a good match
INFO: [controller:377] 080027e2d0f4: node matched 'leaf_dynamic' pattern in neighbordb
INFO: [controller:399] 080027e2d0f4: new /nodes/080027e2d0f4 folder created
DEBUG: [controller:170] 080027e2d0f4: running dump_node
DEBUG: [controller:170] 080027e2d0f4: running set_location
DEBUG: [controller:182] 080027e2d0f4: response to set_location: {'status': 201, 'location': 'nodes/080027e2d0f4'}
192.168.101.62 - - [27/Nov/2014 01:11:49] "POST /nodes HTTP/1.1" 201 0
DEBUG: [controller:470] GET /nodes/080027e2d0f4 HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 4
Content-Type: text/html
Host: 192.168.101.16:8080
User-Agent: python-requests/1.2.3 CPython/2.7.0 Linux/3.4.43.Ar-2083164.4142F
Resource: 080027e2d0f4

DEBUG: [topology:278] 080027e2d0f4: creating neighbor spine002:Ethernet1 for interface Ethernet2
DEBUG: [topology:278] 080027e2d0f4: creating neighbor spine002:Management1 for interface Management1
DEBUG: [topology:278] 080027e2d0f4: creating neighbor spine001:Management1 for interface Management1
DEBUG: [topology:278] 080027e2d0f4: creating neighbor spine001:Ethernet1 for interface Ethernet1
DEBUG: [topology:140] 080027e2d0f4: created node object Node(serialnumber=None, systemmac=080027e2d0f4, neighbors=OrderedCollection([('Ethernet2', [Neighbor(device='spine002', interface='Ethernet1')]), ('Management1', [Neighbor(device='spine002', interface='Management1'), Neighbor(device='spine001', interface='Management1')]), ('Ethernet1', [Neighbor(device='spine001', interface='Ethernet1')])]))
DEBUG: [controller:170] 080027e2d0f4: running get_definition
DEBUG: [controller:498] 080027e2d0f4: defintion is nodes/080027e2d0f4/definition ([{'action': 'add_config', 'attributes': {'url': 'files/templates/ma1.template', 'variables': {'ipaddress': "allocate('mgmt_subnet')"}}, 'name': 'configure ma1', 'onstart': 'Starting to configure ma1', 'onsuccess': 'SUCCESS: ma1 configure'}, {'action': 'add_config', 'attributes': {'url': 'files/templates/system.template', 'variables': {'hostname': "allocate('leaf_hostname')"}}, 'name': 'configure global system', 'onstart': 'Starting to add basic system config', 'onsuccess': 'SUCCESS: basic config added'}, {'action': 'add_config', 'attributes': {'url': 'files/templates/login.template'}, 'name': 'configure auth'}, {'action': 'add_config', 'attributes': {'url': 'files/templates/ztpprep.template'}, 'name': 'configure ztpprep alias'}, {'action': 'add_config', 'attributes': {'url': 'files/templates/uplink.template', 'variables': {'spine_downlink': "allocate('spine_downlink')"}}, 'name': 'configure uplink', 'onstart': 'Starting to configure uplink', 'onsuccess': 'SUCCESS: uplink configured'}, {'action': 'copy_file', 'always_execute': True, 'name': 'automate reload', 'attributes': {'dst_url': '/mnt/flash/', 'src_url': 'files/automate/ztpprep', 'mode': 777, 'overwrite': 'if-missing'}}])
DEBUG: [controller:170] 080027e2d0f4: running do_validation
DEBUG: [validators:80] 080027e2d0f4: running PatternValidator.validate
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_attributes for 'leaf_dynamic'
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_definition for 'leaf_dynamic'
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_interfaces for 'leaf_dynamic'
DEBUG: [validators:80] 080027e2d0f4: running InterfacePatternValidator.validate
DEBUG: [validators:96] 080027e2d0f4: running InterfacePatternValidator.validate_interface_pattern 
DEBUG: [validators:204] 080027e2d0f4: adding interface pattern '{'Ethernet1': 'spine001:any'}' to valid interface patterns
DEBUG: [validators:80] 080027e2d0f4: running InterfacePatternValidator.validate
DEBUG: [validators:96] 080027e2d0f4: running InterfacePatternValidator.validate_interface_pattern 
DEBUG: [validators:204] 080027e2d0f4: adding interface pattern '{'Ethernet2': 'spine002:any'}' to valid interface patterns
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_name for 'leaf_dynamic'
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_node for 'leaf_dynamic'
DEBUG: [validators:96] 080027e2d0f4: running PatternValidator.validate_variables for 'leaf_dynamic'
DEBUG: [validators:315] 080027e2d0f4: PatternValidator validation successful
DEBUG: [topology:472] 080027e2d0f4: checking pattern 'leaf_dynamic' entries for variable substitution
DEBUG: [topology:482] 080027e2d0f4: pattern 'leaf_dynamic' variable substitution complete
DEBUG: [topology:559] 080027e2d0f4: pattern 'leaf_dynamic' - attempting to match node ("Node(serialnumber=None, systemmac=080027e2d0f4, neighbors=OrderedCollection([('Ethernet2', [Neighbor(device='spine002', interface='Ethernet1')]), ('Management1', [Neighbor(device='spine002', interface='Management1'), Neighbor(device='spine001', interface='Management1')]), ('Ethernet1', [Neighbor(device='spine001', interface='Ethernet1')])]))")
DEBUG: [topology:569] 080027e2d0f4: pattern 'leaf_dynamic' - attempting to match interface Ethernet2([Neighbor(device='spine002', interface='Ethernet1')])
DEBUG: [topology:575] 080027e2d0f4: pattern 'leaf_dynamic' - checking interface pattern for Ethernet2: InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:675] 080027e2d0f4: attempting to match Ethernet2(Neighbor(device='spine002', interface='Ethernet1')) against interface pattern InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:575] 080027e2d0f4: pattern 'leaf_dynamic' - checking interface pattern for Ethernet2: InterfacePattern(interface=Ethernet2, remote_device=spine002, remote_interface=any)
DEBUG: [topology:675] 080027e2d0f4: attempting to match Ethernet2(Neighbor(device='spine002', interface='Ethernet1')) against interface pattern InterfacePattern(interface=Ethernet2, remote_device=spine002, remote_interface=any)
DEBUG: [topology:582] 080027e2d0f4: pattern 'leaf_dynamic' - interface pattern match for Ethernet2: InterfacePattern(interface=Ethernet2, remote_device=spine002, remote_interface=any)
DEBUG: [topology:569] 080027e2d0f4: pattern 'leaf_dynamic' - attempting to match interface Management1([Neighbor(device='spine002', interface='Management1'), Neighbor(device='spine001', interface='Management1')])
DEBUG: [topology:575] 080027e2d0f4: pattern 'leaf_dynamic' - checking interface pattern for Management1: InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:675] 080027e2d0f4: attempting to match Management1(Neighbor(device='spine002', interface='Management1')) against interface pattern InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:675] 080027e2d0f4: attempting to match Management1(Neighbor(device='spine001', interface='Management1')) against interface pattern InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:595] 080027e2d0f4: pattern 'leaf_dynamic' - interface Management1 did not match any interface patterns
DEBUG: [topology:569] 080027e2d0f4: pattern 'leaf_dynamic' - attempting to match interface Ethernet1([Neighbor(device='spine001', interface='Ethernet1')])
DEBUG: [topology:575] 080027e2d0f4: pattern 'leaf_dynamic' - checking interface pattern for Ethernet1: InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:675] 080027e2d0f4: attempting to match Ethernet1(Neighbor(device='spine001', interface='Ethernet1')) against interface pattern InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [topology:582] 080027e2d0f4: pattern 'leaf_dynamic' - interface pattern match for Ethernet1: InterfacePattern(interface=Ethernet1, remote_device=spine001, remote_interface=any)
DEBUG: [controller:529] 080027e2d0f4: node passed pattern validation (nodes/080027e2d0f4/pattern)
DEBUG: [controller:170] 080027e2d0f4: running get_startup_config
DEBUG: [controller:549] 080027e2d0f4: no startup-config nodes/080027e2d0f4/startup-config
DEBUG: [controller:170] 080027e2d0f4: running do_actions
DEBUG: [controller:567] 080027e2d0f4: action configure ma1 included in definition
DEBUG: [controller:567] 080027e2d0f4: action configure global system included in definition
DEBUG: [controller:567] 080027e2d0f4: action configure auth included in definition
DEBUG: [controller:567] 080027e2d0f4: action configure ztpprep alias included in definition
DEBUG: [controller:567] 080027e2d0f4: action configure uplink included in definition
DEBUG: [controller:562] 080027e2d0f4: always_execute action automate reload included in definition
DEBUG: [controller:170] 080027e2d0f4: running get_attributes
WARNING: [controller:589] 080027e2d0f4: no node specific attributes file
DEBUG: [controller:170] 080027e2d0f4: running do_substitution
DEBUG: [controller:609] 080027e2d0f4: processing action configure ma1 (variable substitution)
DEBUG: [controller:609] 080027e2d0f4: processing action configure global system (variable substitution)
DEBUG: [controller:609] 080027e2d0f4: processing action configure auth (variable substitution)
DEBUG: [controller:609] 080027e2d0f4: processing action configure ztpprep alias (variable substitution)
DEBUG: [controller:609] 080027e2d0f4: processing action configure uplink (variable substitution)
DEBUG: [controller:609] 080027e2d0f4: processing action automate reload (variable substitution)
DEBUG: [controller:170] 080027e2d0f4: running do_resources
DEBUG: [topology:147] 080027e2d0f4: computing resources (attr={'url': 'files/templates/ma1.template', 'variables': {'ipaddress': "allocate('mgmt_subnet')"}})
DEBUG: [topology:147] 080027e2d0f4: computing resources (attr={'ipaddress': "allocate('mgmt_subnet')"})
DEBUG: [resources:76] 080027e2d0f4: allocating resources
DEBUG: [resources:108] 080027e2d0f4: looking up resource in 'mgmt_subnet'
DEBUG: [resources:90] 080027e2d0f4: allocated 'mgmt_subnet':'192.168.101.54/24'
DEBUG: [topology:171] 080027e2d0f4: resources: {'ipaddress': '192.168.101.54/24'}
DEBUG: [topology:171] 080027e2d0f4: resources: {'url': 'files/templates/ma1.template', 'variables': {'ipaddress': '192.168.101.54/24'}}
DEBUG: [topology:147] 080027e2d0f4: computing resources (attr={'url': 'files/templates/system.template', 'variables': {'hostname': "allocate('leaf_hostname')"}})
DEBUG: [topology:147] 080027e2d0f4: computing resources (attr={'hostname': "allocate('leaf_hostname')"})
DEBUG: [resources:76] 080027e2d0f4: allocating resources
DEBUG: [resources:108] 080027e2d0f4: looking up resource in 'leaf_hostname'
DEBUG: [resources:90] 080027e2d0f4: allocated 'leaf_hostname':'leaf004'
DEBUG: [topology:171] 080027e2d0f4: resources: {'hostname': 'leaf004'}
DEBUG: [topology:171] 080027e2d0f4: resources: {'url': 'files/templates/system.template', 'variables': {'hostname': 'leaf004'}}
DEBUG: [topology:147] 080027e2d0f4: computing resources (attr={'url': 'files/templates/login.template'})
DEBUG: [topology:171] 080027e2d0f4: resources: {'url': 'files/templates/login.template'}
DEBUG: [topology:147] 080027e2d0f4: computing resources (attr={'url': 'files/templates/ztpprep.template'})
DEBUG: [topology:171] 080027e2d0f4: resources: {'url': 'files/templates/ztpprep.template'}
DEBUG: [topology:147] 080027e2d0f4: computing resources (attr={'url': 'files/templates/uplink.template', 'variables': {'spine_downlink': "allocate('spine_downlink')"}})
DEBUG: [topology:147] 080027e2d0f4: computing resources (attr={'spine_downlink': "allocate('spine_downlink')"})
DEBUG: [resources:76] 080027e2d0f4: allocating resources
DEBUG: [resources:108] 080027e2d0f4: looking up resource in 'spine_downlink'
DEBUG: [resources:90] 080027e2d0f4: allocated 'spine_downlink':'Eth4'
DEBUG: [topology:171] 080027e2d0f4: resources: {'spine_downlink': 'Eth4'}
DEBUG: [topology:171] 080027e2d0f4: resources: {'url': 'files/templates/uplink.template', 'variables': {'spine_downlink': 'Eth4'}}
DEBUG: [topology:147] 080027e2d0f4: computing resources (attr={'dst_url': '/mnt/flash/', 'src_url': 'files/automate/ztpprep', 'mode': 777, 'overwrite': 'if-missing'})
DEBUG: [topology:171] 080027e2d0f4: resources: {'dst_url': '/mnt/flash/', 'src_url': 'files/automate/ztpprep', 'mode': 777, 'overwrite': 'if-missing'}
DEBUG: [controller:170] 080027e2d0f4: running finalize_response
DEBUG: [controller:182] 080027e2d0f4: response to finalize_response: {'body': {'name': 'def_leaf', 'actions': [{'action': 'add_config', 'attributes': {'url': 'files/templates/ma1.template', 'variables': {'ipaddress': '192.168.101.54/24'}}, 'name': 'configure ma1', 'onstart': 'Starting to configure ma1', 'onsuccess': 'SUCCESS: ma1 configure'}, {'action': 'add_config', 'attributes': {'url': 'files/templates/system.template', 'variables': {'hostname': 'leaf004'}}, 'name': 'configure global system', 'onstart': 'Starting to add basic system config', 'onsuccess': 'SUCCESS: basic config added'}, {'action': 'add_config', 'attributes': {'url': 'files/templates/login.template'}, 'name': 'configure auth'}, {'action': 'add_config', 'attributes': {'url': 'files/templates/ztpprep.template'}, 'name': 'configure ztpprep alias'}, {'action': 'add_config', 'attributes': {'url': 'files/templates/uplink.template', 'variables': {'spine_downlink': 'Eth4'}}, 'name': 'configure uplink', 'onstart': 'Starting to configure uplink', 'onsuccess': 'SUCCESS: uplink configured'}, {'action': 'copy_file', 'always_execute': True, 'name': 'automate reload', 'attributes': {'dst_url': '/mnt/flash/', 'src_url': 'files/automate/ztpprep', 'mode': 777, 'overwrite': 'if-missing'}}]}, 'status': 200, 'content_type': 'application/json'}
192.168.101.62 - - [27/Nov/2014 01:11:49] "GET /nodes/080027e2d0f4 HTTP/1.1" 200 1181
DEBUG: [controller:143] GET /actions/add_config HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 4
Content-Type: text/html
Host: 192.168.101.16:8080
User-Agent: python-requests/1.2.3 CPython/2.7.0 Linux/3.4.43.Ar-2083164.4142F
Resource: add_config

192.168.101.62 - - [27/Nov/2014 01:11:49] "GET /actions/add_config HTTP/1.1" 200 3249
DEBUG: [controller:120] GET /files/templates/ma1.template HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 4
Content-Type: text/html
Host: 192.168.101.16:8080
User-Agent: python-requests/1.2.3 CPython/2.7.0 Linux/3.4.43.Ar-2083164.4142F
Resource: templates/ma1.template

192.168.101.62 - - [27/Nov/2014 01:11:49] "GET /files/templates/ma1.template HTTP/1.1" 200 60
DEBUG: [controller:120] GET /files/templates/system.template HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 4
Content-Type: text/html
Host: 192.168.101.16:8080
User-Agent: python-requests/1.2.3 CPython/2.7.0 Linux/3.4.43.Ar-2083164.4142F
Resource: templates/system.template

192.168.101.62 - - [27/Nov/2014 01:11:49] "GET /files/templates/system.template HTTP/1.1" 200 84
DEBUG: [controller:120] GET /files/templates/login.template HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 4
Content-Type: text/html
Host: 192.168.101.16:8080
User-Agent: python-requests/1.2.3 CPython/2.7.0 Linux/3.4.43.Ar-2083164.4142F
Resource: templates/login.template

192.168.101.62 - - [27/Nov/2014 01:11:49] "GET /files/templates/login.template HTTP/1.1" 200 61
DEBUG: [controller:120] GET /files/templates/ztpprep.template HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 4
Content-Type: text/html
Host: 192.168.101.16:8080
User-Agent: python-requests/1.2.3 CPython/2.7.0 Linux/3.4.43.Ar-2083164.4142F
Resource: templates/ztpprep.template

192.168.101.62 - - [27/Nov/2014 01:11:49] "GET /files/templates/ztpprep.template HTTP/1.1" 200 43
DEBUG: [controller:120] GET /files/templates/uplink.template HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 4
Content-Type: text/html
Host: 192.168.101.16:8080
User-Agent: python-requests/1.2.3 CPython/2.7.0 Linux/3.4.43.Ar-2083164.4142F
Resource: templates/uplink.template

192.168.101.62 - - [27/Nov/2014 01:11:49] "GET /files/templates/uplink.template HTTP/1.1" 200 279
DEBUG: [controller:143] GET /actions/copy_file HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 4
Content-Type: text/html
Host: 192.168.101.16:8080
User-Agent: python-requests/1.2.3 CPython/2.7.0 Linux/3.4.43.Ar-2083164.4142F
Resource: copy_file

192.168.101.62 - - [27/Nov/2014 01:11:49] "GET /actions/copy_file HTTP/1.1" 200 5589
DEBUG: [controller:120] GET /files/automate/ztpprep HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 4
Content-Type: text/html
Host: 192.168.101.16:8080
User-Agent: python-requests/1.2.3 CPython/2.7.0 Linux/3.4.43.Ar-2083164.4142F
Resource: automate/ztpprep

192.168.101.62 - - [27/Nov/2014 01:11:49] "GET /files/automate/ztpprep HTTP/1.1" 200 144
192.168.101.62 - - [27/Nov/2014 01:11:49] "GET /meta/files/automate/ztpprep HTTP/1.1" 200 65

実行後の状態確認

Resource pools

実行を見守っている時点で「え?」と思ったのですが、Resource pools の払い出しを見ると以下のようになっています。

$ cat /usr/share/ztpserver/resources/mgmt_subnet
192.168.101.52/24: null
192.168.101.53/24: null
192.168.101.54/24: 080027e2d0f4
192.168.101.55/24: null

$ cat /usr/share/ztpserver/resources/leaf_hostname
leaf001: null
leaf002: null
leaf003: null
leaf004: 080027e2d0f4

$ cat /usr/share/ztpserver/resources/spine_downlink
1: null
2: null
3: null
4: 080027e2d0f4

別に上から順に使われていくわけではないようです。
これについては後述します。

nodes ディレクト

/usr/share/ztpserver/nodes/ 配下には、Static Provisioning の時には手動生成した「ノード単位のディレクトリ・ファイル」が自動生成されています。
definition ファイルは、先の手順で生成した /usr/share/ztpserver/definitions/def_leaf が、neighbordb マッチングの結果、ほぼそのまま持ってこられています。

$ ls -alh /usr/share/ztpserver/nodes/
total 20K
drwxr-xr-x 5 root root 4.0K Nov 29 17:05 .
drwxr-xr-x 8 root root 4.0K Nov 27 00:20 ..
drwxr-xr-x 2 root root 4.0K Nov 24 02:12 080027316065
drwxr-xr-x 2 root root 4.0K Nov 29 16:35 080027be304d
drwxr-xr-x 2 root root 4.0K Nov 29 17:05 080027e2d0f4

$ ls -alh /usr/share/ztpserver/nodes/080027e2d0f4/
total 20K
drwxr-xr-x 2 root root 4.0K Nov 29 17:05 .
drwxr-xr-x 5 root root 4.0K Nov 29 17:05 ..
-rw-r--r-- 1 root root 1.2K Nov 29 17:05 definition
-rw-r--r-- 1 root root  314 Nov 29 17:05 .node
-rw-r--r-- 1 root root  129 Nov 29 17:05 pattern

$ cat /usr/share/ztpserver/nodes/080027e2d0f4/pattern
definition: def_leaf
interfaces:
- Ethernet1: spine001:any
- Ethernet2: spine002:any
name: leaf_dynamic
node: null
variables: {}

$ cat /usr/share/ztpserver/nodes/080027e2d0f4/.node
{"neighbors": {"Ethernet2": [{"device": "spine002", "port": "Ethernet1"}], "Management1": [{"device": "spine001", "port": "Management1"}, {"device": "spine002", "port": "Management1"}], "Ethernet1": [{"device": "spine001", "port": "Ethernet1"}]}, "model": "vEOS", "version": "4.14.2F", "systemmac": "080027e2d0f4"}

Resource pools 動作調査

Resource pools の払い出し判定ってどうなってるの?っていうところを見てみます。
公式の説明 を見ると、空いている最初の値をとってくるように見えるので…。

開発状況

Static Provisioning の時には既知 issue で若干嵌ったので、とりあえず github issue 見ておきます。

ザッと眺めたところ、この機能はまだテストがちゃんとされていないようです。 2014/11/29 時点では v1.2.0 (2014/12 release 予定)を milestone としているようですが、develop branch を見ても特にテストコードは入ってません。

ソースコード

# python 詳しくないので、間違えたこと言ってたらスミマセン

以下が Resource pools の allocate しているログです。

DEBUG: [topology:147] 080027e2d0f4: computing resources (attr={'url': 'files/templates/ma1.template', 'variables': {'ipaddress': "allocate('mgmt_subnet')"}})
DEBUG: [topology:147] 080027e2d0f4: computing resources (attr={'ipaddress': "allocate('mgmt_subnet')"})
DEBUG: [resources:76] 080027e2d0f4: allocating resources
DEBUG: [resources:108] 080027e2d0f4: looking up resource in 'mgmt_subnet'
DEBUG: [resources:90] 080027e2d0f4: allocated 'mgmt_subnet':'192.168.101.54/24'

ソースコードとしては、端的には ここ で、関係するのは以下あたり。

動作としては…

  1. 対象の Resouce pools ファイルを /usr/share/ztpserver/resources/ から YAML として load (PyYAML)
  2. 上記 YAML の中身を、空の dict 型 object に key, value 格納 (value が null とか空なら None)
  3. 上記の dict を走査、valueNone ならこいつを割り当てるリソースとして key を取り出す

ってことで、明らかにおかしいところは見つからないけど… dict 型は順序が保証されるものではないから…とか?
OrderedDict なんていう順序保証型の辞書型もあるらしいけど。

ちなみに、事前に /usr/share/ztpserver/resources/ 配下の定義ファイルにコメント入れたり、順序をぐちゃぐちゃにしても、コメントは消されて、ソートされて上書きされます。(dict をファイルに吐き出している)

一応 MLAG 構成完成させる

仕方ないので、インチキしてとりあえず想定するパラメータが付与されるように leaf001,002 を作りました…。
具体的には /usr/share/ztpserver/resources/ 配下の Resource pools 設定ファイルを弄って、付与したいパラメータだけを書いた状態で 1 台ずつ ZTP…。

f:id:kakkotetsu:20170513165039j:plain

気持ち悪さを残したまま、 MLAG 設定を作り上げておきます。 以下設定を spine001,002 に追加。

interface Ethernet1
   channel-group 1 mode active

interface Ethernet2
   channel-group 2 mode active

Port-channel を組んだことで、しばし待つと LLDP neighbor から消えてしまいます…。
# Ma1 は VirtualBox 環境なので、同一セグメントの vEOS 間で全部拾ってしまっています。

spine002#show lldp neighbors
Last table change time   : 0:00:07 ago
Number of table inserts  : 27
Number of table deletes  : 22
Number of table drops    : 0
Number of table age-outs : 22

Port       Neighbor Device ID             Neighbor Port ID           TTL
Et1        leaf001                        Ethernet2                  120
Et2        leaf002                        Ethernet2                  120
Ma1        spine001                       Management1                120
Ma1        leaf001                        Management1                120
Ma1        leaf002                        Management1                120


spine002#show lldp neighbors
Last table change time   : 0:29:36 ago
Number of table inserts  : 27
Number of table deletes  : 24
Number of table drops    : 0
Number of table age-outs : 24

Port       Neighbor Device ID             Neighbor Port ID           TTL
Ma1        spine001                       Management1                120
Ma1        leaf001                        Management1                120
Ma1        leaf002                        Management1                120

おわり

課題

以下あたりが課題ですかね。適当にやってるところがあるので、間違えてたら指摘下さい。

  • ZTP 関係なく、Port-Channel のメンバ物理 IF では LLDP が動作しないってのはちょっと…。(その後のバージョンや物理箱ではそういう制約は踏まなかったので、この環境固有の問題)
  • Resource pools 周りは、巧く動かせなかったので…。現時点では明確に何とも言えないので、暇があったらもう少し追ってみようかと。とりあえず直近 v1.2.0 が出るので、見てますか?
  • Resource pools が想定通り動いても、今度は起動順で払い出しを制御しないといけない、とかそもそもどのラックのどのスイッチをどのホスト名にするかを気にしないようにする、とか本番環境で実用する場合には運用を熟考しないといけなさそうです。
  • neighbordb の条件付けで正規表現とか、もっと柔軟な定義が使えるようになるようです。そうなると、definition での variables のところも柔軟にできると結構実用的になりそうです。(e.x. LLDP で送ってきた spine 側のポート番号を使って hostname: leaf&("%03d", spine_port) 的な書き方)。
  • write erase からの reload は必要なので、Zero Touch ではないすね。箱だと出荷時点で startup-config なかったりするのでしょうか。

雑感

  • ZTP なんて昔から Cisco がやってるじゃん、と思ってましたが、結構進化していたみたいです。ごめんなさい。
  • ztpserver の issue を見ていると、enhance も結構計画されていて期待できそうです。
  • Arista は「LLDP 情報を基に port description を更新!」なんてのも出しているし、サーバでもどんどん LLDP 動かそう。
  • こういうのを巧く使いこなすには、統一されたネットワーク設計が必要でしょう。それがあって最大限に使いこなすと、ネットワーク屋さんがいなくてもどんどんサーバ増やしていける世界になるんじゃないでしょうか。ていうか、なれ。