该博客由Patrick Ogenstad提供。Patrick在瑞典的思科金牌合作伙伴Conscia Netsafe工作。他还在博客Networklore上发表了有关自动化和开发的文章。
不久前,一位客户问我:“我猜你在Python多重处理方面很擅长吗?” 我以为他的目标不是评估我的技能。我没有回答,而是问了一个自己的问题。“您要解决什么问题?”
原来,他有上千个设备,需要从每个设备收集数据。由于从每台设备连接和收集信息需要一到两秒钟,因此运行整个task要花费几个小时,因此他需要添加并行化。我从北欧神话中告诉他有关诺恩人的信息。他们旋转连接所有众生的命运线。
我问他,他的目标是否是要拥有Nornir的功能,或者他是否想要关于Python多处理的讲座。当他保持沉默时,我告诉了他有关Nornir自动化框架的信息。
Python自动化框架
足够的戏剧。什么是Nonir?简而言之,Nornir是具有inventory管理功能的可插入多线程框架,可帮助操作设备集合。它与其他自动化框架的不同之处在于,它直接从Python使用。将此与Ansible进行比较,在Ansible中您可以基于YAML使用DSL(特定于域的语言)编写剧本。
对于介绍中的朋友来说,这不是问题。当他要求提供Python解决方案时,Nornir非常适合。
有一个神话认为编写代码很难,它对其他人有意义,但对您却没有意义。尽管掌握起来可能很困难并且需要数年时间,但目标应该是学习足够的知识以帮助您完成工作并对此感到满意。
Nornir会做什么?
Nornir处理数据收集。它针对此数据运行task,并跟踪所有线程。在网络环境中,这通常意味着您拥有一个设备inventory,其中包含与每个节点关联的数据。您可以定义task,并且这些task可以包含社区共享的插件或您的自定义插件或python代码。然后,Nornir将所有内容捆绑在一起,并让您针对所有或部分设备(处理数据,并行化并为您跟踪错误)运行这些task。
您与Nornir的首次互动
像大多数Python软件包一样,您可以使用pip安装Nornir。
pip install nornir
使用Nornir之前需要做的第一件事是inventory。从最基本的形式看,Nornirinventory是包含一个或多个host的Python字典。(可选)您可以使用第二个字典来定义组,因为您可以想象这可以使用任何源作为inventory的输入。为简化起见,Nornir包括一些inventory插件,默认插件是使用YAML文件的SimpleInventory。让我们创建一些示例host和组。
hosts.yaml:
--- al-rtr-1: site: alderaan groups: - routers loopback0: 10.230.12.1 en-rtr-1: site: endor groups: - routers nornir_username: wicket nornir_password: G0lden_god loopback0: 10.230.12.2 nornir_host: 127.0.0.1 ta-rtr-1: site: tatooine groups: - routers loopback0: 10.230.12.3 ta-sw-1: site: tatooine groups: - switches ta-sw-2: site: tatooine groups: - switches
大多数情况下,每个host下的密钥可以是任何东西。但是,您必须遵守一个限制。groups键必须是一个列表,但是groups的使用是可选的。您可能还已经注意到,其中一台host已定义了用户名和密码。Nornir附带的插件使用其中一些变量。如果像上面这样用明文输入密码的想法使您感到恐惧,请不要担心,这仅是示例。您可以从您选择的秘密存储中加载密码。\
groups.yaml
--- defaults: nornir_username: luke nornir_password: blue_saber domain: empire.space contact: Unknown routers: nornir_username: han nornir_password: I_shot_first nornir_nos: iosxr contact: Router team switches: nornir_nos: ios contact: Switch group
拥有这些文件后,您可以使用InitNornir函数使用Nornir进行浏览,这是使用合理默认值作为快速入门。
from nornir.core import InitNornir nr = InitNornir()
您可以看到nornir_username已分配给每个host的username属性。您在“默认”组中设置的任何内容都将应用于所有host。您可以在组或host级别覆盖每个属性的值。您可以通过Host对象访问inventory中定义的大多数数据,就像访问Python字典中的键一样。例如,上面的“联系”键。从初始测试中可以看到,用户名值可以作为属性或作为字典中的键来访问。
>>> nr.inventory.hosts {'al-rtr-1': Host: al-rtr-1, 'en-rtr-1': Host: en-rtr-1, 'ta-rtr-1': Host: ta-rtr-1, 'ta-sw-1': Host: ta-sw-1, 'ta-sw-2': Host: ta-sw-2} >>> nr.inventory.hosts['al-rtr-1'].username 'han' >>> nr.inventory.hosts['en-rtr-1'].username 'wicket' >>> nr.inventory.hosts['ta-sw-1'].username 'luke' >>> >>> nr.inventory.hosts['ta-sw-1']['nornir_username'] 'luke' >>> >>> nr.inventory.hosts['ta-sw-1'].nos 'ios' >>> nr.inventory.hosts['ta-sw-1']['contact'] 'Switch group' >>>
在现实世界的另一个线程中,我的朋友没有以SimpleInventory格式定义的inventory。他所拥有的只是一个包含10,000个设备的文本文件,或者更具体地说是mac地址。一种选择是将文本文件转换为YAML格式。
另一种选择是读取内容并将其直接发送到基本Inventory类。这种方法的结果是我们不能再使用InitNornir函数,而不得不初始化一些不同的代码。不过,不必担心,这可以被认为是高级话题,我在这里仅展示它是为了说明在Nornir中扩展功能是多么容易。
from nornir.core import Nornir from nornir.core.configuration import Config from nornir.core.inventory import Inventory with open("source_mac.txt", 'r') as fs: hosts = { h: { 'mac': h } for h in fs.read().splitlines() } inv = Inventory(hosts=hosts) conf = Config(num_workers=100) nr = Nornir(inventory=inv, dry_run=False, config=conf)
source_mac.txt只是每行包含一个mac地址的文件。source_mac.txt的子集如下所示:
B0:98:2B:76:F3:E9
B0:98:2B:76:F9:C3
B0:98:2B:76:F5:94
B0:98:2B:76:ED:98
B0:98:2B:76:F1:5E
B0:98:2B:76:FA:56
B0:98:2B:76:FB:0C
B0:98:2B:76:F3:79
B0:98:2B:76:EF:12
编写Nornir应用程序
并不是说探索inventory的内部细节不是有教益的,但是除非您可以运行task,否则它没有什么帮助。返回星系超过15跳远,我们所有的host都属于一个特定站点。每个站点内的所有host共享公共数据,因此让我们为站点定义数据源。
endor.yaml
--- asn: 66401 networks: - net: 10.12.0.0 mask: 255.255.0.0 - net: 10.16.0.0 mask: 255.255.0.0 - net: 10.192.25.0 mask: 255.255.255.0
tatooine.yaml
--- asn: 66801 networks: - net: 192.168.23.0 mask: 255.255.255.0 - net: 192.168.24.0 mask: 255.255.255.0 - net: 192.168.38.0 mask: 255.255.255.0
我知道您在想什么,为什么他们不使用IPv6?请记住,所有这些都是很久以前的。我们的首要目标是从这些文件中加载数据,以便将其与该站点的每个host关联。我们将使用一个插件来读取Nornir中的YAML文件。但是,我们确实需要编写一些代码以将其捆绑在一起。让我们来看看!
from nornir.core import InitNornir from nornir.plugins.tasks.data import load_yaml from nornir.plugins.functions.text import print_result def load_data(task): data = task.run( task=load_yaml, file=f'{task.host["site"]}.yaml' ) task.host["asn"] = data.result["asn"] task.host["networks"] = data.result["networks"] nr = InitNornir() r = nr.run(task=load_data) print_result(r)
这里介绍了一些新事物。load_yamltask插件,该插件从YAML文件读取数据。我们创建了一个load_datatask,该task又将load_yaml用作子task。目标是从与每个host关联的站点相对应的文件中加载数据。请注意Python 3.6中引入的f字符串。我们保存站点文件的内容,并将值存储在每个host上。最后一步,我们使用print_result函数查看会发生什么。
看起来Alderaan的站点文件丢失了,太糟糕了。默认情况下,Nornir并不特别在意Alderaan的命运。如果您有不同的感觉,则可能是因为某些问题而导致Nornir引发异常。
呈现配置
现在我们已经有了一些主机数据,无论如何,它们中的大多数还是可以开始做更多有趣的事情。让我们将此数据与Jinja2模板引擎一起使用。Nornir当前包括两个使用Jinja2渲染模板的插件,一个用于字符串,另一个用于文件。如果您想使用Mako或Velocity引擎,那么只需创建一个插件就足够简单了。router bgp {{ asn }} bgp router-id {{ loopback0 }} address-family ipv4 unicast {% for n in networks %} network {{ n.net }} {{ n.mask }} {% endfor %}
细心的读者可能会注意到,我们正在引用模板中的loopback0变量。我们仅为路由器组中的host定义了此变量,因此如果我们尝试为交换机呈现此模板,则该变量将无效。为了确保我们仅生成路由器组的配置,我们可以使用过滤器功能。
from nornir.core import InitNornir from nornir.plugins.tasks.data import load_yaml from nornir.plugins.tasks.text import template_file from nornir.plugins.functions.text import print_result def load_data(task): data = task.run( task=load_yaml, file=f'{task.host["site"]}.yaml' ) task.host["asn"] = data.result["asn"] task.host["networks"] = data.result["networks"] task.host["template_config"] = task.run(task=template_file, template="router.j2", path="") nr = InitNornir() routers = nr.filter(nornir_nos="iosxr" r = routers.run(load_data) print_result(r)
配置网络设备
有了渲染的配置后,就可以配置网络设备了。回顾我们早先所学到的知识,我们知道Nornir致力于数据收集,并让您针对该数据运行task。此说明中没有任何内容专门提及网络设备或如何连接它们。Nornir对于如何访问设备并不了解。因此,尽管它目前随附有Paramiko,Napalm和Netmiko的插件,但其他任何方法也都可以实现。如果您想为Restconf或其他东西创建一个插件,那会很好。《星球大战》中有很多爆炸和火灾,因此纳帕姆在这种情况下似乎很合适。在该代码的最后一个示例中,我们导入napalm_configure插件,并选择将过滤器进一步扩展到仅目标Endor。
from nornir.core import InitNornir from nornir.plugins.tasks.data import load_yaml from nornir.plugins.tasks.text import template_file from nornir.plugins.tasks.networking import napalm_configure from nornir.plugins.functions.text import print_result def load_data(task): data = task.run( task=load_yaml, file=f'{task.host["site"]}.yaml' ) task.host["asn"] = data.result["asn"] task.host["networks"] = data.result["networks"] r = task.run(task=template_file, template="router.j2", path="") task.host["template_config"] = r.result task.run(task=napalm_configure, configuration=task.host["template_config"]) nr = InitNornir() routers = nr.filter(nornir_nos="iosxr") endor = routers.filter(site="endor") r = endor.run(load_data) print_result(r)
在学习网络自动化时,您有可能会使用Vagrant,这是测试和学习的绝佳工具。它使您可以即时创建虚拟路由器以运行测试。如果您没有尝试过Vagrant,我强烈建议您这样做。上面我依靠每个host的DNS条目并连接到默认端口。要对在Vagrant中运行的设备使用napalm_configuretask,您必须对inventory进行少量修改。一个示例如下所示:
--- en-rtr-1: nornir_username: vagrant nornir_password: vagrant loopback0: 10.230.12.2 nornir_host: 127.0.0.1 nornir_network_api_port: 12202
保持真实
回到现实,我的朋友想做的是查找每个设备的IP地址,然后查询该设备的信息。Nornir并没有开箱即用地完成任何需要的task,因此他必须自己构建它们。使用python框架的优点是您自己的扩展将更强大,感觉更自然,实现起来更容易,更高效。Nornir附带的插件数量将随着时间的推移而增加,但它也将成为您编写自己的创作的平台。
到你了
尽管我们在上面的示例中确实创建了一个完整的可运行的Nornir应用程序,但仍有许多工作要做。您可以创建许多独立程序,也可以将Nornir集成到更大的程序中。上面的示例在学习环境中效果很好,但是通常在现实世界中发生的事情会更有趣。我期待听到您使用Nornir创建的内容。