はじめに
本記事では、Ansibleネットワークモジュールの内、showコマンド等を実行するための"xxx_command"モジュールに焦点を当てています。記事内で紹介している自作モジュールを、「このモジュールおすすめ!」と言いたかったんですが、まだまだという感じなので、自作モジュール作成の一例として読んでやって下さい!
command系モジュールについて
Ansible 2.6以前では、ios_command
、nxos_command
、junos_command
等、各ベンダーのOSタイプ固有のモジュールが存在していました。2.7からは、加えてOS非依存のcli_command
がリリースされています。
各モジュールの特徴は以下の通りです。
モジュール名 | モジュール | Inventory内のグループ |
---|---|---|
ios_command 等 | OSタイプ毎に用意する必要あり | OSタイプ毎に分ける必要あり |
cli_command | 一つでOK | OSタイプ毎に分ける必要あり |
cli_command
の場合でも、OSの違いを全く意識しなくて良い訳ではなく、以下のようにInventory内でansible_network_os
を指定する必要があります。(調べた限り、サードパーティーが出しているマルチベンダー対応モジュールも同様でした。)
[ios]
192.168.100.200
[ios:vars]
ansible_connection=network_cli
ansible_network_os=ios #OSを指定
ansible_user=test
ansible_password=test
[nxos]
192.168.100.201
[nxos:vars]
ansible_connection=network_cli
ansible_network_os=nxos #OSを指定
ansible_user=test
ansible_password=test
自作モジュールを作った背景
Cisco機器を扱うことが多く、実行するshowコマンドは同じなのに、OSタイプ(IOS、NX-OS、ASA、IOS-XR)毎にグループ分けするのはちょっと面倒だなーとか、不慣れなメンバーにとってはこれが若干ハードルかなーという思いがありました。
そこで、OSを指定せず、自動検出してコマンドを実行してくれるモジュールを作れないか、考えてみました。
別件でたまたま、netmiko
というPythonライブラリの、デバイスタイプ(OS)自動検出機能を調査する機会があり、これをモジュールに組み込めば行けるかも!と思いました。
※netmikoや自動検出機能は、よこちさんのブログを参照頂ければと思います。
netmikoでネットワーク機器の種類を自動検出してコマンドを実行する
コマンド取得対象機器
以下の2種類の機器を用意しました。
ホスト名 | 機種 | OSタイプ | バージョン |
---|---|---|---|
test | Cisco CSR1000v | IOS ※1 | 15.6(1)S2 |
sbx-n9kv-ao | Cisco NX-OSv 9000 ※2 | NX-OS | 9.2(1) |
※1 厳密にはIOS-XEですが、Ansibleやnetmikoでの分類は「IOS」になります。
※2 今回は、Cisco DevNetのSandboxの一つである Open NX-OS Programmabilityを利用させて頂きました。
自作モジュールの内容
モジュール名は、netmikoを使ったcommandモジュールということで、netmiko_commandにしようと思いましたが、正式モジュールではないので思い改め、neko_command
としています (どうでもいい話ですね!)
モジュールはPythonで作成しており、大まかに以下の7つの処理を行っています。
- InventoryファイルやVariableファイルで指定した値を取得し、変数に格納します。ここで、値の指定が必須な場合は
required=True
とします。オプションの場合はrequired=False
とし、デフォルトで使う値をdefault=
で指定します。また、Playbook実行結果に表示したくないパラメータは、no_log=True
とします。 - 指定した値の内、
command
(実行するshowコマンドを指定)については、文頭が「show」から始まっているかチェックします。チェック結果がNGの場合、メッセージを出力してFailとなるようにします。※必須ではないです。 - netmikoを使ってSSHログインするための機器情報を指定。ここで、
device_type
はautodetect
(自動検出)としておきます。 - netmikoの
SSHDetect
を使い、デバイスタイプを自動検出。 - 検出されたデバイスタイプ(
cisco_ios
、cisco_nxos
等)をdevice_type
に再度格納。 - netmikoの
ConnectHandler
を使い、権限レベルに応じてenableパスワードを入力の上、command
で指定したshowコマンドを実行。 - 検出されたデバイスタイプ
best_match
とshowコマンド結果stdout
を、result
という辞書形式の変数に格納し、JSON形式に変換の上出力。
上記の内1(入力)と7(出力)は、多くのモジュールで共通かと思います(もちろんパラメータは違いますが)。また、今回は3~6でnetmikoを使用していますが、ログイン処理やコマンド取得はios_command等に含まれる既存ライブラリを用い、それ以降の処理を独自で実装する選択肢もあると思います。
from ansible.module_utils.basic import *
from netmiko import ConnectHandler
from netmiko.ssh_autodetect import SSHDetect
def main():
'''
Ansible module to get device type automatically and issue show command.
'''
# (1) store value from inventory file
module = AnsibleModule(
argument_spec=dict(
host=dict(required=True),
port=dict(default=22, required=False),
username=dict(required=True),
password=dict(required=True, no_log=True),
secret=dict(required=False, default='', no_log=True),
enable_mode=dict(required=False, default=False),
command=dict(type='str', required=True),
),
supports_check_mode=True
)
# (2) parse command
if module.supports_check_mode and not module.params['command'].startswith('show'):
module.fail_json(
msg='Only show commands are supported when using check_mode, not '
'executing %s' % module.params['command']
)
warnings = list()
result = {'changed': False, 'warnings': warnings}
# (3) define login information
remote_device = {
'device_type': 'autodetect',
'ip': module.params['host'],
'username': module.params['username'],
'password': module.params['password'],
'port' : module.params['port'], # optional, defaults to 22
'secret': module.params['secret'], # optional, defaults to ''
'verbose': False, # optional, defaults to False
}
# (4) autodetect device type
guesser = SSHDetect(**remote_device)
best_match = guesser.autodetect()
# (5) store the detected type in device_type
remote_device['device_type'] = best_match
# (6) issue show command
connection = ConnectHandler(**remote_device)
if module.params['enable_mode']:
connection.enable()
response = connection.send_command(module.params['command'])
# (7) return the output of show command
result['best_match'] = best_match
result['stdout'] = response
module.exit_json(**result)
connection.disconnect()
if __name__ == "__main__":
main()
次に、作成したPythonファイルを、モジュール実行用のロケーションに格納します。格納先は以下コマンドで確認可能です。
[centos@localhost ansible]$ ansible --version
ansible 2.7.0
config file = /etc/ansible/ansible.cfg
ansible python module location = /usr/lib/python2.7/site-packages/ansible # ←ココ
executable location = /usr/bin/ansible
python version = 2.7.5 (default, Apr 11 2018, 07:36:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)]
Inventoryファイル
ファイルの内容は以下の通りです。
- すべて同じグループに収容可能ですので、
all
グループにまとめて対象機器情報を記載しています。 - 認証方式が同じ場合、機器固有の環境変数として
[all]
内で指定するのは、ホスト情報くらいかと思います。今回は、Cisco DevNetを使っており、機器毎にユーザ名、パスワード、ポート番号が異なったため、個別に指定しています。 - グループ変数
[all:vars]
の内、ansible_network_os
は何かしら入れないと、実行の最初のタイミングでAnsibleConnectionFailureエラーとなるため、ダミーの値としてjunos
を指定しています。(イケてないですw) - 今回の2台の機器は、ログイン時のenableパスワード入力は不要ですので、
ansible_become
やansible_become_pass
は省略しています。 -
ansible_command
で、取得コマンドとして「show version」を指定しています。
[all]
test ansible_host=192.168.100.200 ansible_user=test ansible_password=cisco ansible_port=22
sbx-n9kv-ao ansible_host=sbx-nxos-mgmt.cisco.com ansible_user=admin ansible_password=Admin_1234! ansible_port=8181
[all:vars]
ansible_network_os=junos
# To avoid AnsibleConnectionFailure before executing neko_command module,
# set 'junos' as a dummy value (any type of network os is OK.).
ansible_connection=network_cli
ansible_command=show version
Playbook
Playbookの内容は以下の通りです。
-
neko_command
モジュールで、Inventoryファイルで指定した値を元にshowコマンドを実行。その結果をresult
に格納。 -
debug
モジュールで、result
に格納されているキー値best_match
およびstdout_lines
を出力。
---
- hosts: all
gather_facts: no
tasks:
- name: execute show command
neko_command:
host={{ ansible_host }}
username={{ ansible_user }}
password={{ ansible_password }}
port={{ ansible_port }}
command={{ ansible_command }}
register: result
- name: debug
debug:
msg:
- "{{ result.best_match }}"
- "{{ result.stdout_lines }}"
Playbook実行
netmiko
を使用するには、pip intall netmiko
等で事前にインストールを行う必要があります。
その上で、今回はansible-playbook -i inventory4 playbook1-5.yml
を実行します。
実行結果
ホストtest
のbest_match
の値が一部*になっていますが、showコマンドは問題なく取得できました。
ただし、netmikoによる自動検出処理に時間がかかる影響で、1台の所要時間は30秒くらいでした。(ちょっと長い!)
[centos@localhost ansible]$ ansible-playbook -i inventory4 playbook1-5.yml
PLAY [all] *************************************************************************************************************************************
TASK [execute show command] ********************************************************************************************************************
ok: [test]
ok: [sbx-n9kv-ao]
TASK [debug] ***********************************************************************************************************************************
ok: [test] => {
"msg": [
"********_ios",
[
"Cisco IOS XE Software, Version 03.17.02.S - Standard Support Release",
"Cisco IOS Software, CSR1000V Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 15.6(1)S2, RELEASE SOFTWARE (fc1)",
"Technical Support: http://www.********.com/techsupport",
"Copyright (c) 1986-2016 by Cisco Systems, Inc.",
"Compiled Mon 06-Jun-16 22:04 by mcpre",
~ 中略 ~
]
]
}
ok: [sbx-n9kv-ao] => {
"msg": [
"cisco_nxos",
[
"Cisco Nexus Operating System (NX-OS) Software",
"TAC support: http://www.cisco.com/tac",
"Documents: http://www.cisco.com/en/US/products/ps9372/tsd_products_support_series_home.html",
"Copyright (c) 2002-2018, Cisco Systems, Inc. All rights reserved.",
"The copyrights to certain works contained herein are owned by",
"other third parties and are used and distributed under license.",
"Some parts of this software are covered under the GNU Public",
"License. A copy of the license is available at",
"http://www.gnu.org/licenses/gpl.html.",
"",
"Nexus 9000v is a demo version of the Nexus Operating System",
"",
"Software",
" BIOS: version ",
" NXOS: version 9.2(1)",
" BIOS compile time: ",
" NXOS image file is: bootflash:///nxos.9.2.1.bin",
" NXOS compile time: 7/17/2018 16:00:00 [07/18/2018 00:21:19]",
~ 中略 ~
]
]
}
PLAY RECAP *************************************************************************************************************************************
sbx-n9kv-ao : ok=2 changed=0 unreachable=0 failed=0
test : ok=2 changed=0 unreachable=0 failed=0
[centos@localhost ansible]$
失敗談
当初、自作モジュールの出力をbest_match
(自動検出したOSタイプ)とし、それをPlaybook内に定義した変数ansible_network_os
に格納して、後続のcli_command
やcli_config
を使ったタスク実行時に読み込ませることを考えましたが、うまくいきませんでした。
Connectionプラグインとそれに紐づくansible_network_os
は、Playbook開始時に読み込まれるため、途中で値を変えてもダメなようです。
最後に
一応動くものは作れました。ansible_network_os
でダミー値を使ったり、処理時間がかかったりする点はありますが、急ぎではない時、ちょっとshow versionを取りたいなという時には使えそうです。
今後のAnsibleバージョンアップの中で、ansible_network_os
でautodetect
を指定(もしくは何も指定しない)場合、自動検出してくれる機能が実装されれば、もっと便利な世の中になるのになーと思う今日この頃でした。
2018/12/16 追記
この話のモヤモヤは、続編 Ansibleのcommand系ネットワークモジュールを自作した話の続き で一部解決したにゃ!