【Nornir系列】网络设备管理模块
nornir系列填坑第二弹,欢迎大家帮我转发分享在看,把我的公众号推荐给你们身边的网络工程师,毕竟酒香也怕巷子深,这绝对是目前你能找到的中文乃至所有语种中nornir的最佳指引,在一些细节甚至是可以秒杀官方的,我配置了大量的代码和截图演示。后续将不断展开,挖坑填坑,我都是一把好手。
同时,笔者也在挖很多坑,比如试图给大家讲清楚netconf与yang model,录一个nornir的系列视频,大家尽请期待!
Inventory
主机清单,或者大家喜欢叫设备清单,好像没有一个特别准确事实标准的翻译。一个自动化框架里,或大或小,都需要有一个类似的设备管理模块,记录了我们维护的设备、型号、厂商、IP、用户名等等,在处理自动化的时候,通过设备名或者IP地址来对设备进行批量配置。
Nornir也有一个inventory的模块,它是有hosts(主机)、groups(组)和defaults(默认值)三部分组成,这三个概念的抽象以及用法的灵活,使nornir的设备清单管理异常强大,与网络的一些场景异常契合,简直秒杀ansible或者是其他的一些web自动化的框架。
我们今天先起个头,介绍一下这三个基本的概念及灵活使用,后续再写一篇灵活筛选管理设备的文章。
Inventory plugin类
Nornir加载出设备清单,需要用到一个inventory的plugin类,它主要就是实现一个返回Inventory对象的功能load。Inventory就是由hosts、groups、defaults三个变量进行初始化的一个类。
image-20201210183356707
如上,nornir内置了一个简单的主机清单类,它的方法是通过指定三个yaml文件(host_file group_file defaults_file),加载hosts、groups、defaults三个变量,然后返回一个Inventory类,进而实现设备的管理和灵活筛选,以及连接(ssh netconf等等)的建立。
image-20201210183905682
这个过程大家了解即可,有兴趣的,有能力去对接的,按照这个思路主机去开发,想办法重写load,就可以对接自己的cmdb或者是从表格文件中加载自己的文件,也可以兼容ansible的主机清单,昨天晚上对照着写了一个对接自家CMDB的一个inventory的plugin,通过dict的list加载设备清单,非常简单。
目录结构
一般config.yaml放在根目录,invetory加载方式如果是文件的方式,或者再具体点,就来说nornir内置的SimpleInventory,会在根目录创建一个inventory的文件夹,然后写三个文件,在config.yaml(这个文件名可以按需改)分别指向这三个文件的相对路径
image-20201211213026789
详解官方默认的SimpleInventory加载
hosts_file
它加载的方式完全是靠三个文件来进行加载,我们一个个的过以下。先来看看一个稍微复杂的hosts
---
spine00.bma: # 主机的名称,理解为设备名称或者是必须全局唯一,就是个名字,你用ip也是可以的
hostname: 127.0.0.1 # ip地址或者fqdn
username: vagrant # 用户名
password: vagrant # 密码
port: 12444 # 端口,这个要和你使用的connection方式对接,如果是ssh一般是22,如果是rest 可能是80或者443,和具体的连接的plugin关联
platform: eos # 设备平台类型,兼容netmiko和napalm
groups: # 设备分到那个组里,可以是多个组,这个就很灵活,可以以等保区划分,也可以以运维组划分,也可以按地域,且可以不同维度组合,能加的都加上,然后在筛选的时候可以多维度筛选,前提是自己能控制住,人一定要控制住自己的代码。不然还是按照一个维度去设计group。
- bma
data: # 设备的一些相关参数变量,比如定义备份的命令,也可以把运维组、等保区放到data,data本质是一个字典,也可以设置多个维度。比如下面的例子,写了这个设备的位置、角色、类型,大家发挥自己的想像力。
site: bma
role: spine
type: network_device
spine01.bma:
hostname: 127.0.0.1
username: vagrant
password: ""
port: 12204
platform: junos
groups:
- bma
data:
site: bma
role: spine
type: network_device
leaf00.bma:
hostname: 127.0.0.1
username: vagrant
password: vagrant
port: 12443
platform: eos
groups:
- bma
data:
site: bma
role: leaf
type: network_device
leaf01.bma:
hostname: 127.0.0.1
username: vagrant
password: wrong_password
port: 12203
platform: junos
groups:
- bma
data:
site: bma
role: leaf
type: network_device
nornir通过上一次分享的初始化方式把以上yaml文件内容加载到设备管理模块,这些网络设备每一个都是Host对象,Host对象大家可以通过内置的schema函数获取其比较易读的模式。
from nornir.core.inventory import Host
import json
print(json.dumps(Host.schema(), indent=4))
打印出来结果如下
{
"name": "str",
"connection_options": {
"$connection_type": {
"extras": {
"$key": "$value"
},
"hostname": "str",
"port": "int",
"username": "str",
"password": "str",
"platform": "str"
}
},
"groups": [
"$group_name"
],
"data": {
"$key": "$value"
},
"hostname": "str",
"port": "int",
"username": "str",
"password": "str",
"platform": "str"
}
这个就非常容易看到Host的模式,比直接看源代码简单
- 首先它有一个name,是str,也就是leaf01.bma这种给人看的名字
- 我们跳过connection,看最底下的,hostname是ip地址或者fqdn域名,username等是登录的基础信息,密码就不说了,端口(如果是ssh就是写22,如果有些特殊的连接类,比如基于netconf的就写830等等),platform标明设备的平台,可以无缝对接netmiko与napalm,我们也可以根据自己需要,写一个自己的连接类,使用自己的platform规则。
- nornir有相关的connection的类来处理和设备的连接,这个时候可以用以上的基本信息,也可以有连接用的专属参数字典,connection_options,里面有用户名、密码、端口、平台等等,以及可能用到的额外参数extras。我们在加载nornir的时候,每个设备会按需维系一个connection的连接对象,比如nornir_netmiko是实现了一个netmiko的连接,并可以按需保持一个长连接,或者按需关闭,适时发送命令行。napalm用到了自己的连接类,我。我们也可以按需创建自己的连接类,比如给予paramiko或者是pexpect去自己封装,如果你觉得netmiko太重,或者你有自己的想法。
连接类的connection_options不一定非要有数据,我们可以从基础信息里读取,也可以从connection_options的参数里读取,一切都是由具体的连接的plugin实现的,因插件而异。 - groups是设备所属组的名字,是一个列表,这个功能很强大。host会自动继承自己本身没有的值,但是在group里定义了的值。
- data里是我们在执行runbook时候可能用到的参数,是字典,vlaue可以是复杂结构,比如我们可以设置一个backup_cmds:['show run ','show ip route'],每次去批量备份一些基础信息,打一个快照类似的东西。甚至可以更复杂,按需。host会自动继承自己本身没有的值,group里也没定义,但是default里定义了的值。
在task中取host信息
这些信息在执行对应task的时候都可以按需取出来,非常方便,我们截图演示一下host。后续会在task进一步演示。
image-20201211210633540
我们想用name就在task里取到host属性,这就是一个Host对象了,然后继续取Host的属性,用标点符号点来取属性值。
image-20201211211756269
这些数据都可以取到,非常方便,所以我们可以按需在hosts及其所在groups以及defaults里获取,非常非常灵活。缺什么参数了,直接yaml里配置即可,然后继续task里写。
好了我们来看看groups_file
在nornir对象中取出来
我们可以在nornir的inventory属性中取到hosts,然后按照name可以取到对应的host对象,非常像一个字典但是不是字典。
- 在继续取具体到一个主机的时候,我们取属性值的时候,也就是schema中返回的那些属性,我们用“点”,直接取,就是对象的取值方法。
- 在取data里的数据的时候,我们可以直接按照字典的方式取出来
image-20201211235751460
实际使用中,nr.inventory.hosts的打印输出,只是调试中使用,真正的针对host的操作一般使用会分成两种情况:
- 在task中提取host属性值进行判断,进而对不同设备进行不同操作。每个task就是对每台设备批量执行,我们可以在这里根据host的一些值进行判断控制逻辑,或者跳过指定设备或者对制定设备进行操作。
- nornir是经过筛选后的nornir对象,先筛选出所有leaf交换机,返回一个nornir对象,然后批量操作,在这之上封装逻辑,这个我们最后会起个头,讲讲如何简单筛选。下一节会详细展开。
groups_file
这个文件是用来指定分组的,上述host的groups如果指定了,在这个文件里必须定义,请务必记住。
分组是用来维护一些组内共同的属性,比如按厂商分配,他们的vendor应该一致,比如按一些区域分,区域的名字一致,ntp,探针,syslogserver,地域位置属性等等可能都是一致的。这样不用每台设备取维护,只要放到对应组,在host里取出对应的group之后可以取出公共属性,比如vrf、vlan等等都可以。
我们先来看看schema
from nornir.core.inventory import Group
import json
print(json.dumps(Group.schema(), indent=4))
结果
{
"name": "str",
"connection_options": {
"$connection_type": {
"extras": {
"$key": "$value"
},
"hostname": "str",
"port": "int",
"username": "str",
"password": "str",
"platform": "str"
}
},
"groups": [
"$group_name"
],
"data": {
"$key": "$value"
},
"hostname": "str",
"port": "int",
"username": "str",
"password": "str",
"platform": "str"
}
绝大部分与host一致,如果host里的数据配置为空,则会使用所属group的属性填充到设备Host的相关属性。
比如我们把某设备的相关属性去掉
image-20201211233557240
同时在某个group里赋值,比如赋值到demo_for_tree中
image-20201212000120705
但当我们断点取看这个设备的相关信息时(或者直接打印都可以),发现这些属性都被填充上了,awesome!
image-20201211233514375
我们的例子比较极端,一般不会把hostname也置空。
group的继承与冲突处理逻辑
Group也有一个groups的属性,这是一个group的继承关系,一个设备,所属group为bj_leaf,它有两个group值,bj和leaf,那它从地域上继承了bj这个group,从角色上继承了leaf,相关的信息都会继承到属于bj_leaf的所有的host上,同时官方给的例子是一个全局的global的组,某些可以继承global,global相当于父节点基类,继承了这个group的其子类group如果有字段和这个冲突,肯定是以子节点为准,如果设备上的属性与某group冲突,则以设备上的属性为准。
官方例子:
---
global:
data:
domain: global.local
asn: 1
eu:
data:
asn: 65100
bma:
groups:
- eu
- global
cmh:
data:
asn: 65000
vlans:
100: frontend
200: backend
个人举例:
leaf:
data:
role: leaf
chongtu_k: 1
bj:
data:
city: beijing
chongtu_k: 2
bj_leaf:
groups:
- leaf
- bj
我们在设计继承的时候,个人认为尽量不要冲突,或者是冲突可控,冲突可控遵循的逻辑是,后面的会覆盖前面的,我们按照这个顺序原则取写,也可以,合情合理,但是我个人不建议。
有冲突的时候我们打印设备的冲突的k值
image-20201211235138992
group与host的结合,如果在一个大型网络中,可以是一个非常复杂的结构,给了我们足够的空间和灵活度的时候,也考验我们是否能hold住,所以初学者尽量简单使用,循序渐进,有能力的就可以脑洞大开的组织你的设备清单。
defaults_file
defaults就更加简单了,就是默认值,全局的默认值,比如port可以设置成22。
它的schema:
{
"data": {
"$key": "$value"
},
"connection_options": {
"$connection_type": {
"extras": {
"$key": "$value"
},
"hostname": "str",
"port": "int",
"username": "str",
"password": "str",
"platform": "str"
}
},
"hostname": "str",
"port": "int",
"username": "str",
"password": "str",
"platform": "str"
}
例子:
image-20201212000835784
像这样,写个默认值,就不用每个设备取写了,这个优先级肯定是最低的,host>group>default,一个值如果在多个中出现,是按照这个优先级采信,如果只出现在一个配置里,那肯定就是它了。
简单的搜索
我们加载的nornir对象默认是所有设备,但是实际操作中,我们有时候需要筛选,这个时候就用到了filter中的F类,它有点像django的filter,非常赞。我们直接上代码
image-20201212083135502
图上的示例中,我们筛选出了设备name包含了leaf的设备清单,然后进行了打印输出,实际我们一般对接的是操作,比如执行一个task。同时这个nornir的筛选可以支持俄罗斯套娃,层层筛选,功能很强大,后续章节分享。
小结
好了,以上就是今天我们所有的内容,主要分享了nornir的设备管理体系的概念和逻辑以及使用。
最后
同名知乎专栏和微信公众号:NetDevOps加油站,欢迎你的加入!
image-20201204202546271