试着使用Ansible自带的模块通过终端服务器控制网络设备

首先

我们在之前的文章中介绍了使用Python通过终端服务器操作网络设备的示例。
使用Python通过终端服务器操作网络设备的方法。

这次,我以这段Python代码为基础,自己编写了一个能够做同样事情的Ansible模块。

控制台命令模块

在创建模块时,我们参考了networktocode/ntc-ansible的ntc_show_command模块。
该模块的实现功能是执行多个show命令并将结果输出到标准输出,非常简单。

import os.path
import socket
from netmiko import redispatch
import time

HAS_NETMIKO = True
try:
    from netmiko import ConnectHandler
except:
    HAS_NETMIKO = False


def main():

    module = AnsibleModule(
        argument_spec=dict(
            host=dict(required=False),
            username=dict(required=False, type='str'),
            password=dict(required=False, type='str', no_log=True),
            port=dict(required=False),
            secret=dict(required=False, type='str', no_log=True),
            platform=dict(required=False),
            commands=dict(type='list', required=True),   # 複数のshowコマンドを実行できるよう、データ型をリストに指定
            delay=dict(default=1, required=False),
            provider=dict(type='dict', required=False),
        ),
        supports_check_mode=False
    )

    provider = module.params['provider'] or {}

    no_log = ['password', 'secret']
    for param in no_log:
        if provider.get(param):
            module.no_log_values.update(return_values(provider[param]))

    # allow local params to override provider
    for param, pvalue in provider.items():
        if module.params.get(param) != False:
            module.params[param] = module.params.get(param) or pvalue

    # AnsibleのInventory/Playbook/Variableファイルで定義された値を取得し、変数に格納
    ip = module.params['host']
    username = module.params['username']
    password = module.params['password']
    port = module.params['port']
    secret = module.params['secret']
    device_type = module.params['platform']
    command = module.params['commands']
    delay = int(module.params['delay'])

    # netmikoがインストールされていない場合、Failメッセージを出力
    if not HAS_NETMIKO:
        module.fail_json(msg='This module requires netmiko.')
    else:
        # ここからは前回記事と同様
        device_args = dict(
            device_type = 'terminal_server',
            ip = ip,
            port = port,
            username = username,
            password = password,
            secret = secret,
          )

        net_connect = ConnectHandler(**device_args)
        net_connect.write_channel('\r')
        time.sleep(1)
        net_connect.write_channel('\r')
        time.sleep(1)
        net_connect.read_channel()

        redispatch(net_connect, device_type = device_type)

        if secret:
            net_connect.enable()

        output_list = []

        for cmd in command:
            output = net_connect.send_command_timing(cmd, delay_factor=delay)
            output_list.append(output)

        net_connect.disconnect()
        # ここまで

    # 実行結果をJSON形式で標準出力
    results = {}
    results['response_list'] = [output_list]

    module.exit_json(**results)


from ansible.module_utils.basic import *
if __name__ == "__main__":
    main()

为了让Ansible能够识别自定义模块,按照最佳实践>目录结构创建了一个library目录,并将console_command.py放在其中。
另外,还在ansible.cfg文件中添加了对library目录的路径。

[defaults]
library = /home/centos/ansible/library

可以通过运行 ansible –version 命令来确认 ansible.cfg 的位置。

[centos@localhost ~]$ ansible --version
ansible 2.7.9
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/centos/ansible/library']
  ansible python module location = /usr/lib/python3.6/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.6.7 (default, Dec  5 2018, 15:02:05) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]

如果使用pip(Python的包管理系统)安装ansible,则似乎需要创建一个新的ansible.cfg文件,因为它不会自动创建。

剧本

从Ansible2.5版本开始,当通过CLI界面操作网络设备时,使用network_cli连接插件是标准的做法。但是这次我们还自定义了与网络设备的连接和登录过程,所以指定了local。

为简单起见,show命令的输出结果被限定在|符号内。

---

- hosts: all
  gather_facts: no
  connection: local

  tasks:
    - name: execute show command
      console_command:
        commands:
          - show version | inc Cisco
          - show running-config | section interface FastEthernet1 
        port: "{{ port }}"
        provider: "{{ cli }}"
      register: result

    - name: display the output of show command
      debug:
        var: result.response_list

  vars:
    cli:
      platform: "{{ platform }}"
      host: "{{ inventory_hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      secret: "{{ secret | default(omit) }}"

库存文件

本次使用了一台Cisco 1812J作为目标设备。

[all] 
# ターミナルサーバのIPアドレス SSHログイン用のポート番号 NW機器のenableパスワード
192.168.100.56 port=3001 secret=test

[all:vars]
platform=cisco_ios   # NW機器のOS名(=netmikoのdevice_type)
username=admin   # ターミナルサーバのユーザ名
password=password   # ターミナルサーバのパスワード

执行结果

根据 “result.response_list” 的显示,可以看到两个 show 命令的结果都正常显示出来了。(但是换行没有成功。。)

[centos@localhost ansible]$ ansible-playbook -i inventory_consv2 playbook_consv.yml

PLAY [all] ******************************************************************************

TASK [execute show command] *************************************************************
ok: [192.168.100.56]

TASK [display the output of show command] ***********************************************
ok: [192.168.100.56] => {
    "result.response_list": [
        [
            "Cisco IOS Software, C181X Software (C181X-ADVIPSERVICESK9-M), Version XX.X(X)XX, RELEASE SOFTWARE (fc5)\nCopyright (c) 1986-2007 by Cisco Systems, Inc.\nuse. Delivery of Cisco cryptographic products does not imply\nA summary of U.S. laws governing Cisco cryptographic products may be found at:\nCisco 1812-J (MPC8500) processor (revision 0x400) with 118784K/12288K bytes of memory.",
            "interface FastEthernet1\n description << border2 - dist1 Segment >>\n ip address 192.168.200.9 255.255.255.252\n speed 100\n full-duplex"
        ]
    ]
}

PLAY RECAP ******************************************************************************
192.168.100.56             : ok=2    changed=0    unreachable=0    failed=0   

最后

我們這次只實現了最基本的功能,但是參考了ntc_show_command模組後,發現它還有基於textFSM的解析功能和將輸出結果保存為文件的功能,如果兩者一起加入,這個模組會變得非常實用。

bannerAds