使用paramiko远程执行命令、下发文件的实例


Posted in Python onOctober 01, 2017

写部署脚本时,难免涉及到一些远程执行命令或者传输文件。

之前一直使用sh库,调用sh.ssh远程执行一些命令,sh.scp传输文件,但是实际使用中还是比较麻烦的,光是模拟用户登陆这一点,还需要单独定义方法模拟输入。

感受一下:

from sh import ssh
PASS = 'xxxx'
def ssh_interact(line, stdin):
  line = line.strip()
  print(line)
  if line.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方法判断执行退出码更好一些。

安装

可以通过使用pip install paramiko安装,细节这里不再赘述。

封装

首先定义几个异常

# coding: utf-8
import os.path

from paramiko import SSHClient, AutoAddPolicy, AuthenticationException


class ConnectError(Exception):
  """
  连接错误时抛出的异常
  """
  pass

class RemoteExecError(Exception):
  """
  远程执行命令,失败时抛出的异常
  """
  pass

class SCPError(Exception):
  """
  远程下发文件时抛出的异常
  """
  pass
...
class Remote(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:
      if self.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)
    except AuthenticationException: 
      self._ssh = None
      raise ConnectionError('连接失败,请确认用户名、密码、端口或密钥文件是否有效')
    except Exception as e:
      self._ssh = None
      raise ConnectionError('连接时出现意料外的错误:%s' % e)

  def get_ssh(self):
    if not self._ssh:
      self._connect()
    return self._ssh

实例化SSHClient类,通过它的connect()方法获取SSH连接。

需要注意的是,远程访问的主机若是第一次连接,属于未知设备需要认证,通过set_missing_host_key_policy()方法设置一种策略,这里使用的是AutoAddPolicy()。

这里的_connect支持两种方式登录,一种是提供主机的用户名密码,另一种是通过密钥文件。在连接时检查如果指定了密钥文件则使用这种方式登录,否则通过用户名密码登录。

_connect()虽然是实际的建立连接的方法,但实际对外接口是get_ssh(),如果已经有建立好的SSH连接直接返回,避免重复建立连接。

class Remote(object):
  ...
  def ssh(self, cmd, root_password=None, get_pty=False, super=False):
    cmd = self._prepare_cmd(cmd, root_password, super)
    stdout = self._exec(cmd, get_pty)
    return stdout

  def _prepare_cmd(self, cmd, root_password=None, super=False):
    if self.username != 'root' and super:
      if root_password:
        cmd = "echo '{}'|su - root -c '{}'".format(root_password, cmd)
      else:
        cmd = "echo '{}'|sudo -p '' -S su - root -c '{}'".format(self.password, cmd)
    return cmd

  def _exec(self, cmd, gty_pty=False):
    channel = self.get_ssh().get_transport().open_session()
    if get_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()
    if ret_code:
      msg = ''.join(stderr) if stderr else ''.join(stdout)
      raise RemoteExecError(msg)
    return stdout

在远程执行某些命令时,可能需要管理员权限,这种时候需要做一些判断,首先判断登录提供的用户名如果不是root,则需要对命令做一些修改。这里的修改有两种情况,一是,该普通用户本身就有sudo权限,只需要把执行的命令加到sudo之后执行就可以,还有一种是普通用户没有sudo权限,需要通过su先切换到root身份之后再执行,这种情况下需要提供root密码。

还有一点要注意的是get_pty这个参数,实际在远程执行sudo命令时,一般主机都会需要通过tty才能执行,通过把get_pty值设置为True,可以模拟tty,但是随之而来也会有一个问题,如果是远程执行一个需要长期运行的进程,例如启动nginx服务,当远程命令执行后SSH退出之后,此次运行的所有程序也会随之结束,所以在需要通过远程命令运行某些服务或程序时,是不能指定get_pty参数的;但同时,如果是普通用户远程登录,是没有权限执行service命令的。建议的一种方式是修改/etc/sudoers配置文件,注释掉Defaults requiretty这行。

class Remote(object):
  ...

  def scp(self, local_file, remote_path):
    if not os.path.exists(local_file):
      raise SCPError("Local %s isn't exists" % local_file)
    if not os.path.isfile(local_file):
      raise SCPError("%s is not a File" % local_file)
    sftp = self.get_ssh().open_sftp()
    try:
      sftp.put(local_file, remote_path)
    except Exception as e:
      raise SCPError(e)

