NAPALM(1):入门

文章目录

      • 1. 背景介绍
      • 2. 实验目的
      • 3. 物料清单
      • 4. 实验拓扑
      • 5. 实验记录
      • 6. 参考资料

1. 背景介绍

曾经调试过千台设备,也敲过万行代码,当年无论大小割接,绝不提前准备脚本,都是现场凭借比跳egg频率还高的手速敲击命令;最近发现随着年纪的增加越来越不想干活,哪怕修改一台设备接口描述也会将配置在txt中写好,再粘贴下发。遂在闲暇之余研究起了网络自动化。网络自动化没有一个具体的概念,他是一个体系框架,在这框架内可以极高的解放生产力,淘汰CCNA Level Engineer。换句话说可以实现高频率重复性无脑操作的自动化。但正正意义上的网络自动化内容远不止于此;本系列文章所涉及的网络自动化,均是将高频率重复性无脑操作的自动化视为网络自动化。本人学艺不精,不能通过一篇文章全面的描述NAPALM的使用,需要不断学习、吸收再分享,故本文为系列性文章,不定期更新。
下面我先简单介绍下NAPALM应用场景:

首先,让我们假想一个场景:

由于业务发生变更,需要为一个 POD 里面的几十台交换机修改 QoS 配置。作为网络运维人员,应该怎样处理这项工作呢?

如果需要变更的对象是整个数据中心几百台甚至几千台交换机,又该怎样处理这项工作呢?

当下,互联网行业已经普遍采用 DevOps 的体系流程。靠人力去一台设备一台设备的更改配置,已经不再是正确的思维方式。原因不仅仅是浪费时间 —— 要知道,人如果要长时间保持注意力集中,大脑需要耗费大量的能量,很难保证不出现遗漏或者错误。而机器却不会。

因此,正确的方法是利用 DevOps 的流程,让机器来完成这项工作。例如采用基于 Python 的 SSH 库 Paramiko 或 Netmiko,以及 Ansible 或 SaltStack 等自动化工具编写运维脚本。

Netmiko 库和 Ansible 等运维工具虽然可以通过程序化的脚本对网络设备实现批量管理,但仍然需要运维工程师对网络设备的 CLI 很熟悉,预先在脚本中建立需要被执行的 Command 列表。因此运维人员离不开以下两个体系分别是CLI和SNMP协议:

  1. CLI:命令行工具,不同厂商之间、同厂商不同版本、不同设备之间命令语法均可能不一样;这造成运维工程师学习成本的增加。同时设备版本升级后,配置也有不兼容的情况。
  2. SNMP:SNMP Agent 会维护一个 MIB(管理信息库),里面保存着大量的 OID (对象标识符)。一个 OID 是一对唯一的 Key-Value,SNMP Manager 向 SNMP Agent 查询或修改若干 Key 所对应的 Value,就可以实现信息采集或者网络设备的配置修改。SNMP也存在以下问题:
    1. 太古老,性能不好。基于 UDP 协议传输,比较不可靠。虽然在应用层有 Response 机制保证丢包之后的重复 get/ set,但代价就是性能和运行时间都受到影响
    2. 致命的问题是,各厂商都大量的使用私有 MIB,却不存在一个可以自动发现网络设备当前所采用的 MIB 的机制。网络运维人员必须分别向设备厂商索取网络设备的 MIB,耗费大量的时间整理自己需要的 OID,再手工导入到自动化运维平台或者脚本当中。

