お伝えしたいこと
この記事はAnsible Advent Calendar 2024の11日目の記事です。
AnsibleでLinuxやクラウド、ネットワーク機器の設定変更を自動化できるのは周知のとおりですが、情報取得の機能も充実しています。
充実...していますが、機能が多すぎて「やりたいこと1つに対して実現手段が2つも3つも存在する」状況になっています。
一体私たちはどの機能を使いたいのでしょうか?
私自身あまり理解できていなかったので、この場でまとめてみたいと思います。
サマリ表
下表にNW機器のステータス情報を取得する方法をまとめます。
取得できる情報は大きく分けて4種類あります。
もっと大きく分けると、(1)の文字列データと(2-1)〜(2-3)の構造化データの2つに大別できます。
(※) 本記事では例示にArista EOSを使います
自分が使いたい機能を見つける
以降のセクションでは、いくつかの具体的なユースケースに基づいて上表の機能を選択し、実行結果を示していきます。
少しでもイメージが湧きやすくなれば幸いです。
未加工のshowコマンドログを取得する
showコマンドの出力をそのまま見たりファイルに保存したいときは、#サマリ表の 「(1) showコマンドそのまま」 の機能を使用します。
ユースケース (1) showコマンドを未加工で保存したいのはどんな場面?
ネットワーク機器のshowコマンド出力結果を加工せずそのままファイルに保存したいケースとして、私の中では以下の2点が思い当たります。
- 作業やメンテナンス前後で対象機器にshowコマンドをAnsibleで一括実行し、実行結果をファイル保存する。ファイルを目視で確認して作業の成否を判断する (コマンド実行のみ自動化、確認はマニュアル)
- Ansibleでコンフィグを定期的に一括取得・ファイル保存する。作業者は必要なときにコンフィグを参照し、設計業務などに活用する
これらのユースケースを満たすAnsibleの具体的な実装方法を次のセクションで見ていきましょう。
実行例 (1) arista.eos.eos_command module
arista.eos.eos_command moduleでshowコマンドを実行し、結果を記録する例を示します。
今回はshow running-config
を実行し、ネットワーク機器のコンフィグを表示します。
例示ではansible.builtin.debug
モジュールでコマンド出力を画面表示していますが、実際に業務で使う際はansible.builtin.copy
モジュールなどに置き換えてファイル保存する想定です。
- name: Retrieve Arista EOS config hosts: eos gather_facts: false tasks: - name: Execute show running-config arista.eos.eos_command: commands: - show running-config register: _eos_show_running_config_result - name: Debug _eos_show_running_config_result ansible.builtin.debug: msg: '{{ _eos_show_running_config_result.stdout }}'
以下に実行結果を示します。
TASK [Debug _eos_show_running_config_result] *** ok: [veos1] => msg: - |- ! Command: show running-config ! device: veos1 (vEOS-lab, EOS-4.26.5M) ! ! boot system flash:/vEOS-lab.swi ! enable password sha512 $6$xxxxxx # マスクしました no aaa root # ...
showコマンドログを構造化データに変換し、活用する
showコマンドの実行結果を構造化データに変換したいときは、#サマリ表の(2-1)〜(2-3)の機能を使用します。
- (2-1) パーサ利用
- (2-2) network resource module
- (2-3) gather_subset
(参考) 構造化データへの変換のイメージ
「構造化データへの変換」のイメージを補足として説明します。
既に知っている方は読み飛ばしてください。
「構造化データへの変換」がどのようなものかを理解するには、実際に動いているところを見るのが一番です。
以下のネットワーク構成を例にします。
Arista vEOSスイッチが3台接続されています。
veos1
でshow interface description
を実行し、インターフェースのステータスを確認します。
Interface Status Protocol Description Et1 up up veos2 Et1 Et2 up up veos3 Et1
これを構造化データに変換すると、以下のようなデータ構造になります。
利用するパーサによってデータ構造は変わりますが、基本的にはlist
とdict
のネスト構造になります。
例えばntc_templates
やTextFSM
を利用した場合は以下のようなデータ構造になります。
(※) パーサとは、文字列を構造化データとして解釈するプログラムのことです。本記事の文脈では文字列を構造化するプログラムと思って差し支えありません。ntc_templates
やTextFSM
は正規表現で実装されています
interfaces: - port: Et1 status: up protocol: up description: veos2 Eth1 - port: Et2 status: up protocol: up description: veos3 Eth1
構造化することで「特定の値を取り出す」ことが非常に容易になります。
例えばEt1
のstatus
は{{ interfaces.0.status }}
や{{ (interfaces.selectattr("port", "eq", "Et1"))["status"] }}
で取り出せます。
後者の参照方法は一見すると難しく思えるかもしれません。
しかし「同じパーサを使う限りどのshowコマンドでも基本的に同じ方法でデータを取り出せる」ので、分析対象のshowコマンドが今後増えた場合も同じフィルタを使い回せます。
構造化されていないデータを都度grep
やansible.builtin.regex_search filterで都度整形するよりも遥かに少ない手間でデータを構造化できます。
...話が少し逸れましたが、データを構造化できれば夢がかなり広がります。
構造化されたデータを変数に格納して追加のロジックを組めるのです。
例えば、以下のような処理が考えられます。
- Et1がupなら何もせず、downならエラーメッセージを出力する (→ showコマンドの実行だけでなく確認まで自動化できる)
- ansible.builtin.templateモジュールで表やレポートを自動生成 (→ ステータス可視化、レポート自動生成)
2の具体例を挙げます。
上述のlist of dict
形式に構造化されたデータをinputに、template
モジュールを使えば以下のmarkdownを容易に生成できます。
(※) 余談ですが、list of dictは本質的には表と同じ構造のデータなのです。なので表形式に変換することは本当に簡単です
| port | status | protocol | description | | ---- | ------ | -------- | ----------- | | Et1 | up | up | veos2 Eth1 | | Et2 | up | up | veos3 Eth1 |
適切なツールでmarkdownをhtml化すると、以下のような見た目の表になります。
showコマンドログを構造化し、整形することで見やすい表を生成できました。
ユースケース (2) showコマンドを構造化したいのはどんな場面?
#構造化データへの変換のイメージで既に触れたとおり、私が思いつくユースケースは以下の2点です。
- コマンド実行だけでなく、実行結果の確認まで自動化する
- コマンド実行結果を整形してレポートや表形式で出力する
実行例 (2-1) パーサを使う場合
さて、Ansibleでネットワーク機器のステータス情報をパースする手段は3つあります。
順番に説明していきますが、まずは私が個人的に一番好んでいる「(2) パーサ利用」について説明します。
パーサを使う場合、基本的にはansible.utils.cli_parseモジュールを使います。
以下にサンプルを示します。
show interfaces description
をパースするplaybookです。
今回はntc_templates
パーサを利用しているので、動作にはntc_templates
Pythonパッケージが必要です。
- name: Parse EOS show commands hosts: eos gather_facts: false tasks: - name: Parse show interfaces description ansible.utils.cli_parse: command: show interfaces description parser: name: ansible.netcommon.ntc_templates set_fact: _show_interfaces_description_parsed - name: Debug _show_interfaces_description_parsed ansible.builtin.debug: msg: '{{ _show_interfaces_description_parsed }}'
実行結果は以下のようなイメージになります。
TextFSMは必ずlist[dict]
(list of dicts) というデータ型に構造化します。
したがって、showコマンドが変わってもそこそこ画一的な方法でデータを加工できることが利点だと私は考えています。
TASK [Debug _show_interfaces_description_parsed] *** ok: [veos1] => msg: - description: veos2 Et1 port: Et1 protocol: up status: up - description: '' port: Et2 protocol: up status: up # ...
ご参考までに、show interfaces description
の実行ログも再掲します。
Interface Status Protocol Description Et1 up up veos2 Et1 Et2 down down veos3 Et1
ansible.utils.cli_parse module以外にも、似た機能を持つフィルタとしてansible.netcommon.parse_cli filterやansible.netcommon.cli_parse_textfsm filterが存在します。
これらのフィルタはcli_parse
モジュールよりも先にリリースされた経緯があり使われたこともありますが、対応しているパーサも限定的ですし現在ではあまり使う理由がないと思います。
フィルタについては実行例を割愛します。
実行例 (2-2) network resource moduleを使う場合
network resource moduleが存在するOSの場合、この方法が使えます。
#実行例 (2-1) パーサを使う場合と比較するとパーサ導入が不要であるため、お手軽に使い始めることができます。
ntc_templatesやTextFSMは「CLIで得られる情報であれば全て構造化できる」汎用性と、「必ずlist[dict]
形式でデータを返す」統一感があるので、私個人としてはパーサ利用に統一するのが好きです。
このあたりは宗教戦争です。
同じパース結果を得られる書き方が私の知る限り3通り存在するので、順番に挙げていきます。
1つ目の書き方はarista.eos.eos_facts moduleを呼び出す方法です。
Factsを収集すると、ansible_facts
変数に自動的に格納されます。
- name: Test gather facts on EOS hosts: eos gather_facts: false tasks: - name: Gather facts arista.eos.eos_facts: gather_network_resources: - interfaces - l2_interfaces - l3_interfaces - name: Debug _eos_facts_result ansible.builtin.debug: msg: '{{ ansible_facts }}'
実行結果は以下の通りです。
network_resources
の配下がnetwork resource moduleの機能で収集した情報です。
Factsを使うと、network_resource_module以外のFacts情報も勝手に収集されます。
gather_subset
パラメータに!all
を指定してもmin
に相当する情報は最低限収集されます。
TASK [Debug _eos_facts_result] *** ok: [veos1] => msg: net_api: cliconf net_fqdn: veos1 # ... net_system: eos net_version: 4.26.5M network_resources: interfaces: - description: veos2 Et1 enabled: true name: Ethernet1 # ... l2_interfaces: - mode: trunk name: Ethernet1 trunk: trunk_allowed_vlans: - '2' # ... l3_interfaces: # ... - ipv4: - address: 192.168.0.11/24 name: Management1
2つ目の書き方は、network resource moduleのstate: gathered
を指定する方法です。
変数格納するにはregister
キーワードが必要です。
1つのFactsだけを取得するときはこの書き方でも良いと思います。
複数のFactsを収集する場合は、arista.eos.eos_facts
の方がコンパクトに書けると思います。
- name: Gather EOS Facts hosts: eos gather_facts: false tasks: - name: Gather interfaces Facts arista.eos.eos_interfaces: state: gathered register: _eos_interfaces_result - name: Debug _eos_facts_result ansible.builtin.debug: msg: '{{ _eos_interfaces_result.gathered }}'
3つ目の書き方は、gather_facts
を指定する方法です。
書き方がやや特殊で、個人的に覚えにくいです。
またFactsの収集タイミングがPlayの冒頭に固定されます。
私の環境では実機検証できていませんが、module_defaults
の配下にeos
以外のOSについてもデフォルト値を指定することで複数OS分のデータ取得を1つのPlayでスッキリ表現できることがこの書き方のメリットなのだと推測します。
Gathering facts from network devices
- name: Test gather facts on EOS hosts: eos gather_facts: true module_defaults: arista.eos.eos_facts: gather_network_resources: - interfaces - l2_interfaces - l3_interfaces tasks: - name: Debug _eos_facts_result ansible.builtin.debug: msg: '{{ ansible_facts }}'
(参考) cli_parseモジュールの概要説明
cli_parse
モジュールは、文字列をパースして構造化データを変数格納する機能を持ちます。
cli_parse
モジュールを実行するには以下のような情報が必要となります。
構造化に使用するパーサ名をパラメータに指定する必要があり、選択可能なパーサとして以下の選択肢があります。
- ntc_templates
- TextFSM
- TTP (dmulyalin/ttp_templatesにテンプレートサンプルあり)
- PyATS
- Ansible Native Parsing Engine
パーサが複数あるとどれを使うか迷うと思います。
一概には言えませんが、「使い慣れたパーサを使いたい」、「日本語情報が多いものを使いたい」、「自分の現場にはCisco機器が多い」など様々な事情を総合して選定するものと想像します。
ご参考までに、私個人としてはansible.netcommon.ntc_templates
とansible.utils.textfsm
を好んで使います。
ntc_templates
については主要なネットワークOSのパーサ定義は既にある程度揃っていて、GitHub上に公開されています (networktocode/ntc-templates - /ntc_templates/templates)。
自分の欲しいパーサテンプレートがGitHub上に公開されていれば、まずはそれを使ってみます。
もしntc-templates
のリポジトリに既存のパーサ定義が存在しなかったり、既存のパーサ定義とは異なるデータ構造が欲しかった場合にはTextFSMテンプレートを自分で記述しつつansible.utils.textfsm
を使います。
テンプレートファイルの文法はntc-templates
と同様なので、他のテンプレートファイルの書き方を参考にできます。
テンプレートファイルの文法を調べる際は、TextFSMのWiki情報やるつぼっとの記事がおすすめです。
その他、使い方の詳細はansible.utils.cli_parseのパラメータ一覧や実行例が参考になります。
実行例 (2-3) gather_subsetを使う場合
network resource moduleがAnsibleに登場する前から、eos_facts
モジュールなどネットワーク機器からFactsを収集する機能は存在していました。
ややこしいですが、この機能はgather_subset
というキーワードとしてまだ残っています。
network resource moduleの方が機能としては後発ですし、恐らくgather_subset
周りの機能が今後拡充されることはないと思います。
かといって廃止の予告は今のところないので、network resource moduleで取得できない情報の中でgather_subset
で取れる情報があれば、選択肢に入れてみても良いかもしれません。
くどいようですが私個人としては#実行例 (2-1) パーサを使う場合で全て賄ってしまうのが今のところは好きです。
とはいえパーサの使い方を学習するコストもありますし、一概にそれが正義だとは言えません (宗教戦争です)。
今回もFacts機能のため、書き方としてはeos_facts
とgather_facts
の2通りがあります。
1つずつ紹介していきます。
まずは1つ目のarista.eos.eos_factsを利用したplaybookのサンプルです。
- name: Test gather facts on EOS hosts: eos gather_facts: false tasks: - name: Gather facts arista.eos.eos_facts: gather_subset: - interfaces - name: Debug _eos_facts_result ansible.builtin.debug: msg: '{{ ansible_facts }}'
実行結果は以下の通りです。
なんだかshow interfaces
をパースした感がありますね。
TASK [Debug _eos_facts_result] *** ok: [veos1] => msg: # ... net_interfaces: Ethernet1: bandwidth: 1000000000 description: veos2 Et1 duplex: duplexFull ipv4: {} lineprotocol: up macaddress: 0c:65:34:fe:00:01 mtu: 9214 operstatus: connected type: bridged # ... network_resources: {}
続いて2つ目のgather_facts
を利用した書き方です。
Factsの取得タイミングがPlayの冒頭に固定されるものの、ネットワーク機器のOSの差分を吸収できる書き方にはなってそうです。
「AnsibleをOSごとの操作手順の違いを吸収する抽象化レイヤーとして使用する」考え方、昔は流行ってましたね。
抽象化の利点について聞くことは最近減ってきましたが、gather_facts
を使用する唯一のモチベーションはそこなんじゃないかと思っています。
- name: Test gather facts on EOS hosts: eos gather_facts: true gather_subset: - interfaces tasks: - name: Debug _eos_facts_result ansible.builtin.debug: msg: '{{ ansible_facts }}'
おわりに
Ansibleのネットワーク機器の情報を集める方法は本当にたくさんあります。
showコマンドログを保存するだけなら迷いませんが、パースの手段は本当にたくさんあります。
ですが、迷ったらパーサかnetwork resource module系の機能を使いましょう。
言い換えると、#サマリ表の(2-3)は理由がなければ使わなくて良いと思います。
(Aristaのnetwork resource moduleはlldp_interfaces
の情報をなぜか収集できないので、show lldp neighbors
相当の情報をパースしたいときはcli_parse
かgather_subset
のどちらかを使う必要があるという話はありますがそれはそのうち修正される小さなissueです)
結局のところは宗教戦争ですが、この記事がパース手段の選択の一助となれば幸いです。