先确认要下发的文件存在,并且是文件不是目录,如果不是则抛出异常。同时,remote_path需要是远程主机的文件绝对目录,例如/tmp/xxx.xxx,而不能是/tmp。

使用

# coding: utf-8
from remote_client import RemoteClient

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远程执行命令、下发文件的实例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python实现的二维码生成小软件
Jul 11 Python
利用python求相邻数的方法示例
Aug 18 Python
简单实现python收发邮件功能
Jan 05 Python
Python实现的读写json文件功能示例
Jun 05 Python
CentOS7安装Python3的教程详解
Apr 10 Python
ubuntu 18.04搭建python环境(pycharm+anaconda)
Jun 14 Python
Python+threading模块对单个接口进行并发测试
Jun 25 Python
pandas将多个dataframe以多个sheet的形式保存到一个excel文件中
Oct 10 Python
python pyinstaller打包exe报错的解决方法
Nov 02 Python
Python TCPServer 多线程多客户端通信的实现
Dec 31 Python
python计算二维矩形IOU实例
Jan 18 Python
详解python tkinter 图片插入问题
Sep 03 Python
解决Scrapy安装错误:Microsoft Visual C++ 14.0 is required...
Oct 01 #Python
win10下Python3.6安装、配置以及pip安装包教程
Oct 01 #Python
Python实现字符串反转的常用方法分析【4种方法】
Sep 30 #Python
Python实现利用最大公约数求三个正整数的最小公倍数示例
Sep 30 #Python
Python基于pygame模块播放MP3的方法示例
Sep 30 #Python
Python实现自动为照片添加日期并分类的方法
Sep 30 #Python
Python实现获取照片拍摄日期并重命名的方法
Sep 30 #Python
You might like
JAVA/JSP学习系列之四
2006/10/09 PHP
php简单封装了一些常用JS操作
2007/02/25 PHP
fckeditor上传文件按日期存放及重命名方法
2015/05/22 PHP
PHP使用正则表达式获取微博中的话题和对象名
2015/07/18 PHP
Laravel框架生命周期与原理分析
2018/06/12 PHP
JavaScript入门教程(11) js事件处理
2009/01/31 Javascript
EXT中xtype的含义分析
2010/01/07 Javascript
用jquery实现点击栏目背景色改变
2012/12/10 Javascript
JS实现的用来对比两个用指定分隔符分割的字符串是否相同
2014/09/19 Javascript
用原生JS获取CLASS对象(很简单实用)
2014/10/15 Javascript
jquery手风琴特效插件
2015/02/04 Javascript
JavaScript获取页面中第一个锚定文本的方法
2015/04/03 Javascript
jQuery结合ajax实现动态加载文本内容
2015/05/19 Javascript
深入解析Javascript闭包的功能及实现方法
2016/07/10 Javascript
jQuery插件echarts去掉垂直网格线用法示例
2017/03/03 Javascript
jQuery插件Echarts实现的渐变色柱状图
2017/03/23 jQuery
使用cookie绕过验证码登录的实现代码
2017/10/12 Javascript
Node解决简单重复问题系列之Excel内容的获取
2018/01/02 Javascript
vue页面离开后执行函数的实例
2018/03/13 Javascript
Node.js的Koa实现JWT用户认证方法
2018/05/05 Javascript
angular6.0开发教程之如何安装angular6.0框架
2018/06/29 Javascript
vue实现在一个方法执行完后执行另一个方法的示例
2018/08/25 Javascript
详解puppeteer使用代理
2018/12/27 Javascript
JS实现的图片选择顺序切换和循环切换功能示例【测试可用】
2018/12/28 Javascript
Vue使用JSEncrypt实现rsa加密及挂载方法
2020/02/07 Javascript
ant design vue嵌套表格及表格内部编辑的用法说明
2020/10/28 Javascript
[01:08:33]OG vs VGJ.T 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
Python基于辗转相除法求解最大公约数的方法示例
2018/04/04 Python
python图像和办公文档处理总结
2019/05/28 Python
人工神经网络算法知识点总结
2019/06/11 Python
Django使用uwsgi部署时的配置以及django日志文件的处理方法
2019/08/30 Python
在Keras中利用np.random.shuffle()打乱数据集实例
2020/06/15 Python
入党自我鉴定范文
2013/10/04 职场文书
四风问题对照检查材料思想汇报
2014/10/07 职场文书
教师群众路线心得体会
2014/11/04 职场文书
只需要100行Python代码就可以实现的贪吃蛇小游戏
2021/05/27 Python