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)
- 作者: Gary A. Donahue
- 出版社/メーカー: O'Reilly Media
- 発売日: 2012/10/05
- メディア: Kindle版
- この商品を含むブログを見る
Arista Warrior: A Real-World Guide to Understanding Arista Switches and EOS
- 作者: Gary A. Donahue
- 出版社/メーカー: O'Reilly Media
- 発売日: 2012/10/27
- メディア: ペーパーバック
- この商品を含むブログを見る
Resource pools とは?
公式の説明 を読むのが手っ取り早いです。
ノード単位で付与するパラメータをプール化しておき、ZTP 実行時に ztpserver が勝手に空いているところを使って利用済みにしてくれる、簡単なリソース管理までやってくれる方式です。
結果概要
ztpserver v1.1.0 で試行した時点では、課題が残りました。いまいちスッキリしてません。詳細は後述します。
- クライアント(vEOS)識別に neighbordb で LLDP 状態マッチングは便利だけど、Arista 側の機能制約起因で実用に少し工夫が必要
- パラメータ払い出しの Resource pools 動作は微妙なので、現時点では実用に結構な工夫が必要
構成図・環境情報
以下の構成でやります。
物理構成は一般的なデータセンタネットワークのそれで、Arista の Multi-Chassis Link Aggregation (MLAG)を使っています。
本項では MLAG の詳細は説明しません & 設定内容もベストプラクティスではないです。
- 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
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 の謳い文句で言うところの配線にあたります。
まあ、仮想環境なんですけどね。
- ネットワークアダプタ1:ホストオンリーネットワーク(ztpserver と同じところ)
- ネットワークアダプタ2:内部ネットワーク(spine001 と同じところ)
- ネットワークアダプタ3:内部ネットワーク(spine002 と同じところ)
そんなわけで、内部的には仮想スイッチ的なものが挟まるので 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'
ソースコードとしては、端的には ここ で、関係するのは以下あたり。
動作としては…
- 対象の Resouce pools ファイルを
/usr/share/ztpserver/resources/
から YAML としてload
(PyYAML) - 上記 YAML の中身を、空の
dict
型 object にkey
,value
格納 (value
が null とか空ならNone
) - 上記の
dict
を走査、value
がNone
ならこいつを割り当てるリソースとしてkey
を取り出す
ってことで、明らかにおかしいところは見つからないけど… dict
型は順序が保証されるものではないから…とか?
OrderedDict
なんていう順序保証型の辞書型もあるらしいけど。
ちなみに、事前に /usr/share/ztpserver/resources/
配下の定義ファイルにコメント入れたり、順序をぐちゃぐちゃにしても、コメントは消されて、ソートされて上書きされます。(dict
をファイルに吐き出している)
一応 MLAG 構成完成させる
仕方ないので、インチキしてとりあえず想定するパラメータが付与されるように leaf001,002 を作りました…。
具体的には /usr/share/ztpserver/resources/
配下の Resource pools 設定ファイルを弄って、付与したいパラメータだけを書いた状態で 1 台ずつ ZTP…。
気持ち悪さを残したまま、 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 動かそう。
- こういうのを巧く使いこなすには、統一されたネットワーク設計が必要でしょう。それがあって最大限に使いこなすと、ネットワーク屋さんがいなくてもどんどんサーバ増やしていける世界になるんじゃないでしょうか。ていうか、なれ。
Arista の Zero Touch Provisioning (ZTP) を試す (Static Provisioning 編) (original : 2014/11/24)
この記事は某所で 2014/11/24
に書いたもののコピーです。
そのため 2017/05/13
時点ではやや古い情報も含まれています。
概要
本項でやること
ZTP を使って、Arista vEOS x1台の初期設定投入をしてみます。
Cisco ではちょっとした作りこみが必要だった「設定テンプレートを作成し、対象ノードの MAC アドレスとノードごとの変数(ホスト名や管理IP)の組み合わせを定義して、バラまく startupconfig ファイルを生成」というような動作を、ztpserver の仕組みを使ってより柔軟にできるよ、ってところを見ます。
- 方式は Static Provisioning で、対象 vEOS の Sysyte MAC アドレスを事前登録
- 設定ファイルは ztpserver の仕組み(bootstrap と template と definition)を使って動的に生成
Static Provisioning と Dynamic Provisioning とは?
公式の説明 を読むのが手っ取り早いです。
超ザックリ言うと、以下の通り。
- Static は、事前に ztpserver にクライアント(Arista)の System MAC アドレスかシリアル番号を登録しておく。サーバがクライアントに応じた startup-config か bootstrap スクリプトをバラまくことができる。
- Dynamic は、ZTP シーケンスの中でクライアントが送ってきた LLDP 情報を基に、サーバ側で条件マッチして bootstrap スクリプトをバラまくことができる。
Dynamic Provisioning こそが ZTP の本領発揮、という感じですが、本項ではまず Static Provisioning を見ておきます。(startup-config 方式は、あまり面白くなさそうなので割愛)
Arista EOS?
Arista EOS については、以下の本をオススメしときますね。
Arista Warrior: A Real-World Guide to Understanding Arista Switches and EOS
- 作者: Gary A. Donahue
- 出版社/メーカー: O'Reilly Media
- 発売日: 2012/10/27
- メディア: ペーパーバック
- この商品を含むブログを見る
構成図・事前準備
Arista の ztpserver インストール~初期設定 で作った ztpserver と、突っ込んだだけで何も設定していない ARISTA vEOS x1 を使います。以下の簡単構成で。(作業時に手元で作ってた絵があまりにお粗末だったので、試しに ShowNet のアイコンを入れてみたが、かえってチグハグに…)
ARISTA vEOS も 1 台必要なので、以下リンクを参考に作っておきます。公式に、各種ハイパーバイザごとの導入手順詳細とかもあります。
本項の環境は以下の通りです。
- 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 ネットワークアダプタ1:ホストオンリーネットワーク(ztpserverと同じところ)
設定手順
基本的に 公式の Configuration と 公式の Examples を参照しつつ、必要に応じて ztpserver の source を参照して進めていきます。
vEOS で System MAC アドレス確認
今回は Static Provisioning を使うので、vEOS 側の System MAC アドレスを拾っておきます。以下の System MAC address
に表示されている 12 桁のやつです。仮想機器なので Serial number はなし…。
#show version Arista vEOS Hardware version: Serial number: System MAC address: 0800.2731.6065 Software image version: 4.14.2F Architecture: i386 Internal build version: 4.14.2F-2083164.4142F.1 Internal build ID: 19fe6cb3-1777-40b6-a4e6-53875b30658c Uptime: 1 hour and 54 minutes Total memory: 996152 kB Free memory: 34112 kB
ztpserver でクライアントノード単位の設定
/usr/share/ztpserver/nodes/
配下にディレクトリ・ファイル作成していきます。
例によって 公式の Configuration を見ながら。
ディレクトリ作成
以下のように、まずはノードを識別する System MAC アドレスディレクトリを作ります。
この配下に、ノードごとの定義ファイルとか(今回は使いませんが startupconfig ファイルとか)を作ります。
$ sudo mkdir /usr/share/ztpserver/nodes/080027316065
definition ファイル作成
/usr/share/ztpserver/nodes/080027316065/
配下に definition ファイルを作成します。
見ての通り、クライアント側に実行させたい処理やテンプレート呼び出しと、テンプレートに埋め込むクライアント単位の変数を定義します。
--- name: spine001 actions: - name: "configure ma1" action: add_config attributes: url: /files/templates/ma1.template variables: ipaddress: 192.168.101.50/24 - name: "configure system" action: add_config attributes: url: /files/templates/system.template variables: hostname: spine001 - name: "configure ztpprep alias" action: add_config attributes: url: /files/templates/ztpprep.template - name: "automate reload" action: copy_file always_execute: true attributes: dst_url: /mnt/flash/ overwrite: if-missing src_url: files/automate/ztpprep mode: 777
間抜けな&当然の話ですが、yaml の文法を間違えたりすると巧いこと vEOS に設定が反映されません。自分はそれでミスって実機に "hostname $hostname" なんてのが設定されてしまいました。 ztps 起動時に、文法チェックとかはしないのです。
pattern ファイル作成
/usr/share/ztpserver/nodes/080027316065/
配下に pattern ファイルを作成します。
これは、ztpserver の Global 設定 /etc/ztpserver/ztpserver.conf
内で disable_topology_validation = True
にしている場合には作成する必要ないです。(公式の記載)
ただ、今後 Dynamic Provisioning を使う際に、これを True にしておくと都合が悪いので False のままにしておきます。この場合、pattern ファイルに記入する内容で「クライアントがどんな LLDP 状態を送ってきても、それを無視する」を実現させます。
name: static_node interfaces: - none: none:none
ここで気を付けるのは、公式の記載 そのままに interfaces:
内で any: any:any
と書いても、「クライアントの LLDP が動いていない・LLDP で何も情報を得ていない場合」には期待する動作にはならず異常終了してしまうことです。
これについては github の issue に記載されていますが、公式ドキュメントには 2014/11/24 現在反映されていないようです。
また、ソースコードの master / develop ブランチを比較すると、pattern ファイルの必須パラメータなども今後変わっていく気配があります。上記設定はあくまで v1.1.0 版ということで。
ztpserver に各種テンプレートを作成
前述の definition ファイル内で呼び出している各種テンプレートや bash スクリプトを作っておきます。
公式のサンプルを持ってきて、必要に応じてカスタマイズして配置するのが手っ取り早いと思います。
/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/ztpprep.template
alias ztpprep bash sudo /mnt/flash/ztpprep
# 超最低限の設定を入れたつもりだったが、login.template
でユーザ登録くらいはしておくべきだった…。
ZTP 動作確認
実際に動かして、vEOS 1台が設定されることを確認します。
ztpserver 起動
$ 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 再起動
startup-config を消して再起動します。いつものように reload
実行時に色々聞かれるので Save するかは no
を、Confirm は そのまま Enter
で対応します。
# write erase # reload
VirtualBox のコンソールで vEOS 停止~起動を見守ります。ログインプロンプトが出たら、ZTP がはじまります。
ZTP 実行
vEOS 起動後、以下のようなメッセージが出て ZTP シーケンスが開始します。左の窓が ztpserver で、右の窓が vEOS です。
公式のシーケンス図と見比べながら、見守ります。
definition ファイルで定義した通りに動作していることをフムフム言いながら見ていると、vEOS の再起動が始まります。
vEOS 再起動が終わったら、定義した通りの startup-config で起動していることを確認できます。
ztpserver 側のログを見ると以下の感じ(debug レベルにしているので長い…)
$ 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 192.168.101.61 - - [24/Nov/2014 02:17:00] "GET /bootstrap HTTP/1.1" 200 35386 192.168.101.61 - - [24/Nov/2014 02:17:01] "GET /bootstrap/config HTTP/1.1" 200 200 DEBUG: [controller:252] POST /nodes HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate, compress Content-Length: 110 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": {}, "version": "4.14.2F", "systemmac": "08:00:27:31:60:65", "model": "vEOS", "serialnumber": ""} DEBUG: [topology:140] 080027316065: created node object Node(serialnumber=, systemmac=080027316065, neighbors=OrderedCollection()) DEBUG: [controller:170] 080027316065: running node_exists DEBUG: [controller:170] 080027316065: running dump_node DEBUG: [controller:170] 080027316065: running set_location DEBUG: [controller:182] 080027316065: response to set_location: {'status': 409, 'location': 'nodes/080027316065'} 192.168.101.61 - - [24/Nov/2014 02:17:28] "POST /nodes HTTP/1.1" 409 0 DEBUG: [controller:470] GET /nodes/080027316065 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: 080027316065 DEBUG: [topology:140] 080027316065: created node object Node(serialnumber=None, systemmac=080027316065, neighbors=OrderedCollection()) DEBUG: [controller:170] 080027316065: running get_definition DEBUG: [controller:498] 080027316065: defintion is nodes/080027316065/definition ([{'action': 'add_config', 'attributes': {'url': '/files/templates/ma1.template', 'variables': {'ipaddress': '192.168.101.50/24'}}, 'name': 'configure ma1'}, {'action': 'add_config', 'attributes': {'url': '/files/templates/system.template', 'variables': {'hostname': 'spine001'}}, 'name': 'configure system'}, {'action': 'add_config', 'attributes': {'url': '/files/templates/ztpprep.template'}, 'name': 'configure ztpprep alias'}, {'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] 080027316065: running do_validation DEBUG: [validators:80] 080027316065: running PatternValidator.validate DEBUG: [validators:96] 080027316065: running PatternValidator.validate_attributes for 'static_node' WARNING: [validators:171] 080027316065: PatternValidator warning: 'static_node' is missing optional attribute (definition) WARNING: [validators:171] 080027316065: PatternValidator warning: 'static_node' is missing optional attribute (variables) DEBUG: [validators:96] 080027316065: running PatternValidator.validate_definition for 'static_node' DEBUG: [validators:96] 080027316065: running PatternValidator.validate_interfaces for 'static_node' DEBUG: [validators:80] 080027316065: running InterfacePatternValidator.validate DEBUG: [validators:96] 080027316065: running InterfacePatternValidator.validate_interface_pattern DEBUG: [validators:204] 080027316065: adding interface pattern '{'none': 'none:none'}' to valid interface patterns DEBUG: [validators:96] 080027316065: running PatternValidator.validate_name for 'static_node' DEBUG: [validators:96] 080027316065: running PatternValidator.validate_node for 'static_node' DEBUG: [validators:96] 080027316065: running PatternValidator.validate_variables for 'static_node' DEBUG: [validators:315] 080027316065: PatternValidator validation successful DEBUG: [topology:472] 080027316065: checking pattern 'static_node' entries for variable substitution DEBUG: [topology:482] 080027316065: pattern 'static_node' variable substitution complete DEBUG: [topology:559] 080027316065: pattern 'static_node' - attempting to match node ('Node(serialnumber=None, systemmac=080027316065, neighbors=OrderedCollection())') DEBUG: [controller:529] 080027316065: node passed pattern validation (nodes/080027316065/pattern) DEBUG: [controller:170] 080027316065: running get_startup_config DEBUG: [controller:549] 080027316065: no startup-config nodes/080027316065/startup-config DEBUG: [controller:170] 080027316065: running do_actions DEBUG: [controller:567] 080027316065: action configure ma1 included in definition DEBUG: [controller:567] 080027316065: action configure system included in definition DEBUG: [controller:567] 080027316065: action configure ztpprep alias included in definition DEBUG: [controller:562] 080027316065: always_execute action automate reload included in definition DEBUG: [controller:170] 080027316065: running get_attributes WARNING: [controller:589] 080027316065: no node specific attributes file DEBUG: [controller:170] 080027316065: running do_substitution DEBUG: [controller:609] 080027316065: processing action configure ma1 (variable substitution) DEBUG: [controller:609] 080027316065: processing action configure system (variable substitution) DEBUG: [controller:609] 080027316065: processing action configure ztpprep alias (variable substitution) DEBUG: [controller:609] 080027316065: processing action automate reload (variable substitution) DEBUG: [controller:170] 080027316065: running do_resources DEBUG: [topology:147] 080027316065: computing resources (attr={'url': '/files/templates/ma1.template', 'variables': {'ipaddress': '192.168.101.50/24'}}) DEBUG: [topology:147] 080027316065: computing resources (attr={'ipaddress': '192.168.101.50/24'}) DEBUG: [topology:171] 080027316065: resources: {'ipaddress': '192.168.101.50/24'} DEBUG: [topology:171] 080027316065: resources: {'url': '/files/templates/ma1.template', 'variables': {'ipaddress': '192.168.101.50/24'}} DEBUG: [topology:147] 080027316065: computing resources (attr={'url': '/files/templates/system.template', 'variables': {'hostname': 'spine001'}}) DEBUG: [topology:147] 080027316065: computing resources (attr={'hostname': 'spine001'}) DEBUG: [topology:171] 080027316065: resources: {'hostname': 'spine001'} DEBUG: [topology:171] 080027316065: resources: {'url': '/files/templates/system.template', 'variables': {'hostname': 'spine001'}} DEBUG: [topology:147] 080027316065: computing resources (attr={'url': '/files/templates/ztpprep.template'}) DEBUG: [topology:171] 080027316065: resources: {'url': '/files/templates/ztpprep.template'} DEBUG: [topology:147] 080027316065: computing resources (attr={'dst_url': '/mnt/flash/', 'src_url': 'files/automate/ztpprep', 'mode': 777, 'overwrite': 'if-missing'}) DEBUG: [topology:171] 080027316065: resources: {'dst_url': '/mnt/flash/', 'src_url': 'files/automate/ztpprep', 'mode': 777, 'overwrite': 'if-missing'} DEBUG: [controller:170] 080027316065: running finalize_response DEBUG: [controller:182] 080027316065: response to finalize_response: {'body': {'name': 'spine001', 'actions': [{'action': 'add_config', 'attributes': {'url': '/files/templates/ma1.template', 'variables': {'ipaddress': '192.168.101.50/24'}}, 'name': 'configure ma1'}, {'action': 'add_config', 'attributes': {'url': '/files/templates/system.template', 'variables': {'hostname': 'spine001'}}, 'name': 'configure system'}, {'action': 'add_config', 'attributes': {'url': '/files/templates/ztpprep.template'}, 'name': 'configure ztpprep alias'}, {'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.61 - - [24/Nov/2014 02:17:28] "GET /nodes/080027316065 HTTP/1.1" 200 657 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.61 - - [24/Nov/2014 02:17:28] "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.61 - - [24/Nov/2014 02:17:28] "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.61 - - [24/Nov/2014 02:17:28] "GET /files/templates/system.template HTTP/1.1" 200 84 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.61 - - [24/Nov/2014 02:17:28] "GET /files/templates/ztpprep.template HTTP/1.1" 200 43 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.61 - - [24/Nov/2014 02:17:28] "GET /actions/copy_file HTTP/1.1" 200 5589
あと、nodes ディレクトリ配下に .node
ファイルができています。このファイルが残ったまま再実行しても、特にエラーにはならないです。
$ ls -alh /usr/share/ztpserver/nodes/080027316065/ total 20K drwxr-xr-x 2 root root 4.0K Nov 24 02:12 . drwxr-xr-x 3 root root 4.0K Nov 23 11:19 .. -rw-r--r-- 1 root root 665 Nov 24 02:12 definition -rw-r--r-- 1 root root 85 Nov 24 02:17 .node -rw-r--r-- 1 root root 69 Nov 24 01:55 pattern $ cat /usr/share/ztpserver/nodes/080027316065/.node {"neighbors": {}, "model": "vEOS", "version": "4.14.2F", "systemmac": "080027316065"}
おわり
Cisco でやったことあればフーンという感じではありますが…template や変数埋め込みの仕組みが簡易なのや、bootstrap の仕組みで Arista 側に任意の bash スクリプトを実行させることが出来るのは、嬉しいかもです。
でも個人的に一番嬉しいのは、仮想OSとして各種ハイパーバイザ上で動かせるイメージが配布されていることで、こういうちょっとした機能試験が手元で簡単にできることです。(例えば別メーカで言うと Juniper の Junos を弄る場合 FireFly が使えますが、EX/MX シリーズとは勿論機能が違うし、箱版の SRX よりできることが少ないです。結果、EX を試したい時には EX2200-C とかの小さなファンレス機を探して箱物を買う羽目に…)
まあ、本領発揮は Dynamic Provisioning で!ということで。
Arista の ztpserver インストール~初期設定 (original : 2014/11/24)
この記事は某所で 2014/11/24
に書いたもののコピーです。
そのため 2017/05/13
時点ではやや古い情報も含まれています。
概要
本項の内容
Arista が公開している ztpserver(2014/11/24 時点の release 版である v1.1.0) を Ubuntu に手動でインストールして初期設定します。
ZTP (zero touch provisioning) とは?
ZTP とは PXE ブートみたいなプロビジョニングを Arista のスイッチで実現する方式です。
スイッチを結線して起動すると、DHCP でアドレスを取得して config ファイルや bootstrap を ztpserver から取得して初期設定されます。
スイッチでは旧来 Cisco が "Smart Install" という名前で実現している方法で、Arista の ZTP は旧来の Cisco より出来ることが多いです。
Arista の ZTP 概要(動作フローなど)は公式ドキュメント Overview 章 ZTP Overview を見ると手っ取り早く掴めます。
参考リンク
Arista EOS?
Arista EOS 自体については、以下の本をオススメしときますね。
Arista Warrior: A Real-World Guide to Understanding Arista Switches and EOS
- 作者: Gary A. Donahue
- 出版社/メーカー: O'Reilly Media
- 発売日: 2012/10/27
- メディア: ペーパーバック
- この商品を含むブログを見る
Arista ZTP に関する必読系
Packer で楽々インストール (本項では使わず)
Arista 公式で Packer も公開されているので、この辺を使うと手っ取り早く作って試せます。ネットワーク構成なども固まってしまうので、必要に応じてカスタマイズして使うのが良いと思います。本項の手動インストールでは参考資料として使いました。
事前準備
ztpserver をインストールするサーバを準備して、Python 2.7 以上をインストールしておきます。
あとは 公式の Requirement を満たせば、何でも良いと思います。
本項の環境は以下の通りです。(ztpserver は GuestOS にインストールしました。)
- ホスト:Windows7 + VirtualBox 4.3.18
- OS:Ubuntu-14.04.01-server-amd64
- CPU:1 core
- Memory:512MB
- ネットワークアダプタ1:NAT (apt などインターネット通信用)
- ネットワークアダプタ2:ホストオンリーネットワーク (ZTP する Arista との通信用)
インストール~初期設定手順
基本的に 公式のInstallation に従いつつ、必要に応じて 公式の Ubuntu12.04 用 ztpserver Packer レシピ を参考にしながら進めます。
必要パッケージインストール
とりあえず Python が 2.7 以上であることを確認します。
$ python --version Python 2.7.6
必要なパッケージを apt-get で install していきます。面倒なら適当に -y
とかつけて下さい。
ZTP v1.1.0 で bootstrap を使う場合、XMPP サーバと接続できないと異常終了してしまいます。これは ZTP 用のサーバとは別に居ても良いのですが、今回は相乗りさせるので ejabberd
も入れておきます。
$ sudo apt-get install python-setuptools python-pip python-dev li bxml2-dev libxslt-dev $ sudo apt-get install git $ sudo apt-get install libyaml-dev $ sudo apt-get install isc-dhcp-server $ sudo apt-get install ejabberd $ sudo apt-get install libapache2-mod-wsgi
ztpserver インストール
お手軽に pip install
で入れるか git clone
してコンパイルするか、お好きな方で。
ztpserver インストール (pip 版)
pip で簡単にインストールできます。これで実行したら、次項の git clone 版は実行不要です。
ファイルが /etc/
や /usr/share/
配下に展開されるので sudo
してます。
$ sudo pip install ztpserver $ ztps -v ztps version 1.1.0
ztpserver インストール (git clone 版)
pip install では味気ないと思うなら、手動でやることもできます。とはいえ、git clone して setup 叩くだけの簡単なお仕事です。
github から clone します。
$ git clone https://github.com/arista-eosplus/ztpserver.git $ cd ztpserver/ $ git show commit de90d377dd4be7f2fe5eccc70c4f3ca11c8fdc9a Author: Andrei Dvornic <advornic@aristanetworks.com> Date: Fri Nov 21 19:53:00 2014 +0000 fixes #242 diff --git a/client/bootstrap b/client/bootstrap index 0692900..5512442 100644 --- a/client/bootstrap +++ b/client/bootstrap @@ -358,7 +358,8 @@ class Node(object): self._disable_copp() except jsonrpclib.jsonrpc.ProtocolError as err: log('WARNING: unable to disable COPP: %s ' - '(can be ignored for EOS-4.11.x and older)' % err) + '(platform/EOS version might not support this feature)' % + err) global SYSTEM_ID #pylint: disable=W0603 SYSTEM_ID = \ @@ -397,8 +398,9 @@ class Node(object): 'show management api http-commands | grep running') def _disable_copp(self): - # COPP does not apply to vEOS - if self.system()['model'] != 'vEOS': + # COPP does not apply to vEOS or EOS-4.11.x and earlier + if (self.system()['model'] != 'vEOS' and + int(self.system()['version'].split('.')[1]) < 12): self.api_config_cmds(['control-plane', 'no service-policy input copp-system-policy'])
2014/11/24 時点のリリース版を checkout します。
$ git checkout v1.1.0 Note: checking out 'v1.1.0'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new_branch_name HEAD is now at 269e7c2... Merge pull request #199 from arista-eosplus/rtd-docs $ git show commit 269e7c264c459c3af13104d28a9fd140cb3be47d Merge: 53fb159 4731287 Author: Jere Julian <jerearista@users.noreply.github.com> Date: Wed Aug 27 19:25:06 2014 -0400 Merge pull request #199 from arista-eosplus/rtd-docs Add capability for sphinx at ReadTheDocs to find bootstrap and actions docstrings $ ls -alh total 100K drwxrwxr-x 11 kotetsu kotetsu 4.0K Nov 23 00:42 . drwxr-xr-x 4 kotetsu kotetsu 4.0K Nov 23 00:40 .. drwxrwxr-x 2 kotetsu kotetsu 4.0K Nov 23 00:42 actions drwxrwxr-x 2 kotetsu kotetsu 4.0K Nov 23 00:42 bin -rw-rw-r-- 1 kotetsu kotetsu 20 Nov 23 00:42 CHANGELOG.md drwxrwxr-x 3 kotetsu kotetsu 4.0K Nov 23 00:42 client drwxrwxr-x 2 kotetsu kotetsu 4.0K Nov 23 00:42 conf -rw-rw-r-- 1 kotetsu kotetsu 1.8K Nov 23 00:41 CONTRIBUTING.md drwxrwxr-x 3 kotetsu kotetsu 4.0K Nov 23 00:42 docs drwxrwxr-x 8 kotetsu kotetsu 4.0K Nov 23 00:42 .git -rw-rw-r-- 1 kotetsu kotetsu 400 Nov 23 00:42 .gitignore -rw-rw-r-- 1 kotetsu kotetsu 300 Nov 23 00:41 INSTALL.md -rw-rw-r-- 1 kotetsu kotetsu 2.3K Nov 23 00:42 LICENSE -rw-rw-r-- 1 kotetsu kotetsu 2.0K Nov 23 00:42 Makefile -rw-rw-r-- 1 kotetsu kotetsu 96 Nov 23 00:42 MANIFEST.in -rw-rw-r-- 1 kotetsu kotetsu 1.1K Nov 23 00:42 .pylintrc -rw-rw-r-- 1 kotetsu kotetsu 2.5K Nov 23 00:41 README.md -rw-rw-r-- 1 kotetsu kotetsu 55 Nov 23 00:41 requirements.txt -rw-rw-r-- 1 kotetsu kotetsu 37 Nov 23 00:41 setup.cfg -rw-rw-r-- 1 kotetsu kotetsu 3.2K Nov 23 00:42 setup.py drwxrwxr-x 6 kotetsu kotetsu 4.0K Nov 23 00:41 test -rw-rw-r-- 1 kotetsu kotetsu 353 Nov 23 00:41 .travis.yml drwxrwxr-x 2 kotetsu kotetsu 4.0K Nov 23 00:41 utils -rw-rw-r-- 1 kotetsu kotetsu 6 Nov 23 00:42 VERSION drwxrwxr-x 2 kotetsu kotetsu 4.0K Nov 23 00:42 ztpserver
build ~ install します。
ファイルが /etc/
や /usr/share/
配下に展開されるので sudo
してます。
$ python setup.py build $ sudo python setup.py install $ ztps -v ztps version 1.1.0
DHCPサーバ (isc-dhcp-server) の設定
/etc/dhcp/dhcpd.conf
に環境に合わせた設定を追加しておきます。
domain-name 関係は今回使っていないので、設定しません。(内部 DNS サーバを使って、XMPPサーバや ZTP サーバをドメインで渡す場合には設定して下さい。本項では面倒なのでやっていないだけで、公式 Packer ではそうしています。)
以下例で range
に指定しているのは、ZTP の DHCP シーケンス中で一時的に Arista に付与されるアドレスなので、複数機器を同時に多数 ZTP 処理するような場合には広めにとっておきましょう。
option bootfile-name
には ZTP サーバのアドレスを入れておきます。ポート番号や path はこのままで OK です。
/etc/dhcp/dhcpd.conf
の追加設定例
#Subnet created for VEOS devices subnet 192.168.101.0 netmask 255.255.255.0 { range 192.168.101.61 192.168.101.69; option bootfile-name "http://192.168.101.16:8080/bootstrap"; default-lease-time 86400; max-lease-time 86400; }
サービス起動
$ sudo service isc-dhcp-server start
runlevel は 2345 になっている筈です。/etc/init/isc-dhcp-server.conf
参照。
XMPP サーバ (ejabberd) の設定
bootstrap を使うために必要なので、設定します。多分、startup-config ファイルを配布する方式だけを使う場合には、XMPP サーバは不要だと思います。
まず /etc/ejabberd/ejaejabberd.cfg
を編集。
以下設定例で ztpsadmin
の部分は、この後登録する ejabberd の admin ユーザ名です。(何でも良いです。)
%% Admin user {acl, admin, {user, "ztpsadmin", "localhost"}}.
ejabberd の admin ユーザを登録します。以下例では ztpsadmin
が設定ファイルにも書いたユーザ名で eosplus
はパスワード文字列です。
$ sudo ejabberdctl register ztpsadmin localhost eosplus User ztpsadmin@localhost successfully registered
サービスを起動します。
$ sudo service ejabberd restart Restarting jabber server: ejabberd is not running. Starting ejabberd.
$ ejabberd status
とか見ても、正常なのか良く分からなかったので、Web ブラウザで動作確認しました。http://<ejabberd 入れたサーバの IP アドレス>:5280/admin
にアクセスしてユーザ名に ztpsadmin@localhost
を、パスワードに eosplus
を入力して、何かオレンジの画面が出たらきっと動いてます。WebUI で設定変更とか出来るようです。
ztpserver の設定
ZTP の方式によって色々とカスタマイズできる個別設定は別項として、ここでは共通設定を済ませておきます。
例によって 公式の Configuration と 公式の Examples に従い、必要に応じて 公式 Packer で配布している設定ファイル群 を参考に進めます。
Global configuration file (/etc/ztpserver/ztpserver.conf) 編集
/etc/ztpserver/ztpserver.conf
を編集します。
デフォルトの identifier = serialnumber
を identifier = systemmac
にしています。自分が試した vEOS (公式で配布されている仮想OS)は、ZTP シーケンス時 serialnumber を空で ZTP サーバに送信していたので、identifier = serialnumber
だと異常終了してしまったからです。
[default] # Location of all ztps boostrap process data files data_root = /usr/share/ztpserver # UID used in the /nodes structure (either systemmac or serialnumber) identifier = systemmac # Server URL to-be-advertised to clients (via POST replies) during the bootstrap process server_url = http://192.168.101.16:8080 # Enable local logging logging = True # Enable console logging console_logging = True # Globally disable topology validation in the bootstrap process disable_topology_validation = False [server] # Note: this section only applies to using the standalone server. If # running under a WSGI server, these values are ignored # Interface to which the server will bind to (0:0:0:0 will bind to # all available IPv4 addresses on the local machine) interface = 192.168.101.16 # TCP listening port port = 8080 [ files] # Path for the files directory (overriding data_root/files) folder = files path_prefix = /usr/share/ztpserver [actions] # Path for the actions directory (overriding data_root/actions) folder = actions path_prefix = /usr/share/ztpserver [bootstrap] # Path for the bootstrap directory (overriding data_root/bootstrap) folder = bootstrap path_prefix = /usr/share/ztpserver # Bootstrap filename filename = bootstrap [neighbordb] # Neighbordb filename (file located in data_root) filename = neighbordb
bootstrap 設定ファイル (/usr/share/ztpserver/bootstrap/bootstrap.conf) 編集
これは bootstrap 方式で ZTP する場合にだけ使われるので、共通設定とはいえないかもですが。
/usr/share/ztpserver/bootstrap/bootstrap.conf
を編集します。xmpp のところには、ejaejabberd で設定した username
と password
を書きます。domain は本項の環境では使わないので、IP アドレスを。
logging: - destination: file:/tmp/ztps-log level: DEBUG xmpp: username: ztpsadmin password: eosplus domain: 192.168.101.16 msg_type : debug rooms: - xmpproom
ztpserver 起動
以下のように起動します。ZTP の方式によってはシーケンス中に /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
おわり
インストールと初期設定だけなので、特筆すべき点はないです。