使用paramiko远程执行命令、下发文件的实例
写部署脚本时,难免涉及到一些远程执行命令或者传输文件。
之前一直使用sh库,调用sh.ssh远程执行一些命令,sh.scp传输文件,但是实际使用中还是比较麻烦的,光是模拟用户登陆这一点,还需要单独定义方法模拟输入。
感受一下:
fromshimportssh
PASS='xxxx'
defssh_interact(line,stdin):
line=line.strip()
print(line)
ifline.endswith('password:'):
stdin.put(PASS)
ssh('x.x.x.x',_out=ssh_interact)
来自官方文档
后来发现paramiko库更加优雅、便捷,所以准备用pramiko替换掉sh。
之前通过同事了解到,paramiko在远程执行python脚本时,脚本中的输出内容可能会通过stderr这个管道输出出来,所以直接用paramiko的SSHClient类中的exec_command方法执行,通过读stderr管道中有无输出来判断命令是否成功执行的方式是行不通的。所以用更底层一些的Channel类的recv_exit_status方法判断执行退出码更好一些。
安装
可以通过使用pipinstallparamiko安装,细节这里不再赘述。
封装
首先定义几个异常
#coding:utf-8 importos.path fromparamikoimportSSHClient,AutoAddPolicy,AuthenticationException classConnectError(Exception): """ 连接错误时抛出的异常 """ pass classRemoteExecError(Exception): """ 远程执行命令,失败时抛出的异常 """ pass classSCPError(Exception): """ 远程下发文件时抛出的异常 """ pass
...
classRemote(object):
def__init__(self,host,username,password=None,port=22,key_filename=None):
self.host=host
self.username=username
self.password=password
self.port=port
self.key_filename=key_filename
self._ssh=None
def_connect(self):
self._ssh=SSHClient()
self._ssh.set_missing_host_key_policy(AutoAddPolicy())
try:
ifself.key_filename:
self._ssh.connect(self.host,username=self.username,port=self.port,key_filename=self.key_filename)
else:
self._ssh.connect(self.host,username=self.username,password=self.password,port=self.port)
exceptAuthenticationException:
self._ssh=None
raiseConnectionError('连接失败,请确认用户名、密码、端口或密钥文件是否有效')
exceptExceptionase:
self._ssh=None
raiseConnectionError('连接时出现意料外的错误:%s'%e)
defget_ssh(self):
ifnotself._ssh:
self._connect()
returnself._ssh
实例化SSHClient类,通过它的connect()方法获取SSH连接。
需要注意的是,远程访问的主机若是第一次连接,属于未知设备需要认证,通过set_missing_host_key_policy()方法设置一种策略,这里使用的是AutoAddPolicy()。
这里的_connect支持两种方式登录,一种是提供主机的用户名密码,另一种是通过密钥文件。在连接时检查如果指定了密钥文件则使用这种方式登录,否则通过用户名密码登录。
_connect()虽然是实际的建立连接的方法,但实际对外接口是get_ssh(),如果已经有建立好的SSH连接直接返回,避免重复建立连接。
classRemote(object):
...
defssh(self,cmd,root_password=None,get_pty=False,super=False):
cmd=self._prepare_cmd(cmd,root_password,super)
stdout=self._exec(cmd,get_pty)
returnstdout
def_prepare_cmd(self,cmd,root_password=None,super=False):
ifself.username!='root'andsuper:
ifroot_password:
cmd="echo'{}'|su-root-c'{}'".format(root_password,cmd)
else:
cmd="echo'{}'|sudo-p''-Ssu-root-c'{}'".format(self.password,cmd)
returncmd
def_exec(self,cmd,gty_pty=False):
channel=self.get_ssh().get_transport().open_session()
ifget_pty:
channel.get_pty()
channel.exec_command(cmd)
stdout=channel.makefile('r',-1).readlines()
stderr=channel.makefile_stderr('r',-1).readlines()
ret_code=channel.recv_exit_status()
ifret_code:
msg=''.join(stderr)ifstderrelse''.join(stdout)
raiseRemoteExecError(msg)
returnstdout
在远程执行某些命令时,可能需要管理员权限,这种时候需要做一些判断,首先判断登录提供的用户名如果不是root,则需要对命令做一些修改。这里的修改有两种情况,一是,该普通用户本身就有sudo权限,只需要把执行的命令加到sudo之后执行就可以,还有一种是普通用户没有sudo权限,需要通过su先切换到root身份之后再执行,这种情况下需要提供root密码。
还有一点要注意的是get_pty这个参数,实际在远程执行sudo命令时,一般主机都会需要通过tty才能执行,通过把get_pty值设置为True,可以模拟tty,但是随之而来也会有一个问题,如果是远程执行一个需要长期运行的进程,例如启动nginx服务,当远程命令执行后SSH退出之后,此次运行的所有程序也会随之结束,所以在需要通过远程命令运行某些服务或程序时,是不能指定get_pty参数的;但同时,如果是普通用户远程登录,是没有权限执行service命令的。建议的一种方式是修改/etc/sudoers配置文件,注释掉Defaultsrequiretty这行。
classRemote(object):
...
defscp(self,local_file,remote_path):
ifnotos.path.exists(local_file):
raiseSCPError("Local%sisn'texists"%local_file)
ifnotos.path.isfile(local_file):
raiseSCPError("%sisnotaFile"%local_file)
sftp=self.get_ssh().open_sftp()
try:
sftp.put(local_file,remote_path)
exceptExceptionase:
raiseSCPError(e)
先确认要下发的文件存在,并且是文件不是目录,如果不是则抛出异常。同时,remote_path需要是远程主机的文件绝对目录,例如/tmp/xxx.xxx,而不能是/tmp。
使用
#coding:utf-8
fromremote_clientimportRemoteClient
rc=RemoteClient('10.1.100.1','test','test_pass')
rc.ssh('whoami')#[u'test\n']
rc.scp('/tmp/test.out','/tmp/test.out')
总结
相较于sh,paramiko好用的不是一星半点,这里只是提供了一个简单的封装,paramiko本身还有很多其他用法,欢迎大家积极讨论。
以上只是本人的一点理解,如果有错误之处,欢迎指正。
以上这篇使用paramiko远程执行命令、下发文件的实例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。