所以 SNMP 和CLI只适合用来做信息采集,提供告警和可视化报表,但自动化运维的 API 则需要考虑其他的选项。站在网络运维人员的角度,这个 API 到底什么样的?其应该具有以下特点:

  1. 容易使用 —— Usability 是所有产品的核心价值

  2. 需要能够清晰地区分“配置数据”,“设备运行状态数据”和“统计数据”

  3. 需要能够分别从各个网络设备获取上述 3 种数据,并且可以方便地对比不同设备的数据

  4. 可以让网络运维人员统一地管理整个网络的所有设备,而不是一台一台的单独管理

  5. 对不同厂商的设备都能够使用同一种配置方法

  6. 配置变更对网络业务的影响要尽可能的小

  7. 能够提供一个标准化的,对设备 Pulling 和 Pushing 配置文件的流程,以满足对设备配置的备份和恢复的业务需求

  8. 能够很方便地,持续地,检查设备配置文件的一致性

  9. 能够提供基于文本的配置方式,并且不会导致配置的乱序,例如不能搅乱 ACL 规则的顺序

目前能够满足这些要求的网络设备的北向 API 接口就是 Netconf,但是于很多厂商虽然支持 Netconf,但有一些 Key-Value 却存在差异。比如为了表达“端口”,有些厂商用 intf 作为 Key,但另外一些厂商却用 interface 作为 Key。另一个例子就是 Uptime,设备运行时间,各家厂商的设备返回的时间格式更是五花八门。这为网络运维人员处理数据的工作造成了很大的麻烦,不得不耗费大量的时间和精力去阅读设备厂商的 Netconf 文档,去编写大量的正则表达式。

还有,虽然主流的 SDN Controller 的南向接口都支持 Netconf,但是在实际部署时,却无法用单一的 Controller 去控制多厂商的网络设备。通常都是各个厂商使用自己的 SDN Controller 控制自己的设备,然后再用 REST API 与用户的 SDN Controller 对接。故NAPALM出现在我们面前:

NAPALM 是一个 Python 库,它的全称是 Network Automation and Programmability Abstraction Layer with Multivendor support,多厂商支持的网络自动化和可编程抽象层。

目前 Ansible 集成了 3 个 NAPALM 模块,分别是:

  • napalm_parse_yang:用于从设备或文件中解析配置/状态数据

  • napalm_diff_yang:用于比较 2 个 YANG 对象的差异

  • napalm_translate_yang:用于将 YANG 对象转译成设备原始的配置

从设备取出原始配置数据/状态数据之后,可以使用 NAPALM 将其翻译成标准格式的 NAPALM 数据。反之,也可以将标准格式的 NAPALM 数据翻译成设备原始配置数据,并 Push 到网络设备里面,以修改设备的配置文件。

NAPALM(1):入门_第1张图片
是的,NAPALM 还是不能彻底解决网络自动化所面临的问题。

因为各厂商 Netconf 的数据表达存在很多差异,所以 NAPALM 必须要依赖第三方的 Module 来完成原始数据的解析和翻译。如果要解析厂商 A 的某个 OS 系统的配置,就需要一个 OSA_Module;如果要解析厂商 B 的某个 OS 系统的配置,则需要 OSB_Module。所以目前 NAPALM 支持的 OS 类型还比较少,仅限于某几个国外品牌厂商的 OS 系统。

但是NAPALM是目前被集成最广泛的网络自动化库,对思科和juniper支持很好。下面我们将进入是实验部分。

2. 实验目的

  1. 安装NAPALM
  2. 通过NAPALM登录设备
  3. 通过NAPALM自定义函数方法

3. 物料清单

  1. EVE-GN 网络模拟器
  2. PC1台
  3. Python3.7
  4. NAPALM

4. 实验拓扑

在EVE_GN中新建一台IOS交换机,然后桥接到PC网卡(任意能与物理PC通信的网卡),本实验环境是桥接到vm station的NAT网卡。IP地址如下图:
NAPALM(1):入门_第2张图片

5. 实验记录

  1. 安装NAPALM
    本人PC系统为Win10 64bit enterprise,python版本如下3.7.3:
C:\Users\Nero>python --version
Python 3.7.3

C:\Users\Nero>systeminfo

主机名:           DESKTOP-KDNS6Q5
OS 名称:          Microsoft Windows 10 企业版
OS 版本:          10.0.17763 暂缺 Build 17763
OS 制造商:        Microsoft Corporation
....
系统类型:         x64-based PC

