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 で!ということで。