【分享】网络自动化的项目实践:场景、工具和方案
场景1:针对既有网络的操作和/或增量配置,提高重复性任务的效率。它又包括两个方面(以思科设备为例):
- 日常的enable mode操作,例如采集一组设备的设备信息,接口状态和描述检查,设备版本查验,配置文件一致性验证等。
- 为现有网络上平稳运行的多个设备提供具备共性的增量配置。例如新增或删除一个授权登录的帐号,统一修改AAA认证服务器/日志/Netflow/NTP服务器地址,更新SNMP Community string,统一关闭某个端口等。
场景2:针对新建大规模数据中心,提供快速部署。主要是基于一个基线配置模板,调用特定于设备的Location/Type/Role/Loopback等信息的变量文件,来动态创建完整的配置文件。
一、流行的网络自动化工具和手段
根据我个人的工作经验和负责的项目,总结了如下几个解决方案,并列举了一些细节:
1. Cisco Prime Infrastructure,封闭系统,一个运行在虚拟服务器上的OVA文件。
- 内置专有OS,提供专有的Shell界面,不是工业标准的Linux shell。
- 无法获知它的Inventory文件(CVS格式)/配置文件/模板/变量在系统中的保存位置。
- 无法获知它的系统采用什么脚本编写,也无法修改实现个性化。
- 系统不支持任何版本控制机制,无法将本地的配置文件Push到GitHub。
- 内置封闭的Web daemon提供用户GUI接口,采用Oracle DB但是用户没有DB root帐号和Prime 用户帐号,即无法编辑修改数据库文件。
- Cicso PI是一个集网络监测、日志服务、Netflow采集分析、配置备份和网络自动化为一体的综合系统,但是每一个单项拿出来都不如专门提供特定功能的系统强大,例如配置备份有RANCID,Syslog有Splunk, Netflow有Scrutinizer, 网络监测有Observium/Nagios/Check_MK。
2. RANCID, 在我所在公司的部署中,是通过与Observium集成来实现的:
- 通过RANCID自带的脚本(generate-rancid.custom.php)读取Observium inventory文件(和Linux server的/etc/hosts也有关联)动态生成router.db,但是设备型号的描述粒度不够细。
- 版本控制采用CVS或SVN,与Git相比略显老旧。
- 通过clogin –c “show clock” “show version” ‘ls *aus*’命令来针对hostname中有‘aus’字符的所有网络设备按顺序执行一组enable模式操作。
- 通过clogin –x commands.txt ‘ls *sv4*’命令来针对hostname中有‘sv4’字符的所有设备按顺序执行一组配置命令。
- 脚本执行过程中和用户没有交互,只能采用rancid自己的帐号登录设备进行操作,审计功能弱。
- 不如Python脚本调用command-set和device-group两个Argument方便灵活。
- 没有异常处理机制(例如针对100台设备进行批量操作,万一第30台设备因故障登录失败,脚本将停止)。
3. Ansible,发展之初更多地面向服务器进行自动化部署
(1) 实现复杂,引入很多新概念,包括如下:
- Role/Playbook
- Jinja2 template
- Yaml grammar
- Host Inventory
- Engine/Modules/Plugins
(2) 一次Ansible操作的流程,可以显示出完整的Ansible架构,如下图:
(3) 看似一条简单的命令“Ansible-playbook templates.yaml”实际上需要四个input (tasks, hosts, jinja-template, vars),每个input都是特定于设备的,即每台设备都有专门针对自己的四个参数,如果设备的数量不够大,很可能维护变量/模板/Inventory等文件的精力要抵消自动化提高的效率。说通俗一点就是,我采用Ansible节省了30分钟时间,但是我事先需要花2个小时准备相关的、需要被Ansible在执行过程中调用的基础数据。
(4) 理论上更适合新建大规模数据中心的应用场景,网络拓扑对称且层次化,有规律可循。按照固定的Pattern来搭建,容易制定一个具有普适性的基线模板,只需为数不多的变量就可以生成不同site、type和role的设备配置文件。
(5) 由于我现在任职的公司主要是总部和分支机构之间的Corporation网络,每个Remote site的组网原理类似Campus network,以其中一个分公司的LAN-Switch为例,共有57个个性化元素需要提取出来作为变量,工作量很大。其中的VLAN ID变量虽然只有6个,但是在288个接口上随机分布,Jinca2模板必须为每个Interface预留VLAN ID变量的输入;更复杂的是Interface description, 作为LAN-Switch我们需要对连接
WLC/VMW/NetApp/WAP/Zoom/Camera/Monitor系统的Port进行特别描述,但是46个接口描述变量在交换机的288个接口上也是随机的,和其他分公司的LAN-Switch之间没有共性,导致Ansible的变量文件几近不可维护,花在维护变量和模版上的精力,远远大于自动化提高的效率。
4. 还有几个技术方案我没有深入研究,在NANOG会议资料中看到行业专家进行过权威陈述,摘要如下:
- Python with NAPALM (Python module): 思科目前只支持IOS-XR,不适合企业网络。
- NETCONF/YANG: 12年前的IETF标准(RFC4741),截至目前只有JUNOS支持比较好。
- XML via CLI: Schema:尚未标准化。
- RESTful API: 工业标准还在制定中。
- OpenFlow: 目前最新版本1.6,只有一个技术规范不足以支撑SDN生态(IETF有8000+ RFC)。
二、网络和网络之间的差异
上面论述了目前业界比较流行的几种解决方案。首先我们要有这样的共识:到目前为止网络自动化尚没有完美的解决方案。不同的业务模式和公司规模导致它的组网模式和组网技术差异很大。
另外,同样是网络,ISP网络(以中国电信的ChinaNet为例)主要是覆盖全国甚至全球的Backbone(由所有POP和传送网组成),以及提供接入线路可延伸到千家万户的城域网组成;云计算提供商(以AWS为例)则主要是在世界各地建立自营的大型互联网数据中心,利用租用ISP的波分或裸光纤实现所有IDC互联;企业网络则主要是由规模相对较小的Production backbone/IDC和用于公司总部、分部之间互联的Corporation网络构成。
现阶段可行的方案
这三类网络拥有者对网络自动化的需求是不一样的。没有任何一种网络自动化方案可以适应、解决所有网络架构。我这里提出的可行方案,是基于我公司的网络具体现实而推荐的。
我公司的网络属于企业网络,有如下几个特点:
- 网络结构稳定,变化不频繁,平均每年新增一个分支机构。
- 每次网络的调整内容不复杂,Monitor由Solarwinds改为Observium后需要修改SNMP Community string, Netflow服务器由NFSEN改为Scrutinizer后需要修改Collector服务器地址。
- 面向公众的大型数据中心已经迁移到AWS,公司只维护一个用于企业内部服务(人事、法律、财务数据,和源代码、邮件、AD/LDAP/RSA等)的小型Internal DC,网络规模24个Rack, 不到40台交换机。
- 网络由多个厂家设备组成,包括Cisco, Juniper, F5, CheckPoint等。
基于上面的分析,我们平时的网络运营维护最大的特点是依次登录多台设备进行重复性的操作,或者是在现有网络平稳运行的基础上对部分功能进行增量配置。我们认为采用Python结合Netmiko模块是切实可行的,满足现阶段我们的需求。
三、Python和Netmiko介绍
这个方案是我公司的Best current practice。
Python是业界很流行的脚本语言,可读性强,模块和函数丰富,在网络自动化方面有很多活跃社区作出贡献。
Netmiko是一个开源Python模块,由Kirk Byers(https://pynet.twb-tech.com/) 维护,所有源代码和脚本、JSON文件、技术文档和应用范例都在GitHub上供借鉴和下载(https://github.com/ktbyers/netmiko) 。
Netmiko简化了针对网络设备的SSH管理功能,例如:
1. 支持不同厂家设备和同厂家不同平台设备,成功建立SSH连接。
- 基于Cisco和Juniper CLI的差异性,自动隐性输入enable, conft。
- 针对Cisco WLC,自动屏蔽login字符录入。
2. 针对多厂家环境可顺利执行Enable模式的所有操作并将设备的交互信息反馈到执行脚本的机器。
3. 针对多厂家环境可顺利执行Configure模式的所有操作并将设备的交互信息反馈到执行脚本的机器。
基于上述特点,和我公司的具体需求,我们重点实现了场景1的网络自动化,可以将自动化的实现高度提炼为如下的结构:
Python script + command-set + device-group
编制两个Python脚本,名字为enable.py和configure.py,分别代表执行enable或configure模式下的脚本文件。每个脚本都可以调用两个参数,一个是命令集(command-set)要执行的一组操作指令,另一个是设备组(device-group)即脚本要执行的对象。我们还优化了脚本,提供交互式的运行环境,用户只需要键入脚本名称,Python和Linux shell交互,自动询问用户需要执行哪组命令,用户进行选择后脚本自动询问需要执行的目标设备,用户再次进行选择后Python按脚本顺序执行,并将结果输出到Linux服务器。
为了配合两个不同脚本的执行,我们还制定了一些文件名称约定,命令集本质上是txt文件,为了区分是enable还是configure模式,采用后缀名.enab和.conf来分别代表;Device-group必须采用Python字典数据结构,是JSON格式,可直接采用.json来代表。文件命名约定主要利于在Linux shell下与用户交互时提供正确的输出供用户选择。
脚本的执行环境是一台Linux 服务器,安装PIP和Netmiko, 脚本中调用json, netmiko, sys, signal, os等模块/函数,实现需要的功能。此外import getpass实现通过用户的AD帐号进行认证来登录目标设备,并在Python输出中隐藏用户密码。脚本中利用for loop来实现对命令集的逐行执行和设备集的逐个登录。
以下是enable.py的源代码:
#!/usr/bin/env python from __future__ import absolute_import, division, print_function from getpass import getpass import json import netmiko #from netmiko import ConnectHandler #from netmiko.cisco import CiscoIosBase (device type: "cisco_ios", "cisco_xe") #from netmiko.cisco import CiscoIosBase (device type: "cisco_xe") import sys import signal import os signal.signal(signal.SIGPIPE, signal.SIG_DFL) # IOError: Broken pipe signal.signal(signal.SIGINT, signal.SIG_DFL) # KeyboardInterrupt: Ctrl-C def get_input(prompt=''): try: line = raw_input(prompt) except NameError: line = input(prompt) return line def get_credentials(): """Prompt for and return a username and password.""" username = get_input('Username(Please input your adm credentials): ') password = getpass() return username, password netmiko_exceptions = (netmiko.ssh_exception.NetMikoTimeoutException, netmiko.ssh_exception.NetMikoAuthenticationException) username, password = get_credentials() os.system('find *.enab') os.system('echo') os.system('echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"') commandfile = raw_input("Please select what command you want to run: \n") os.system('find *.json') os.system('echo') os.system('echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"') devicegroup = raw_input("Please select what device-group you want to apply to: \n") with open(commandfile) as cmd_file: commands = cmd_file.readlines() with open(devicegroup) as dev_file: devices = json.load(dev_file) for device in devices: device['username'] = username device['password'] = password try: print('~' * 80) print('Connecting to device:', device['ip']) connection = netmiko.ConnectHandler(**device) for command in commands: print(connection.send_command(command)) """To keep 2 lines space between 2 devices""" print() print() connection.disconnect() except netmiko_exceptions as e: print('Failed to ', device['ip'], e)
Python脚本一旦制定就不用频繁更新,唯一需要维护的是command-set和device-group信息,因为每次的任务是不同的,包括需要批量执行的操作指令不同,每次需要对哪些设备进行操作也是特定于任务的。这就要求脚本的具体执行者需要对自己的操作编写单独的命令集和设备组文件。
例如我们要通过enable.py调用一组命令对lab中的所有设备依次执行,device-group的文件如下(Python字典文件,Json格式):
[ { "ip": "lab-wan-isr4431-1", "device_type": "cisco_xe" }, { "ip": "lab-wan-isr4431-2", "device_type": "cisco_xe" }, { "ip": "lab-wan-c3650-1", "device_type": "cisco_xe" }, { "ip": "lab-wan-c3650-2", "device_type": "cisco_xe" }, { "ip": "lab-lan-c3850ss-1", "device_type": "cisco_xe" } ]
假定要对上面的每台设备依次执行show clock命令,.enab的内容如下(txt格式):
show clock
执行结果将在执行此脚本的Linux服务器上,以STD1 方式输出如下:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Connecting to device: lab-wan-isr4431-1 03:42:24.324 UTC Sat Apr 28 2018 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Connecting to device: lab-wan-isr4431-2 03:42:29.472 UTC Sat Apr 28 2018 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Connecting to device: lab-wan-c3650-1 *03:40:15.780 UTC Sat Apr 28 2018 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Connecting to device: lab-wan-c3650-2 *03:42:05.544 UTC Sat Apr 28 2018 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Connecting to device: lab-lan-c3850ss-1 *03:42:45.277 UTC Sat Apr 28 2018
这里的例子中,command-set只有一条show clock命令,实际上可以由多条CLI命令进行任意组合,非常灵活。我在项目中已经预先定义了一些命令集和设备组,连同Python脚本一并Push到GitHub Repository。同时在自己的Laptop上安装了Pycharm IDE工具和GNS3网络设备仿真环境,以及GitHub Desktop客户端。利用这些工具,用户可以在本地编辑脚本和配套文件,在虚拟网络环境中验证,并实现所有文档的版本管理。
对于设备组的编写我们最初希望能通过API或脚本的方式从现有网管系统自动Retrieve, 经过研究个人觉得暂时还不可行。我公司现有RANCID/PI/Observium/IPAM等几套系统,首先是device-group的数据格式不一致,RANCID是TXT,PI是CVS, Observium是JSON,IPAM是专有格式。除了数据格式不同外,对Netmiko而言,最关键的信息是Device-type,这也是Netmiko支持跨平台多厂家的最大优势所在,都是通过对设备类型的明确指定来指导SSH登录设备后的操作细节。
前述的几个系统要么没有指定设备类型(例如PI默认就只是Cisco, IPAM对设备类型根本不关注,Observium系统不对设备进行控制无需SSH登录因此也没有设备类型信息),要么设备类型信息粒度粗(RANCID对思科设备只有Cisco一类,但实际上思科设备可细分为IOS-XE, NX-OS, IOS-XR和IOS)。基于此,我们目前放弃了自动创建Device-group字典文件的想法。退一步讲,即使我们通过脚本来自动创建一个json格式的device-group,我们仍然需要手工指定哪些设备归属到哪个组里面,人工参与程度并未降低,提高的效率有限。
四、其他需要考虑的问题
综上所述,就是我们公司现在已经实现的网络自动化。由于网络新增站点不频繁,一年新增一处,对这种新增网络的部署,也暂时决定不采用Ansible/Jinca2/Task/Variable来实现,主要是不值得,我们有更简单快捷的方式:拿一个现有站点的配置文件作为模板,对个性化的元素例如主机名、环回地址、SNMP location, BGP peer, OSPF area, Interface IP等进行修改,灌入设备,运抵现场,上电开通即可,不需要简单问题复杂化。
另外要认识到,网络自动化第一宗旨是提高效率,对全网只有有限数量的VPN网关,只设置在数据中心的LB,每个Site安全策略不具备共性的FW,没有必要实行网络自动化。
最后一点,我本人对网络自动化还有些顾虑,就是潜在的风险。毕竟一个脚本是对一组设备执行一系列操作,万一命令集配置有误,有可能导致全网瘫痪。目前还没有很完善的回退机制,一旦发生将是灾难性的结果。这也是为什么我在笔记本上安装GNS3的原因,每次需要进行验证,确保无误后才能执行。配套的管理流程也需要跟上,在Change工单上需要提交命令集和设备组的详细信息,以备万一需要回退,确定知道在哪些设备上回退哪些操作。
以上是结合我本人的工作实践和项目研究总结的心得,网络自动化是网络行业的趋势,网络工程师无论喜欢与否,在大趋势面前也只能顺势而为,否则就有被淘汰的危险。技术方案还在不断进步,未来会有新的发展,即使是现有的手段,不同的公司也会有不同的推荐和实现,希望我的文章能起到抛砖引玉的作用,我也非常希望和同行业的专家交流,对文章中内容有不同观点欢迎提出指正,相互学习,共同提高。