在cmd输入pip install napalm将自动安装NAPALM及其依赖包。

pip install napalm		#安装napalm
pip install napalm -U  #更新napalm
  1. 通过NAPALM实现TELNET连接设备:图中交换机IP地址为192.168.162.111,但是我们现在将使用EVE_NG服务器IP+设备端口号的形式来登录设备,即: 192.168.162.133 32778 来登录设备;因为EVE_NG默认控制台putty是使用上述IP信息和telnet协议;后面将介绍如何使用SSH连接设备。
from napalm import get_network_driver						#导入相关模块
driver = get_network_driver('ios')							#指定设备类型为ios,NAPALM对设备的支持是安装版本来实现的,在登录设备之前必须先指定NOS类型
device = driver(
    '192.168.162.133',                                      #设备ip地址
    None,													#设备用户名,此设备未设置用户名
    None,													#密码,此设备未设置密码
    optional_args={'port':32778,'transport':'telnet','secret':'enable123'} #选项字段,指定端口协议enable密码, NAPALM默认为SSH端口号22.
    )
device.open()      											 # 配置好以后,连接设备,没报错即连接成功
device.load_merge_candidate(config='hostname test_device')   #下发配置 hostname test_device修改主机名; 这里需要在交换机上开始SCP,如果没开启,NAPALM会报错提醒。
print(device.compare_config(),"step=1")                      #比较配置信息,详见后文脚本运行日志
device.commit_config()										 #确认下发
print(device.compare_config(),"step=2")						 #为了对比,再比对一次配置,应没有新增配置,详见后文脚本运行日志
device.rollback()											 #配置回滚(回退),结果应该是hostname回复为默认 switch
device.close()												 #断开连接

脚本运行打印日志:

+hostname test_device step=1       #上面比较配置信息显示新增该配置(step=1 是为了方便观察程序运行添加,不影响功能)
 step=2							   #配置commit后再比较配置,显示未新增配置,符合预期。

通过在交换机控制台观察到现象,注意设备名由Switch—>test_device------Switch的变化,因为上面的配置将设备名修改为test_device后,再进行配置回滚,恢复到Switch:
NAPALM(1):入门_第3张图片
3. 通过SSH连接设备:事先配置好G1/0接口,并给交换机配置好IP地址为192.168.162.111.配置好用户名密码。

from napalm import get_network_driver
driver = get_network_driver('ios')
with driver('192.168.162.111','napalm','napalm',optional_args={'port':22},) as device:    #通过with打开连接设备。默认为ssh连接,无须额外配置
	print(device.get_facts())

运行上面的脚本输出如下,返回的是一个字典,内容有运行时间uptime、厂商vendor、版本os_version,可以看书NAPALM已经将设备回显给标准化了,无论什么厂商的设备都将显示为如下格式:

