本記事について
本記事では、Red Hat から提供されている構成管理ツールである Ansible を使用して、ネットワーク機器である Cisco IOS 機器のコマンドログを取得しファイルに保存する方法の一例を説明します。
動作確認環境
本記事は以下の環境にて検証した結果に基づき作成されています。
- Ansible 環境
- CentOS Stream release 9
- Python 3.12.6
- pip 24.2
- ansible-core 2.17.4
- Ansible community version 10.4.0
- cisco.ios 8.0.0
- ターゲット機器環境
- Cisco C891FJ-K9
- Cisco IOS 15.8(3)M9
- Cisco Catalyst2960-8TC-L
- Cisco IOS 12.2(55)SE12
- Cisco C891FJ-K9
前提
本記事では、Cisco IOS 機器を Ansible を使用して設定する方法について記載します。Ansible でターゲット機器を操作するためには、Ansible からターゲット機器に対して、SSH 接続できる必要があります。このため、Cisco IOS 機器側では以下の最低限の設定がされていることを前提とします。
- ホスト名設定
- ドメイン名設定
- RSA鍵の作成
- SSHログイン用ユーザの設定
- enableパスワードの設定
- SSH接続を受け付けるインターフェースのIPアドレス設定
- ルーティング設定(必要な場合)
- line vty の設定
- login local
- transport input ssh/all
ここでは Cisco IOS 機器を対象としています。同じ Cisco 機器でも OS の異なる Nexus や ASA 等は対象外となります。
また Ansible では Cisco IOS 機器に SSH 接続するための初期設定が完了していることを前提とします。
CentOS Stream 9 での Ansible のインストール方法についてはこちらの記事を参照してください。
想定するネットワーク構成
ここでは以下のようにターゲットとなる Cisco IOS 機器と Ansible マシンが L2 ネットワークで接続されている環境で操作を行うことを想定します。
使用する Ansible モジュールと出力イメージ
本記事では Cisco IOS 機器のコマンドログを取得するために、cisco.ios コレクションの ios_command モジュールを使用します。
cisco.ios コレクションは、ansible パッケージをインストールするとデフォルトで含まれています。
cisco.ios コレクションがインストールされているかどうかはansible-galaxy collection list
コマンドで確認できます。以下のようにcisco.ios
が表示されていればインストールされています。
(venv) [root@CentOSST9 ~]# ansible-galaxy collection list
# /root/venv/lib/python3.12/site-packages/ansible_collections
Collection Version
---------------------------------------- -------
amazon.aws 8.2.1
ansible.netcommon 6.1.3
ansible.posix 1.5.4
ansible.utils 4.1.0
ansible.windows 2.5.0
(中略)
cisco.aci 2.10.1
cisco.asa 5.0.1
cisco.dnac 6.18.0
cisco.intersight 2.0.17
cisco.ios 8.0.0
cisco.iosxr 9.0.0
cisco.ise 2.9.3
(以下略)
またログテキストをファイルに保存するために ansible.builtin コレクションの blockinfile モジュールを使用します。ios_command モジュールで取得されるコマンドログは IOS の CLI のプロンプト部分までは含まれず出力ログのみになるため、本記事ではどのコマンドのログなのかが分かりやすいように blockinfile モジュールのマーカー部分に実行コマンドを表示するように設定することにします。
保存されるログファイルの内容は以下のような形式になります。
The hostname is Router01.
# =========================== Start: show clock ===========================
"20:10:47.492 JST Fri Sep 27 2024"
# =========================== End : show clock ===========================
# =========================== Start: show version ===========================
"Cisco IOS Software, C800 Software (C800-UNIVERSALK9-M), Version 15.8(3)M9, RELEASE SOFTWARE (fc1)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2022 by Cisco Systems, Inc.
(中略)
Configuration register is 0x2102"
# =========================== End : show version ===========================
# =========================== Start: show running-config ===========================
"Building configuration...
Current configuration : 2453 bytes
!
! No configuration change since last restart
! NVRAM config last updated at 10:39:40 JST Fri Sep 27 2024 by admin
!
version 15.8
(以下略)
ログファイルは Playbook を実行するごとに以下のように実行した日付・時刻毎のフォルダ内に保存されるようにします。
log
20240927-212725
n-n_Router01_20240927.txt
n-n_Switch01_20240927.txt
20240927-213732
Router01_20240927.txt
Switch01_20240927.txt
20240927-213918
1-1_Router01_20240927.txt
1-1_Switch01_20240927.txt
ログ取得対象コマンドリストの指定方法
本記事では、機器ごとのログ取得コマンドリストをインベントリの中でホストグループ変数としてリスト型変数で指定することとします。ログ取得コマンドリストが同じ機器は同じホストグループに分類します。
インベントリの作成形式として、標準の形式だとリスト型変数の定義に適さないため、本記事ではYAML形式でインベントリを作成することにします。
インベントリの作成
本記事では hosts_getlog というファイル名でYAML形式のインベントリを作成します。
内容例は以下の通りです。
all:
hosts: #←いずれのホストグループにも属さないホストはこの配下に記載。本例ではこのようなホストは無し
vars: #←全ホスト共通の変数はこの配下に記載
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.ios.ios
ansible_become: yes
ansible_become_method: enable
fname_prefix: "n-n_" #←保存するログファイル名の頭に付与する文字列を定義(項目番号など)
children: #←ホストグループはchildren配下に記載
ios_rt: #←本例ではIOSルータ用ホストグループ
hosts: #←このホストグループに所属するホストはhosts配下に記載
RT01: #←個別ホストの定義と、ホスト固有変数の定義
ansible_host: 10.1.1.1
ansible_user: admin
ansible_password: admin
ansible_become_password: admin
vars: #←ホストグループ変数
cmd_list: #←このホストグループで使用するコマンドリストをリスト型変数で定義
- show clock
- show version
- show running-config
- show inventory
- show env all
- show ip route
ios_sw: #←本例ではIOSスイッチ用ホストグループ
hosts: #←このホストグループに所属するホストはhosts配下に記載
SW01: #←個別ホストの定義と、ホスト固有変数の定義
ansible_host: 10.1.1.50
ansible_user: admin
ansible_password: adminadmin
ansible_become_password: admin
vars: #←ホストグループ変数
cmd_list: #←このホストグループで使用するコマンドリストをリスト型変数で定義
- show clock
- show version
- show running-config
- show inventory
- show env all
このインベントリ例では、「ios_rt」「ios_sw」の2つのホストグループを定義し、ホストグループ内に対象ホストを定義しさらにグループ変数として実行コマンドリストを示すリスト型の変数「cmd_list」を定義しています。
コマンドリストを示す「cmd_list」の変数名をこの後作成する Playbook で使用します。この変数名は変えることができますが、その場合は Playbook 内で記載する変数名も合わせて変更する必要があります。
ホストグループ、ホスト、コマンドリスト、ログファイル名接頭辞用変数(fname_prefix)は実行する環境や状況に合わせて変更してください。
ログファイル名接頭辞用変数(fname_prefix)は全ホスト共通変数として定義していますが、機器ごとに変えたい場合は変数の定義位置をグループ変数やホスト変数の位置に変更してください。
ログファイル名に接頭辞を付けたくない場合は「fname_prefix: “”」と定義してください。
Playbook の作成
本記事では ios_getcmdlog.yml というファイル名で Playbook を作成します。
内容例は以下の通りです。なお、基本的に変数はすべてインベントリで定義する設計にしているので、細かいカスタマイズを必要とする場合以外は以下の内容をそのままコピペで問題ありません。
- name: Get Command Log
hosts: all
gather_facts: no
tasks:
- name: STEP1_Set Date Time
set_fact:
date: "{{ now(False, '%Y%m%d') }}"
datetime: "{{ now(False, '%Y%m%d-%H%M%S') }}"
run_once: true
- name: STEP2_Gather Facts
cisco.ios.ios_facts:
gather_subset: min
- name: STEP3_Set File Path
set_fact:
filepath: "./log/{{ datetime }}/{{ fname_prefix }}{{ ansible_net_hostname }}_{{ date }}.txt"
- name: STEP4_Execute Commands
cisco.ios.ios_command:
commands: "{{ item }}"
register: command_log
with_items: "{{ cmd_list }}"
ignore_errors: true
- name: STEP5_Create File and Add Hostname
lineinfile:
path: "{{ filepath }}"
line: "The hostname is {{ ansible_net_hostname }}."
create: yes
- name: STEP6_Add Command Log
blockinfile:
path: "{{ filepath }}"
marker: "# {mark}"
marker_begin: "=========================== Start: {{ item.item }} ==========================="
marker_end: "=========================== End : {{ item.item }} ==========================="
prepend_newline: true
block: |
"{{ item.stdout | join('\n') }}"
create: yes
with_items:
- "{{ command_log.results }}"
loop_control:
label: "{{ item.item }}"
when: item.stdout is defined
各タスクの内容は以下の通りです。
- name: STEP1_Set Date Time
- 保存するログファイルパスに使用する現在日時と現在日時・時刻を変数に保存するタスクです
- set_fact:
- 変数を定義することができるモジュールです
- date: “{{ now(False, ‘%Y%m%d’) }}”
- 現在時刻を取得し「YYYYMMDD」形式の日付を格納した変数 date を定義しています
- datetime: “{{ now(False, ‘%Y%m%d-%H%M%S’) }}”
- 現在時刻を取得し「YYYYMMDD-HHMMSS」形式の日付・時刻を格納した変数 date を定義しています
- run_once: true
- この指定により対象ホストが複数ある場合でも最初の1ホストのみに対してのみこのタスクを実行するようになります
- このタスクで取得する現在時刻を保存するファイルパスに使用しますが、機器ごとにこの処理を行うと取得される時刻がずれてファイルパスが機器別に分かれてしまいます。同一フォルダ内に全ログファイルを保存するためにこの指定をしています
- name: STEP2_Gather Facts
- 保存するログファイル名に使用する機器ホスト名を取得するタスクです
- cisco.ios.ios_facts:
- Cisco IOS 機器から様々な情報を取得することができるモジュールです
- gather_subset: min
- この指定で機器ホスト名を含む情報を取得できます
- ホスト名は変数
ansible_net_hostname
に格納されます
- name: STEP3_Set File Path
- 保存するログファイルパスを変数として定義するタスクです
- set_fact:
- 変数を定義することができるモジュールです
- filepath: “./log/{{ datetime }}/{{ fname_prefix }}{{ ansible_net_hostname }}_{{ date }}.txt”
- Playbook と同フォルダ内に log フォルダを作成し、更にそのフォルダ内に日付時刻を名前としたフォルダを作成して、日付時刻フォルダ内に機器別にログファイルを保存することにしています。このようなファイルパスを格納した変数 filepath を定義しています
- {{ datetime }}と{{ date }}は STEP1 のタスクで定義した日付・時刻の変数です
- {{ fname_prefix }} はインベントリで定義したファイル名接頭辞に該当する変数です
- {{ ansible_net_hostname }} は STEP2 のタスクで取得したホスト名です
- name: STEP4_Execute Commands
- Cisco IOS 機器でコマンドを実行するタスクです
- cisco.ios.ios_command:
- Cisco IOS 機器でコマンドを実行できるモジュールです
- commands: “{{ item }}”
- 実行するコマンドを指定しています。コマンド毎にループ処理としているため“{{ item }}”でコマンドを指定しています
- register: command_log
- コマンドログを含むコマンド実行結果を変数 command_log に保存します
- with_items: “{{ cmd_list }}”
- インベントリで定義したコマンドリストを示すリスト型の変数 cmd_list を使用してループ処理を行っています
- ignore_errors: true
- この指定をすると Playbook 実行時に発生したエラーを無視して処理を実行できます
- コマンドリストとして対象機器で実行できないコマンドが指定されていた場合、cisco.ios.ios_command モジュールの結果としてエラーになり、Playbook の処理が停止してしまいます。実行できないコマンドがあった場合でもエラーを無視して処理を続行するためにこの指定をしています
- name: STEP5_Create File and Add Hostname
- ログファイルを作成すると同時にログファイルの一行目にホスト名情報を記載するタスクです
- lineinfile:
- ファイルの行操作ができるモジュールです
- path: “{{ filepath }}”
- 操作対象ファイルのパスを指定しています。filepath は STEP3 で定義した変数です
- line: “The hostname is {{ ansible_net_hostname }}.”
- ホスト名を示す行を対象ファイルに挿入しています。ansible_net_hostname は STEP2 のタスクで取得したホスト名が格納されている変数です
- create: yes
- 指定のパスにファイルが存在しない場合ファイルを新規作成します
- name: STEP6_Add Command Log
- 取得したコマンドログを1コマンド分ずつログファイルに追記するタスクです
- blockinfile:
- ブロック(複数行のテキスト)をファイルに追記できるモジュールです
- path: “{{ filepath }}”
- 操作対象ファイルのパスを指定しています。filepath は STEP3 で定義した変数です
- marker: “# {mark}”
- 追記するブロックの上下に追記されるマーカーの形式を指定しています
- marker_begin: 及び marker_end:
- 追記するブロックの上下に追記されるマーカーの内容を指定しています
- item.item は対象のコマンドを示しています
- prepend_newline: true
- 追記するブロックの上に空白行を挿入しています
- block:
- 追記するブロックの内容を示しています
- item.stdout は対象のコマンドの出力ログを示しています
- join(‘\n’) ログ1行ごとに改行するため指定しています
- create: yes
- 指定のパスにファイルが存在しない場合ファイルを新規作成します
- with_items: – “{{ command_log.results }}”
- command_log は STEP4 のタスクで定義したコマンド実行結果を格納した変数です
- 各コマンドの実行結果は command_log の results 属性内にリストとして格納されているため、このリストを利用してループ処理を行っています
- コマンドログは stdout 属性に格納されるため、block: で item.stdout と指定しています
- 実行コマンドは item 属性に格納されるため、marker_begin: 及び marker_end: で item.item と指定しています
- loop_control:
- label: “{{ item.item }}”
- with_items: でループ処理を行った場合、タスクの実行結果ログとしてループで使用する変数の値(コマンド実行結果情報)が表示されますが、コマンド実行結果情報にはコマンドログがそのまま含まれるため、タスク実行結果ログが非常に長いものになってしまいます。表示されるタスク実行結果ログに表示される変数値を実行コマンドのみにするために loop_control: にて label: “{{ item.item }}” と指定しています
- when: item.stdout is defined
- このタスクの実行条件を item.stdout(コマンド実行ログを格納する属性)が定義されていることとして指定しています
- コマンドリストの中に対象機器で実行できないコマンドが含まれていた場合、stdout 属性は定義されません。stdout 属性は定義されていない状態でこのタスクを実行すると、stdout 属性が存在しないというエラーになり Playbook の処理が停止してしまいます。実行できなかったコマンドについてはログ保存処理をスキップするために when: を指定しています
Playbook の実行
以下コマンドで Playbook を実行します。
- ansible-playbook -i <インベントリ> <Playbook>
以下は実行例です。
(venv) [root@CentOSST9 ansible]# ansible-playbook -i hosts_getlog ios_getcmdlog.yml
PLAY [Get Command Log] **********************************************************************************************
TASK [STEP1_Set Date Time] ******************************************************************************************
ok: [RT01]
TASK [STEP2_Gather Facts] *******************************************************************************************
ok: [RT01]
ok: [SW01]
TASK [STEP3_Set File Path] ******************************************************************************************
ok: [RT01]
ok: [SW01]
TASK [STEP4_Execute Commands] ***************************************************************************************
ok: [RT01] => (item=show clock)
ok: [SW01] => (item=show clock)
ok: [RT01] => (item=show version)
ok: [SW01] => (item=show version)
ok: [RT01] => (item=show running-config)
ok: [SW01] => (item=show running-config)
ok: [RT01] => (item=show inventory)
ok: [SW01] => (item=show inventory)
ok: [RT01] => (item=show env all)
ok: [SW01] => (item=show env all)
ok: [RT01] => (item=show ip route)
TASK [STEP5_Create File and Add Hostname] ***************************************************************************
changed: [RT01]
changed: [SW01]
TASK [STEP6_Add Command Log] ****************************************************************************************
changed: [SW01] => (item=show clock)
changed: [RT01] => (item=show clock)
changed: [RT01] => (item=show version)
changed: [SW01] => (item=show version)
changed: [RT01] => (item=show running-config)
changed: [SW01] => (item=show running-config)
changed: [RT01] => (item=show inventory)
changed: [SW01] => (item=show inventory)
changed: [RT01] => (item=show env all)
changed: [SW01] => (item=show env all)
changed: [RT01] => (item=show ip route)
PLAY RECAP **********************************************************************************************************
RT01 : ok=6 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
SW01 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
実行できないコマンドが含まれていた場合、以下のように STEP4 でエラーになりますが処理は続行されます。STEP6 ではエラーになったコマンドについては処理がスキップされます。
(venv) [root@CentOSST9 ansible]# ansible-playbook -i hosts_getlog ios_getcmdlog.yml
PLAY [Get Command Log] **********************************************************************************************
TASK [STEP1_Set Date Time] ******************************************************************************************
ok: [RT01]
TASK [STEP2_Gather Facts] *******************************************************************************************
ok: [RT01]
ok: [SW01]
TASK [STEP3_Set File Path] ******************************************************************************************
ok: [RT01]
ok: [SW01]
TASK [STEP4_Execute Commands] ***************************************************************************************
ok: [RT01] => (item=show clock)
ok: [SW01] => (item=show clock)
ok: [RT01] => (item=show version)
ok: [SW01] => (item=show version)
failed: [RT01] (item=show hogepiyo) => {"ansible_loop_var": "item", "changed": false, "item": "show hogepiyo", "msg": "show hogepiyo\r\nshow hogepiyo\r\n ^\r\n% Invalid input detected at '^' marker.\r\n\r\nRouter01#"}
ok: [SW01] => (item=show running-config)
ok: [RT01] => (item=show running-config)
ok: [SW01] => (item=show inventory)
ok: [RT01] => (item=show inventory)
ok: [SW01] => (item=show env all)
ok: [RT01] => (item=show env all)
ok: [RT01] => (item=show ip route)
...ignoring
TASK [STEP5_Create File and Add Hostname] ***************************************************************************
changed: [SW01]
changed: [RT01]
TASK [STEP6_Add Command Log] ****************************************************************************************
changed: [RT01] => (item=show clock)
changed: [SW01] => (item=show clock)
changed: [RT01] => (item=show version)
skipping: [RT01] => (item=show hogepiyo)
changed: [SW01] => (item=show version)
changed: [RT01] => (item=show running-config)
changed: [SW01] => (item=show running-config)
changed: [RT01] => (item=show inventory)
changed: [SW01] => (item=show inventory)
changed: [RT01] => (item=show env all)
changed: [SW01] => (item=show env all)
changed: [RT01] => (item=show ip route)
PLAY RECAP **********************************************************************************************************
RT01 : ok=6 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
SW01 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Playbook 実行後、以下のように実行毎に日時フォルダが作成され、そのフォルダ内に機器別にログファイルが保存されます。
log
20240927-224446
1-1_Router01_20240927.txt
1-1_Switch01_20240927.txt
20240927-224600
1-1_Router01_20240927.txt
1-1_Switch01_20240927.txt
20240927-231331
1-1_Router01_20240927.txt
1-1_Switch01_20240927.txt
20240927-231519
1-1_Router01_20240927.txt
1-1_Switch01_20240927.txt
参考資料
Ansible 関連記事一覧
- Ansible インストール
- Cisco IOS 操作
- Cisco Nexus スイッチ操作
Amazon アフィリエイトリンク
以下は Amazon アフィリエイトリンクです。インフラエンジニアにそこそこおすすめなアイテムです。
note メンバーシップへの参加もお待ちしています!
【アフィリエイト】おすすめ WordPress テーマ【SWELL】
当サイトでは WordPress テーマとして SWELL を使用しています。以前は無料・高機能テーマとして知られる Cocoon を使用していて Cocoon も使いやすかったのですが、SWELL を使い始めてからは SWELL のほうが圧倒的に使いやすいなと思いました。そして何より読み込み速度が速い。SWELL を使い始めてから、過去の Cocoon のブログを見直したときに「あれ、こんなに表示遅かったっけ?」という感覚になりました。
また SWELL はデフォルトでもオシャレなデザインですが柔軟にカスタマイズすることもでき個性のあるサイトを作成できます。さらにブログパーツや広告タグといった再利用可能なブログの「部品」も作成することができ、ブログ作成効率も高いです。
技術ブログやアフィリエイト等での収益化を見据えたブログの作成に SWELL は最適です。初見では価格が高いなと思うと思いますが、私としては SWELL を採用して良かったしそれ以上の価値があると感じています。
ブログの新設やテーマ変更を考えている人は一度 SWELL を検討してみてください。
以下の画像リンクから詳細な情報を確認できます。
レンタルサーバーを探している人には安定性に定評のあるエックスサーバーをお勧めします。
当サイトもエックスサーバーを使用しています。WordPress のインストールも簡単にできます。
コメント