经过上一章节的分析,Netmiko已经确定是SSH连接网络设备工具包的不二之选。那么这一章节我们就正式进入Netmiko的讲解,由浅入深的学会使用Netmiko。
首先我们再次总结一下Netmiko能够提供的能力
成功建立到设备的 SSH 连接。
封装掉与设备交互的许多低级机制。
抽象出与设备交互的统一API。
在广泛的网络供应商和平台上执行上述操作。
简化 show 命令的执行、检索和格式化。
简化配置命令的执行。
在开始讲解如何写代码之前,仍然需要一个架构图来了解Netmiko究竟做了哪些事情,通过架构图的直观展示,可以让第一次接触该包,或者了解不够深入的朋友,对其整体的逻辑有一个清晰的认识。
单纯从架构图可以发现,Netmiko中核心的几个概念都已经较难发现Paramiko的影子,因为虽然Netmiko底层仍然依赖了Paramiko的SSH连接能力,但都对其进行的一定的封装,并且将Telnet,Serial的连接一并进行了High-Level的抽象,这对于后续的使用来说就会非常方便。
从上一章节的交互图中可以看出,在连接到网络设备之后,我们需要做诸多的预处理操作,那么这些操作都是与设备厂商强相关的,也就是说Netmiko必须对不同的设备类型做一定的适配。
究竟支持哪些设备厂商,在工具包的开源项目里就有提供,netmiko/PLATFORMS.md。
除此之外我们也可以在使用过程中通过源码检索到,检索方式可以参照视频讲解。
使用Netmiko来连接设备的代码非常的简短,如下:
from netmiko import ConnectHandler
params = {
"device_type": "cisco_ios",
"host": "192.168.31.149",
"username": "cisco",
"password": "cisco"
}
net_connect = ConnectHandler(**params)
上述代码是通过用户名和密码进行SSH连接的方式,这种方式最为普遍,所以我们的讲解都按照这种方式来,另外还有使用证书认证的方式,我也会在视频中提到相关参数的使用方法。
这个函数就是Netmiko中的最上层的工厂函数,它的作用就是根据传入的device_type
来选择对应的连接对象,并把工厂函数接收到的所有参数都传递到连接对象中去。
“工厂模式”也是在实际工作中较为常用的一个设计模式,这里的ConnectHandler就是使用了这一思想;将创建方式相同的连接对象通过工厂函数的封装来实现对上层的屏蔽。
该函数会把params
参数原封不动的透传到连接对象中去(用到了之前番外篇提到的可变参数自动化运维番外篇-Python参数 ),返回一个初始化好的连接对象Connection
。
这个对象就是真正的连接对象了,不同厂商设备的连接对象继承自基类连接对象(BaseConnection)
在这个对象初始化的过程中会打开SSH连接,由上一节内容的介绍可以知道,Netmiko在创建连接的时候是会很多准备工作,这些准备工作如下:
test_channel_read:尝试读取通道中的数据。
set_base_prompt:设置分隔符,用来去除输出内容结尾的设备提示符,通常为设备名
set_terminal_width:设置终端宽度,这个值与输出内容的行宽度有关,会影响到结果的换行。
disable_paging:关闭输出分页。
上述是连接基类BaseConnection
中提供的函数,不同设备厂商的连接对象会有各自不同的实现。
正是因为Netmiko在登录之后做了上述几个操作,所以会让人感觉Netmiko连接的速度要比Paramiko慢很多,Paramiko只需要协商SSH成功就可以直接收发数据了,但Netmiko的四个准备动作就需要和设备交互四次以上,才算建立连接成功。
可能部分朋友会觉得我讲的有点儿复杂了,但其实我认为这些内容都是非常有必要了解的,实际Netmiko源码中的处理要更为复杂,我这里只把最为核心的操作给大家介绍了一下。
如果像其他博客一样,直接贴几行Netmiko连接设备执行命令的代码,那么我这个教程就毫无意义了;而且如果不了解这些内容,在后续执行命令出现任何问题的时候,因为在最开始根本就没有了解到Netmiko的实现逻辑,所以会排查起来毫无头绪。
因此我希望大家可以耐心一点,慢即是快。
Netmiko的执行命令操作分为几种,我们先以最常用到的一种来举例
from netmiko import ConnectHandler
params = {
"device_type": "cisco_ios",
"host": "192.168.31.149",
"username": "cisco",
"password": "cisco"
}
net_connect = ConnectHandler(**params)
cmd = "show interface brief"
output = net_connect.send_command(cmd)
print(output)
这个函数通常用来执行show类型的命令,默认情况下,该方法将一直等待接收数据,直到检测到设备提示符。
设备提示符默认在执行命令前会自动确定,自动确定的方法为向设备发送一个换行符,然后读取输出内容的最后一行作为设备提示符,后续执行指定命令的时候就会等待输出结果中出现这个提示符,视为命令执行完成。
该函数的主要逻辑如下所示:
从流程图可以看出,调用一个简单的send_command函数后,Netmiko默默帮我们做了非常多的事情,尤其是在“读取数据”的环节,做了很多特殊的处理,并且大家在执行命令的时候遇到问题,大多数都是在“读取数据”的环节出了问题,所以只有了解清楚Netmiko究竟做了哪些事情,才能在出问题的时候快速解决。
在向通道发送命令之后,会进入到读取通道的数据的逻辑,流程图如下所示:
以上流程图中两个高亮的节点就是读取数据中比较核心的地方;
超时时间的设置直接决定读取通道数据是否抛出异常,很多情况下获取不到返回结果都是由于超时导致。
在Netmiko5.x中我们可以直接在调用send_command
时传入read_timeout
参数来指定此次命令执行超时时间,也可以在调用ConnectHandler
创建连接的时候传入read_timeout_override
参数来将其作为每次执行命令的超时时间。
在创建连接的时候如果将delay_factor_compat
置为True,则可以将超时时间的设置切换为Netmiko3.x的模式,而在Netmiko3.x中超时时间有不同的计算方法。read_timeout的值是由几个不同的变量共同决定的,公示如下:
read_timeout = max_loops * loop_delay * delay_factor
如果除了要执行的命令,没传任何其他参数的话,那么这里的超时时间默认为100秒,计算方式如下:
read_timeout = 500 * 0.2 * 1 = 100s
同样也可以在调用send_command
的时候通过传入max_loops|loop_delay|delay_factor
来决定执行命令的超时时间。
send_command
函数是通过判断是否返回内容中存在指定pattern
来决定是否读取结束,但这个Netmiko做了一个特殊的机制来保护性能。
通过流程图可以知道,输出结果是通过循环读取通道来进行累加的,那么就会存在输出内容特别长的情况,所以Netmiko设置了一个MAX_CHARS
变量,针对输出内容长度是否超过MAX_CHARS
来做不同的处理,流程如下:
上面的流程看起来复杂,其实简单来说就是通过队列先进先出的特性,当output结果过长时,只对最近几次结果进行pattern匹配。
这一章节主要讲解了如何使用Netmiko进行设备连接,并执行show命令,但我想强调的是,这一章节的几行代码是次要的,最重要的是对于Netmiko收发数据机制的理解,只有理解了这部分逻辑,才能够对各种异常情况得心应手。