{'uptime': 2280, 'vendor': 'Cisco', 'os_version': 'vios_l2 Software (vios_l2-ADVENTERPRISEK9-M), Version 15.2(4.0.55)E, TEST ENGINEERING ESTG_WEEKLY BUILD, synced to  END_OF_FLO_ISP', 'serial_number': '9YHU26C8R49', 'model': 'IOSv', 'hostname': 'Switch', 'fqdn': 'Switch.ivi', 'interface_list': ['GigabitEthernet0/0', 'GigabitEthernet0/1', 'GigabitEthernet0/2', 'GigabitEthernet0/3', 'GigabitEthernet1/0', 'GigabitEthernet1/1', 'GigabitEthernet1/2', 'GigabitEthernet1/3', 'GigabitEthernet2/0', 'GigabitEthernet2/1', 'GigabitEthernet2/2', 'GigabitEthernet2/3', 'GigabitEthernet3/0', 'GigabitEthernet3/1', 'GigabitEthernet3/2', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'Vlan1', 'Vlan300']}
  1. 自定义方法:虽然NAPALM有很多内建方法可以得到常用网络信息如使用get_mac_table()得到mac地址表、get_interfaces_ip()得到ip信息等,但是实际网络环境复杂多样,总会遇到有需要使用额外命令的时候,下面我们假设需要设备输出show ip interface brief信息。对应的解决方案是扩展NAPALM库,新增一个自定义方法,完成输出格式化,下次再使用的时候直接调用即可。新增该方法有两个途径

    途径一是通过在在NAPALM对应的路径下的模块中直接增加自定义的函数,然后关闭,再调用。下面我们来操作一把:
    我的路径是C:\Program Files\Python37\Lib\site-packages\napalm,找到其中的ios文件夹:
    NAPALM(1):入门_第4张图片
    下图中ios.py 即是ios设备的库文件,所有方法均在该文件中,因此我们可以打开它直接在其后面新增自己编写的方法。
    NAPALM(1):入门_第5张图片
    我们编写一个脚本用于输出show ip interface brief的显示

    def cuget_interfaces_brief(self):
        command = 'show ip interface brief'
        output = self._send_command(command)

        return_vars = []
        for line in output.splitlines():
            return_vars.append(tuple(line.split()))

        return return_vars                      #将返回一个列表,列表内嵌套元组,命令show ip interface brief 在屏幕上的回显一行放进一个元组

我们将上述脚本直接粘贴到ios.py文件的末尾,然后保存:
NAPALM(1):入门_第6张图片
现在我们连接设备后就可以使用该方法了:

from napalm import get_network_driver
driver = get_network_driver('ios')
with driver('192.168.162.111','napalm','napalm',optional_args={'port':22},) as device:          #通过with方法连接设备,少了device.open()这一步
    # device.load_merge_candidate(config='hostname Switch')
    print(device.get_facts())
    print(device.cuget_interfaces_brief())                         	#显示自定义方法输出结果


#为了让输出结果和show ip interface brief类似,我们下面对自定义方法进行格式化输出
    t = 0
    for i in device.cuget_interfaces_brief():
        for tup in i:
            print(tup,end='\t'*3) if t == 0 else print(tup,end='\t'*2)

        print()
        t += 1

脚本输出如下:
print(device.get_facts())的输出:

{'uptime': 2280, 'vendor': 'Cisco', 'os_version': 'vios_l2 Software (vios_l2-ADVENTERPRISEK9-M), Version 15.2(4.0.55)E, TEST ENGINEERING ESTG_WEEKLY BUILD, synced to  END_OF_FLO_ISP', 'serial_number': '9YHU26C8R49', 'model': 'IOSv', 'hostname': 'Switch', 'fqdn': 'Switch.ivi', 'interface_list': ['GigabitEthernet0/0', 'GigabitEthernet0/1', 'GigabitEthernet0/2', 'GigabitEthernet0/3', 'GigabitEthernet1/0', 'GigabitEthernet1/1', 'GigabitEthernet1/2', 'GigabitEthernet1/3', 'GigabitEthernet2/0', 'GigabitEthernet2/1', 'GigabitEthernet2/2', 'GigabitEthernet2/3', 'GigabitEthernet3/0', 'GigabitEthernet3/1', 'GigabitEthernet3/2', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'GigabitEthernet3/3', 'Vlan1', 'Vlan300']}

print(device.cuget_interfaces_brief())的输出

