回想起上次写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())