kakkotetsu

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 (English Edition)

Arista Warrior: A Real-World Guide to Understanding Arista Switches and EOS (English Edition)

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

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

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 動かそう。
  • こういうのを巧く使いこなすには、統一されたネットワーク設計が必要でしょう。それがあって最大限に使いこなすと、ネットワーク屋さんがいなくてもどんどんサーバ増やしていける世界になるんじゃないでしょうか。ていうか、なれ。