[('Interface', 'IP-Address', 'OK?', 'Method', 'Status', 'Protocol'), ('GigabitEthernet0/0', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet0/1', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet0/2', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet0/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet1/0', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet1/1', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet1/2', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet1/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet2/0', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet2/1', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet2/2', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet2/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/0', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/1', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/2', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('GigabitEthernet3/3', 'unassigned', 'YES', 'unset', 'up', 'up'), ('Vlan1', 'unassigned', 'YES', 'unset', 'administratively', 'down', 'down'), ('Vlan300', '192.168.162.111', 'YES', 'NVRAM', 'up', 'up')]

格式化print(device.cuget_interfaces_brief())后的输出:是不是和CLI 屏幕输出一样了? 爽不爽

Interface			IP-Address			OK?			Method			Status			Protocol			
GigabitEthernet0/0		unassigned		YES		unset		up		up		
GigabitEthernet0/1		unassigned		YES		unset		up		up		
GigabitEthernet0/2		unassigned		YES		unset		up		up		
GigabitEthernet0/3		unassigned		YES		unset		up		up		
GigabitEthernet1/0		unassigned		YES		unset		up		up		
GigabitEthernet1/1		unassigned		YES		unset		up		up		
GigabitEthernet1/2		unassigned		YES		unset		up		up		
GigabitEthernet1/3		unassigned		YES		unset		up		up		
GigabitEthernet2/0		unassigned		YES		unset		up		up		
GigabitEthernet2/1		unassigned		YES		unset		up		up		
GigabitEthernet2/2		unassigned		YES		unset		up		up		
GigabitEthernet2/3		unassigned		YES		unset		up		up		
GigabitEthernet3/0		unassigned		YES		unset		up		up		
GigabitEthernet3/1		unassigned		YES		unset		up		up		
GigabitEthernet3/2		unassigned		YES		unset		up		up		
GigabitEthernet3/3		unassigned		YES		unset		up		up		
GigabitEthernet3/3		unassigned		YES		unset		up		up		
GigabitEthernet3/3		unassigned		YES		unset		up		up		
GigabitEthernet3/3		unassigned		YES		unset		up		up		
GigabitEthernet3/3		unassigned		YES		unset		up		up		
Vlan1		unassigned		YES		unset		administratively		down		down		
Vlan300		192.168.162.111		YES		NVRAM		up		up		

***Repl Closed***

途径二:扩展NAPALM的第二种方法是新增配置文件,新建类方法,然后继承对应版本的内建class,既可以使用自建类,也可以继承内建类的所有方法。这种方法不修改和影响内建方法,灵活性更强,推荐使用该方法:

首先在 C:\Program Files\Python37\Lib\site-packages\napalm下新建文件夹,取名custom_ios
NAPALM(1):入门_第7张图片
文件夹内新建两个文件__init__.pyios.py,前者的名字必须固定为__init__.py
NAPALM(1):入门_第8张图片
分别在两个文件粘贴如下内容:
__init__.py:内容如下

"""custom.napalm.ios package.by nero"""
from napalm.custom_ios.ios import CustomIOSDriver

__all__ = ["CustomIOSDriver"]

ios.py内容如下:

from napalm.ios.ios import IOSDriver
class CustomIOSDriver(IOSDriver):                       #注意__init__.py中的对应名字一定要和这里class的名字保持一致,同时这里继承了内建IOSDriver的所有方法,便于我们调用自定义方法的时候可以继续使用内建的方法
    """Custom NAPALM Cisco IOS Handler."""

    def cuget_interfaces_brief(self):
        command = 'show ip interface brief'
        output = self._send_command(command)

        return_vars = []
        for line in output.splitlines():
            return_vars.append(tuple(line.split()))

        return return_vars

完成以上操作后就可以开始测试效果了:将之前的例子调用方法修改一下,其他语句均不变,如下:

from napalm import get_network_driver
driver = get_network_driver('custom_ios')               #括号内是刚才新建文件夹的名字,仅修改此处
with driver('192.168.162.111','napalm','napalm',optional_args={'port':22},) as device:
    print(device.get_facts())
    print(device.cuget_interfaces_brief())
    t = 0
    for i in device.cuget_interfaces_brief():
        for tup in i:
            print(tup,end='\t'*3) if t == 0 else print(tup,end='\t'*2)

        print()
        t += 1

运行和预期一样,正常工作。

6. 参考资料

  1. http://www.ruijie.com.cn/fa/xw-hlw/61232/
  2. NAPALM’s documentation

你可能感兴趣的:(Python,网络工程,网络,运维,python)