Paramiko浅析


回想起上次写blog,还是在上次,不禁令人感慨。目前所谓的网络运维自动化,绝大部分都是通过ssh实现的,使用ssh做网络自动化的本质,是对人类行为的模拟,说白了,你是在写代码模拟你日常的cli操作(这点肯定是不如api操作的)。而python实现ssh的模块Paramiko,也几乎是你首选的ssh轮子。

作为python最成功的一个sshv2开源项目,Paramiko在netdevops业界的地位毋庸置疑。计算机网络从业者都知道Netdevops给整个网络业界带来的改变是颠覆性的,很多开源项目在默默支撑着当代社会的运转,而Paramiko几乎是网络工程师(包括我)进入python和netdevops世界的领路人。

解释下Paramiko的几个核心部分的东西

SSHClient

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

Transport

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

Channel

A secure tunnel across an SSHTransport. A Channel is meant to behave like a socket

顾名思义,Channel就是那条真正在工作的SSH通道。Paramiko实际上可以创建两条不同的通道,一条是建立SSH连接后就一定会存在的通道,另一条是你调用exec_command方法的时候,它会创建另一条通道出来。但是,并非所有的SSH服务端都支持,按官方的说法就是,基于标准的OpenSSH实现都是可以的……不过嘛,大厂的网络设备,总会有非标的情况。

invoke_shell

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

使用Paramiko操作设备

无论如何,你总要实例化一个SSHClient的,所以

import paramiko

hostname = "YOUR DEVICE HOSTNAME"
username = "YOUR ACCOUNT USERNAME"
password = "YOUR PASSWORD"

ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
ssh_client.connect(hostname=hostname, port=22, username=username, password=password)

通过exec_command发送单条命令

stdin, stdout, stderr = ssh_client.exec_command("dis version\n")

time.sleep(0.5)    //在读stdout之前中断一下让回显完全写入
stdout.channel.close()
res = stdout.readlines()
print(res)

\n其实就是在模拟敲回车。命令执行完之后需要关闭Channle,否则你会发现你根本不能读取到stdout中的数据。stdout实际上是在调用exec_command方法时自动调用了makefile(),返回了一个类文件的对象,你可以直接使用Python的file()函数和方法,比如readlines,逐行读取后返回一个列表。

invoke_shell多条命令

不再使用exec_command,实例化SSHClient的代码不变,然后

channel = ssh_client.invoke_shell()
channel.send("sys\n")
channel.send("dis version\n")
channel.send("dis vlan\n")
time.sleep(1)

# RETURN A BYTES OBJECT THAT DECODED BY THE UNICODE
print(channel.recv(65535).decode("utf-8"))

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

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

stdout = channel.makefile()
channel.close()
print(stdout.readlines())