Python远程控制模块paramiko遇到的问题及解决记录

最近一直在开发自动化运维发布平台,底层命令行、文件通道主要基于 paramiko 模块,使用过程中遇到各种各样的问题,本文主要用于收集问题及解决记录,以备后续使用。Python远程控制模块paramiko遇到的问题及解决记录

一、Error reading SSH protocol banner 连接错误

这个关键词,在百度、谷歌一搜一大把的提问,也有少部分给出了解决方案,但是最终都无法解决,我经过不断尝试和解读 paramiko 源码,终于搞定了这个问题,在此记录分享下。

1、具体报错信息:

2、解决办法:

重新下载 paramiko 插件源码,解压后,编辑安装目录下的 transport.py 文件:

vim build/lib/paramiko/transport.py

 

搜索 self.banner_timeout 关键词,并将其参数改大即可,比如改为 300s:

self.banner_timeout = 300

最后,重装 paramiko 即可。

3、下面的曲折、啰嗦的解决过程,不喜请跳过:

在谷歌搜到一个老外相关提问,虽然他说的是 pysftp,其实也是基于 paramiko:

https://stackoverflow.com/questions/34288526/pysft-paramiko-grequests-error-reading-ssh-protocol-banner/44493465#44493465

他最后给出了他的解决方案:

UPDATE:

It seems the problem is caused by importing the package grequests. If I do not import grequests, pysftp works as expected. The issue was raised before but has not been solved

意思是,在 paramiko 使用前,先 import grequests,就能解决问题。我照做之后,发现对手头的现网环境无效,可能错误产生的原因不一样。

但是,我从老外的问题描述过程中,找到了解决方法,他是这样说的:

I have already tried changing the banner timeout from 15 seconds to 60 secs in the transport.py, but it did not solve the problem.

我看到有个 timeout 和 transport.py,就想到现网那些报 Error reading SSH protocol banner 错误的机器也是非常卡,而且目测了下发起 paramiko 连接到报错的时间,基本是相同的。

于是系统中搜索,并找到了 transport.py 这个文件:

/usr/lib/python2.7/site-packages/paramiko/transport.py

并搜了下 banner,发现果然有一个参数设置,而且和目测的超时基本一致!

Python远程控制模块paramiko遇到的问题及解决记录

于是,顺手修改成 300S,并重新测试发现没任何效果,依然 15S 超时。接着打断点、甚至移走这个文件,问题依旧!!看来这个文件不会被引用。。。

回到最初的报错信息,发现里面显示的是:

而系统里面搜不到这个问题,最后醍醐灌顶,发觉 Python 模块编译后,基本是以 egg 文件保存的,看来 必须修改源码才行了。

于是 cd 到 paramiko 的源码目录,执行搜索,找到 2 各 transport.py 文件:

尝试将文件中的 self.banner_timeout 值改成 300,重新安装 paramiko,结果一次性测试成功!

然后,我顺便在老外的帖子回答了下(请忽略蹩脚的英语),算是回馈吧!Python远程控制模块paramiko遇到的问题及解决记录

二、paramiko 远程执行后台脚本“阻塞”问题

我写的远程命令通道上线之后,发现在远程脚本中后台再执行另一个脚本,通道会一直等待后台脚本执行完成才会返回,有时甚至会僵死。

1、复现过程如下:

①、编写测试脚本

脚本 1:test.sh

脚本 2:run.sh

脚本 3:test.py

将 test.sh 和 run.sh 传到远程服务器上,比如放到 192.168.1.10:/tmp/下。

②、发起远程执行

在本地执行 python test.py,会发现整个脚本不会立即打印 run ok,而是等 30s 之后才打印包括 test.sh 的所有输出信息。

2、解决办法

将远程脚本的标准输出 stdout 重定向到错误输出 stderr 即可,test.py 修改如下:

现在执行,就能立即得到结果了。其实原因很简单,因为 stdout(标准输出),输出方式是行缓冲。输出的字符会先存放在缓冲区,等按下回车键时才进行实际的 I/O 操作,导致 paramiko 远程命令产生等待问题。而 stderr(标准错误),是不带缓冲的,这使得出错信息可以直接尽快地显示出来。所以,这里只要将脚本执行的标准输出重定向到错误输出(1>&2),然后 paramiko 就可以使用 stderr 快速读取远程打屏信息了。

三、This operation would block forever 报错解决

这次扩容一个基于 pramiko 的自动化 apiserver,结果发现在新环境执行远程命令或文件传输时,抛了如下报错:

总以为是 python 组件安装有问题,反反复复检查,最终发现居然是多装了一个插件导致的!

解决办法:

删除已经安装 greenlet 插件即可,具体原因见后文:

下面是"艰难险阻"的解决过程,不喜勿看:

1、看到报错,作为懒人第一时间就搜了下 【This operation would block forever', <Hub】这个关键词,发现没能 get 到解决方案。

2、按照经验,我先找到图中 _check_banner 函数如下:

3、很明显这个异常由 buf = self.packetizer.readline(timeout) 语句抛出,我印象中的粗暴定位方法就是不使用 try,直接将此语句执行看看:

结果报错信息就更加具体了,如下所示:

这次基本就定位到了 gevent 和 greenlet 这个真凶了!本以为是我的 apiserver 调用了 gevent,结果定位了半天,确定并没有使用。而且印象中 paramiko 这个插件也没用到 gevent,可这异常是怎么来的?

直到我再次在谷歌搜索【LoopExit: ('This operation would block forever', <Hub at】关键词,找到一个博客文章:http://www.hongquan.me/?p=178,总算知道是什么原因了!

具体原因:主要是因为 greenlet 里面有个 run 函数,覆盖了 paramiko 的 transport.py 里面的同名函数,导致 paramiko 执行 _check_banner 时,实际调用了 greenlet 的 run 函数,因此报错!再次醉了!

《未完待续,更多问题,后续继续补充...》

评论已关闭!