【问题标题】:Paramiko and exec_command - killing remote process?Paramiko 和 exec_command - 杀死远程进程?
【发布时间】:2011-10-12 02:40:54
【问题描述】:

我正在使用 Paramiko 来tail -f 远程服务器上的文件。

以前,我们通过ssh -t 运行此程序,但事实证明这很不稳定,-t 导致我们的远程调度系统出现问题。

我的问题是当脚本捕捉到信号时如何杀死尾巴?

我的脚本(基于Long-running ssh commands in python paramiko module (and how to end them)

#!/usr/bin/env python2
import paramiko
import select

client = paramiko.SSHClient()
client.load_system_host_keys()
client.connect('someserver', username='victorhooi', password='blahblah')
transport = client.get_transport()
channel = transport.open_session()

channel.exec_command("tail -f /home/victorhooi/macbeth.txt")
while True:
    try:
        rl, wl, xl = select.select([channel],[],[],0.0)
        if len(rl) > 0:
            # Must be stdout
            print channel.recv(1024)
    except KeyboardInterrupt:
        print("Caught control-C")
        client.close()
        channel.close()
        exit(0)

脚本成功捕获我的 Ctrl-C 并结束。但是,它使tail -f 进程在远程系统上运行。

client.close() 和 channel.close() 似乎都不会终止它。

我可以在 except 块中发出什么命令来杀死它?

远程服务器正在运行 Solaris 10。

【问题讨论】:

    标签: python ssh solaris signals paramiko


    【解决方案1】:

    您应该使用 ssh keepalives...您遇到的问题是远程 shell 无法知道(默认情况下)您的 ssh 会话已被终止。 Keepalives 将使远程 shell 检测到您终止了会话

    client = paramiko.SSHClient()
    client.load_system_host_keys()
    client.connect('someserver', username='victorhooi', password='blahblah')
    transport = client.get_transport()
    transport.set_keepalive(1)   # <------------------------------
    # ... carry on as usual...
    

    将 keepalive 值设置为尽可能低(甚至 1 秒)...几秒钟后,远程 shell 将看到 ssh 登录已终止,并将终止由它生成的所有进程。

    【讨论】:

    • 嗯,我尝试添加 set_keepalive(5) 行 - 仍然没有运气 =(。即使在等待 5 之后,在 Ctrl-C-ing Python 脚本之后,“tail -f”进程仍然存在秒(或更长)。呃。远程服务器是 Solaris 10,顺便说一句,如果这很重要的话。
    • 你需要等待几个keepalive间隔...将你的keepalive设置为1,我很确定你会在10秒内看到它终止(如果Solaris表现得像我的BSD盒子)
    • @victorhooi 您应该使用类似的任何相关详细信息编辑问题。
    • @victorhooi,如果您使用 channel.invoke_shell() 显式调用 shell,这可能会让您的事情变得更容易...尽管这需要对您的脚本进行更多修改
    【解决方案2】:

    有一种方法可以做到这一点。它就像在 shell 上一样工作

    ssh -t commandname
    

    选项 -t 打开一个伪 pty 来帮助 ssh 跟踪这个过程应该持续多长时间。同样可以通过 pormiko via

    完成
    channel.get_pty()
    

    在 execute_command(...) 之前。这不会像使用 channel.invoke_shell() 那样打开 shell,它只是请求这样一个伪接口来绑定所有进程。如果在远程机器上发出 ps aux 也可以看到效果,该进程现在以 ptxXY 接口附加到 sshd。

    【讨论】:

    • 请注意,这有效,但前提是您正在运行的程序在给予 SIGHUP 时表现良好
    【解决方案3】:

    这里有一种获取远程进程ID的方法:

    def execute(channel, command):
        command = 'echo $$; exec ' + command
        stdin, stdout, stderr = channel.exec_command(command)
        pid = int(stdout.readline())
        return pid, stdin, stdout, stderr
    

    这里是如何使用它(将...替换为原始问题中的位):

    pid, _, _, _ = execute(channel, "tail -f /home/victorhooi/macbeth.txt")
    while True:
        try:
            # ...
        except KeyboardInterrupt:
            client.exec_command("kill %d" % pid)
            # ...
    

    【讨论】:

    • 这实际上是获取远程shell的pid,而不是远程shell中运行的命令的pid,对吧?
    • @DavidDoria 同样的事情。 :-) 使用exec 表示执行的命令继承了shell PID。
    【解决方案4】:

    虽然不是最有效的方法,但应该可以。在你 CTRL+C;在 KeyboardInterrupt 处理程序中,您可以像这样exec_command("killall -u %s tail" % uname)

    #!/usr/bin/env python2
    
    import paramiko
    import select
    
    import time
    ltime = time.time()
    
    # Or use random:
    # import random
    # ltime = random.randint(0, 500)
    
    uname = "victorhooi"
    client = paramiko.SSHClient()
    client.load_system_host_keys()
    client.connect('someserver', username=uname, password='blahblah')
    transport = client.get_transport()
    channel = transport.open_session()
    
    channel.exec_command("tail -%df /home/victorhooi/macbeth.txt" % ltime)
    while True:
        try:
            rl, wl, xl = select.select([channel],[],[],0.0)
            if len(rl) > 0:
                # Must be stdout
                print channel.recv(1024)
        except KeyboardInterrupt:
            print("Caught control-C")
            channel.close()
            try:
                # open new socket and kill the proc..
                client.get_transport().open_session().exec_command("kill -9 `ps -fu %s | grep 'tail -%df /home/victorhooi/macbeth.txt' | grep -v grep | awk '{print $2}'`" % (uname, ltime))
            except:
                pass
        
            client.close()
            exit(0)
    

    这将终止任何名为 tail 的打开进程。这可能会导致问题,但如果您打开了不想关闭的 tails,如果是这种情况,您可以 grepps,获取 pid 和 kill -9

    首先,设置tail 以在跟随之前从文件末尾读取n 行。将n 设置为像time.time() 这样的唯一nuber,因为tail 不在乎该数字是否大于文件中的行数,time.time() 中的大数字应该会导致问题并且是唯一的。然后 grep 在ps 中查找该唯一编号:

       client.get_transport().open_session().exec_command("kill -9 `ps -fu %s | grep 'tail -%df /home/victorhooi/macbeth.txt' | grep -v grep | awk '{print $2}'`" % (uname, ltime))
    

    【讨论】:

    • 这可能对我们不起作用——我们在远程服务器上运行了许多其他尾进程来处理其他事情。有没有一种方法可以在运行 exec_command 时获取尾部的 PID,保存它,然后以这种方式杀死它? (是的,所有这些远程尾部都有些混乱 - 我希望用像 RabbitMQ 这样的适当总线来替换它,但是我们必须凑合使用)。
    • 看我上面的编辑,这个评论的格式很奇怪)。
    • 是的,该解决方案有效。但是,正如您所注意到的,它有点 hacky(整个 PID 的 grepping,特别是如果多个人跟踪同一个文件)。它仍然有效。我很惊讶没有办法通过 Paramiko 提取 PID,就像你通常可以使用 Python 的 POpen 一样。奇怪。
    • 该解决方案不是pythonic解决方案。您永远不必在远程机器上生成一个进程来终止您已经生成的命令...如果该命令也需要被终止怎么办?如果您在没有 kill 命令的机器上运行 ssh 服务器怎么办?
    【解决方案5】:

    ssh -t 也有同样的问题。有一个名为 closer 的库 - 它通过 ssh 运行远程进程并自动为您关闭。看看吧。

    【讨论】:

      【解决方案6】:

      我刚刚遇到了这个问题,无法在最后发出 pkill 来关闭进程。

      更好的解决方案是将您正在运行的命令更改为:

      tail -f /path/to/file & { read ; kill %1; }
      

      这将使您可以根据需要运行您的 tail 命令。一旦您向远程进程发送换行符, kill %1 将执行并停止您后台运行的 tail 命令。 (供参考:%1 是一个作业规范,用于描述您会话中的第一个后台进程,即 tail 命令)

      【讨论】:

        【解决方案7】:

        对于“tail”,您可以使用 --pid=PID 参数并让 tail 处理它:

          --pid=PID  with -f, terminate after process ID, PID dies
        

        【讨论】:

          【解决方案8】:

          您可以按照https://stackoverflow.com/a/38883662/565212 中的说明使用get_pty。

          例如场景 - 何时调用 client/channel.close():
          Step1:执行写入日志文件的远程命令。
          Step2:生成一个执行tail命令并阻塞在readline循环中的线程
          Step3:在主线程中,当命令返回,就知道不会有日志了,杀掉尾线程。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-11-01
            • 1970-01-01
            • 1970-01-01
            • 2014-03-23
            • 1970-01-01
            相关资源
            最近更新 更多