您的位置:首页 > 编程语言 > Python开发

网卡速率变化导致paramiko模块timeout的失效,多线程超时控制解决办法。

2018-01-13 12:32 483 查看
起因: 上周给几个集群的机器升级软件包,每个集群大概两千台服务器,但是在软件发包和批量执行命令的过程中有两个集群都遇到了问题,在批量执行命令的时候总是会在后面卡住久久不能退出,最后只好手动杀掉进程。
如下图是sshpt批量执行命令时,到最后卡住久久不动,很久以后报出一个TypeError的异常,因为返回值不是列表而是一个异常对象,这个异常并不是关键,只是代码对结果类型判断不全。真正原因应该是某台机器执行命令久久没有返回导致线程不能退出。

通过增加debug日志,我找到了这台有问题的机器。然后ssh上去看看,在ssh登录的过程中,异常的缓慢卡顿。通过ethtool命令,发现了问题,网卡变成百兆了,根据经验,是由于可能网卡或者网线老化导致的。



这个的确是机器的问题,ssh非常慢,导致长时间的卡住,那么如何解决这个问题呢。
这时想到sshpt支持timeout参数,能否通过设置timeout,使执行超时的线程退出,而不是卡在那里。

sshpt  —help
-T <seconds>, --timeout=<seconds>
Timeout (in seconds) before giving up on an SSH
connection (default: 30)


然鹅,加上--timeout参数后并没有什么卵用。。。看了一下sshpt关于timeout的代码,发现这个timeout仅仅在ssh.connect中用到,而exec_command调用中并没有传入timeout。
def paramikoConnect(host, username, password, timeout, port=22):
"""Connects to 'host' and returns a Paramiko transport object to use in further communications"""
# Uncomment this line to turn on Paramiko debugging (good for troubleshooting why some servers report connection failures)
#paramiko.util.log_to_file('paramiko.log')
ssh = paramiko.SSHClient()
try:
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
#ssh.connect中传入timeout
ssh.connect(host, port=port, username=username, password=password, timeout=timeout)
except Exception, detail:
# Connecting failed (for whatever reason)
ssh = str(detail)
return ssh

def sudoExecute(transport, command, password, run_as='root'):
"""Executes the given command via sudo as the specified user (run_as) using the given Paramiko transport object.
Returns stdout, stderr (after command execution)"""
#exec_command中并没有timeout控制
stdin, stdout, stderr = transport.exec_command("sudo -S -u %s %s" % (run_as, command))
if stdout.channel.closed is False: # If stdout is still open then sudo is asking us for a password
stdin.write('%s\n' % password)
stdin.flush()
return stdout, stderr
测试发现,这台100M网卡的机器在ssh.connect的过程中并没有超时,而是主要卡在命令执行的调用exec_command函数

其实paramiko的代码中是支持对exec_command的timeout参数传入
class SSHClient (ClosingContextManager):
def exec_command(
self,
command,
bufsize=-1,
timeout=None,    #支持timeout
get_pty=False,
environment=None,
):

chan = self._transport.open_session(timeout=timeout)
if get_pty:
chan.get_pty()
chan.settimeout(timeout)
if environment:
chan.update_environment(environment)
chan.exec_command(command)
stdin = chan.makefile('wb', bufsize)
stdout = chan.makefile('r', bufsize)
stderr = chan.makefile_stderr('r', bufsize)
return stdin, stdout, stderr


于是修改了sshpt的sudoExecute代码,加上一个timeout=10测试一下
def sudoExecute(transport, command, password, run_as='root'):
"""Executes the given command via sudo as the specified user (run_as) using the given Paramiko transport object.
Returns stdout, stderr (after command execution)"""
#原来没有timeout参数
#stdin, stdout, stderr = transport.exec_command("sudo -S -u %s %s" % (run_as, command))
#加入timeout=10秒,执行10秒超时
stdin, stdout, stderr = transport.exec_command("sudo -S -u %s %s" % (run_as, command),timeout=10)
if stdout.channel.closed is False: # If stdout is still open then sudo is asking us for a password
stdin.write('%s\n' % password)
stdin.flush()
return stdout, stderr


结果呢,依然还是卡住了。。。

增加debug日志。
def exec_command(
self,
command,
bufsize=-1,
timeout=None
get_pty=False,
environment=None,
):
#调用open_session()
chan = self._transport.open_session(timeout=timeout)
def open_session(
self,
window_size=None,
max_packet_size=None,
timeout=None,
):
#调用open_channel()
return self.open_channel('session',
window_size=window_size,
max_packet_size=max_packet_size,
timeout=timeout)
def open_channel(self,
kind,
dest_addr=None,
src_addr=None,
window_size=None,
max_packet_size=None,
timeout=None):
if not self.active:
raise SSHException('SSH session not active')
timeout = 3600 if timeout is None else timeout
....
#timeout判断代码
start_ts = time.time()
while True:
event.wait(0.1)
if not self.active:
e = self.get_exception()
if e is None:
e = SSHException('Unable to open channel.')
raise e
if event.is_set():
break    #通过加日志发现,在这里成功break跳出了timeout的判断逻辑。
elif start_ts + timeout < time.time():
raise SSHException('Timeout opening channel.')
chan = self._channels.get(chanid)
# 但是执行_channels.get(chanid)的时候卡住没有返回。
# 这说明这种情况下get(chanid)并没有在timeout的判断逻辑中,所以设置timeout并没有起到作用
if chan is not None:
return chan
e = self.get_exception()
if e is None:
e = SSHException('Unable to open channel.')
raise e


未完待续

在github上提的issues https://github.com/paramiko/paramiko/issues/1150
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python 多线 paramik