米乐M6

行业新闻

米乐M6 M6米乐NetDevOps] 使用SSH进行网络自动化的例子及问题(1)

  NETCONF和XML再丑,RESTCONF再不好用,那也仍然是API,只要这个API能毫秒级响应你的请求并给你返回数据,那它总是比SSH好用的。所以,在做任何自动化尝试之前,第一件事就是找找看你要操作的设备是否有API可供使用,如果没有,再去考虑用SSH。

  使用SSH做网络自动化的本质,是对人类行为的模拟,说白了,你是在写代码模拟你日常的CLI操作。对于绝大部分网工(包括我)来说,Python是首选的做网络自动化的编程语言,而Python实现SSHv2的模块Paramiko,也几乎是你首选的SSH轮子。

  P.S.1. 还有个轮子叫Netmiko,但我个人不是很喜欢这类轮子,尽管它进行了更高层面的封装,但这也意味着,如果他的轮子测试不完善或者有Bug,排障会更加痛苦。我个人认为,你都已经开始学Python了,那么思考一下怎么处理SSH返回的字符串,应该是个“入门级”的任务了……否则如果学了半天仍然只是在用别人封装好的高级轮子,那可能还是学一下怎么写更漂亮的PPT唬人比较实在(笑……

  由于不同的厂家,甚至同一厂家的不同硬件,甚至同一硬件不同代的软件版本,CLI都可能发生变化,这就意味着,上次针对A设备的版本1写的代码,在A设备的版本2上执行可能就是出错的,如果希望代码能够重用,就需要尽可能细的做模块的拆分,以及大量的测试。说白了,使用CLI,等于你自己在为设备开发一套API接口。

  所以,如无必要,尽量不要用SSH&CLI去做自动化,厂商做不好的事,作为用户,只能通过无尽的投诉和Case去逼他们做好,而不要为难自己——除非,做这件事给你带来实际的价值,比如你就是上千台不太可能支持API,也不太可能马上淘汰的设备需要维护,又或者,做这件事能在Boss面前展现你的Value,又又或者,只是为了解决燃眉之急(比如我)。

  P.S.2. 我不打算讲如何安装以及如何import这样的问题,我假定你在看这篇文章的时候,已经掌握了Python的基础知识, 而Python的教程满天飞,自己百度就好。

  SSHClient是Paramiko最高层的抽象,大部分时候,我们直接通过实例化一个SSHClient来访问所有的函数/方法。

  在你实例化SSHClient的时候,Transport对象就已经创建过了,如无特殊需求,一般不需要单独使用。Transport包含的是SSH建立通道所需要的参数,如密钥交互算法。

  顾名思义,Channel就是那条真正在工作的SSH通道。Paramiko实际上可以创建两条不同的通道,一条是建立SSH连接后就一定会存在的通道,另一条是你调用exec_command方法的时候,它会创建另一条通道出来。但是,并非所有的SSH服务端都支持,按官方的说法就是,基于标准的OpenSSH实现都是可以的……不过嘛,大厂的网络设备,总会有非标的情况。在下面的例子中你会看到,Cisco IOS-XE软件很可能就是个非标实现,至少,它不能通过exec_command来发送多条命令,而ASA软件则可以。

  Paramiko提供了一个invoke_shell()的方法,让你直接调用SSH建立通道时默认存在的那条Channel,不过通过这条Channel返回的数据是字节流,但它仍然是一个Channel对象,这意味着你可以通过调用所有Channel支持的方法来达到和exec_command同样的效果。

  我们重新来理解一下CLI,本质上,它是一种“交互式”的操作界面,你输入command,它给你返回正确或者错误的结果。

  CLI本质上是为人设计的,用程序去调CLI,其实就是写代码来模拟人的行为,难度取决于CLI本身设计是否合理。好的CLI的回显数据,实际上也是格式化的,有一定的套路,只不过这个套路没有一个明确的规范,你必须为不同的command去适配不同的规则。

  简而言之,假如一个程序用到了三条配置命令和三条show命令,为保不出错,最好是有一个贴近生产环境的实验环境让你实际把这些命令都刷一遍,每条命令如何解析搞清楚,再去写程序。

  此外,程序和人有一点不同的是,人可以连上去一次性把事情做完,而写程序,出于重用代码的目标,你不太可能把一堆命令写在一起。

  人脑是可以判断敲完第一条命令后第二条我要敲什么的,程序不行,如果你把一堆命令写在一起做成一个脚本,这个脚本就很难重用了,因为下次你的需求,用到的命令或者只是执行命令的顺序可能是不同的。

  但是这也就意味着要把抽象的粒度缩小到尽可能小的程度,比如配置接口IP地址的任务做成一个模块,把配置BGP邻居做成另一个模块,那么实际上两个模块执行的时候是两个不同的连接,执行的命令分别是——

  反之,如果你在一个脚本里同时执行这两个任务,换言之你只建立了一个SSH连接,你把两件事的逻辑写在一起,没有做进一步的拆分,万一下次米乐 M6你不要配置IP,你只要配置BGP,那等于这次写的脚本白写了。

  [补] 其实换个角度想,每次的RESTAPI调用,也是一个单独的HTTP连接,并没什么不同。所以多建几次拆几次SSH连接也没什么,充其量是如果你用了AAA,你大概会看到某个user经常登陆吧(笑……

  \n其实就是在模拟敲回车。命令执行完之后需要关闭Channle,否则你会发现你根本不能读取到stdout中的数据,你的程序会Hanging……很多人都遇到了这个问题。

  不过,对于很多网络设备的CLI来说,实际上你在执行完一条命令后,CLI在等你执行下一条命令,如果没有关闭Channel,res = stdout.readlines()这行其实一直都没有执行,除非你设置timeout,或者主动关闭Channel

  stdout实际上是在调用exec_command方法时自动调用了makefile(),返回了一个类文件的对象,你可以直接对这个对象使用Python的file()函数和方法,比如readlines,逐行读取后返回一个列表

  But,到这里为止,上面的代码还是有问题,如果执行它,会发现返回了一个空列表,或者返回的内容不全,都有可能……

  而实际上,当你登录ASA时,从登录到输出show inventory这条命令,全部的回显应该是这样子的

  这个问题让人非常哭笑不得的地方是,它出现可能是因为你的程序跑的太快了,回显压根没来得及写入stdout,我参考了@弈心的代码,以及一些Google出来的结果,解决的办法就是,加个延时,让程序在读stdout之前中断一下。

  由于调用了readlines方法,所以你会看到返回的是个列表,数据的每一行就是列表的一个元素。

  不过,这里只是单条命令的效果。而大部分时候,我们需要在设备上操作多条命令,比如,配置的时候你需要config terminal后才能进行配置操作,所以……

  exec_command是可以配置多条命令的,只是,在不同的SSH服务器上,表现有些不同。

  笔者就遇到了Cisco的ASA和IOS软件完全不同的效果——ASA可以识别你输入的任何一个字符并且起作用,而IOS因为一个神奇的feature,让你的努力变为无效……

  以下面这个例子来说,show interface ip brief必须要特权模式,而我使用的账户默认没有在特权模式中,所以需要先输入enable和密码进入特权模式

  ASA会认到所有的命令,所以如果你多在两个命令之间加一个空格,回显就会报错

  autocommand是IOS的一个feature,我们用exec_command发送两条命令时触发了这个功能,而且这个功能还无法关闭,更骚的是,如果你去Google,会发现很多人在不同的自动化软件上都遇到过这个问题……

  这个feature的本意是在用户登录的时候自动执行一条命令,不知为何,程序发送命令下去触发了这个功能,怎么看都像是个Bug……而且似乎没打算修。

  吐槽.1 这也是我为什么不推荐SSH,如非万不得已,不要尝试挑战这些封闭的黑盒子

  在上面的IOS的问题中,很明显是autocommand这个feature在作妖,因为我并没有真的要启用这个功能。可以救命的方式是Paramiko的invoke_shell,调用这个方法,就真的是在进行“人类迷惑行为M6 米乐模拟”了……

  请注意,这里返回输出的,不再是可以调用file()方法的对象了,而是一个Bytes对象,顾名思义,是字节流 ,通过utf-8解码可以得到和CLI一样的回显。

  P.S.8. 这里time.sleep()的时间设的比较短,这可能会有问题,比如,数据可能会不全,可以适当延长,比如延长到1s,1000ms足够大部分文本类数据的传输了,如果网络状况不好,可能还要更久一点,所以实际的业务逻辑中,可以把这里设置为一个可调的变量。

  不过,字节流不好处理,数据提取会比较痛苦,所幸invoke_shell仍然是一个Channel,所以可以调用makefile()方法来达到和使用exec_command一样的效果。

  同样,也要在读取之前close()掉Channel,返回的数据将会是按行分割的列表。

  列表有索引,这也就意味着,只要事先测试过,设计好命令的顺序,返回的数据在第几行是可以提前确定下来的,可以确定也就可以比较轻松的写逻辑。

  这篇我没有给出具体场景的代码逻辑,只是刨了一下用Paramiko & SSH去连接设备,如何推命令,以及获取到的数据是什么样子,有一定基础的人,看到列表类数据就会会心一米乐 M6笑,这是个可以进一步处理的东西。