Как прекратить сеанс Ruby Net :: SSH, если для удаленного channel.exec требуется слишком много времени? - PullRequest
0 голосов
/ 10 июня 2019

Я использую Ruby Net :: SSH для SSH к серверам, которые я отслеживаю, запускаю команды на этих серверах через channel.exec, а затем собираю вывод команды в качестве данных мониторинга. Иногда на сервере возникает ошибка (например, неисправный жесткий диск), из-за которой он зависает бесконечно, не реагируя на команду, отправленную из Ruby. Я подтвердил это, выполнив ту же команду непосредственно на неверно работающий сервер через консоль терминала и наблюдая зависание оболочки без ответа. Ruby будет бесконечно ждать ответа от канала SSH, тем самым останавливая выполнение сценария мониторинга. Как я могу сказать Руби, что время ожидания истекло через несколько секунд?

Я изучал Google и Stack Overflow, но единственные ответы, которые я нашел до сих пор, - это решения, указывающие на библиотеку Ruby Timeout :: timeout, а также несколько статей, объясняющих, почему я никогда не должен использовать эту библиотеку:

Наилучший вариант, который я нашел до сих пор, - это добавить «timeout 10» к команде Net: SSH отправляет с channel.exec, таким образом, устанавливая ограничение в 10 секунд, налагаемое оболочкой Linux:

channel.exec("timeout 10 sleep infinity") do |ch, command_executed|

Это похоже на работу при тестировании непосредственно в терминальной консоли:

bash-4.1$ time timeout 10 sleep infinity
real    0m10.001s
user    0m0.001s
sys     0m0.000s

Мое беспокойство по поводу вышеупомянутого решения заключается в том, что оно полагается на удаленные серверы, чтобы надежно устанавливать собственные ограничения по времени. Единственная причина, по которой я запускаю сценарий мониторинга, - это обнаружение сбоев на удаленных серверах, что означает, что я не могу предположить, что они будут надежно выполнять любые команды, которые я посылаю. Хотя это решение, безусловно, лучше, чем ничего, я предпочитаю, чтобы мой монитор не зависел от объектов, которые он должен контролировать. В идеале я бы хотел, чтобы Ruby установил собственное время ожидания независимо от удаленных серверов.

Вот пример кода, который вы можете использовать для воспроизведения симптома. Обратите внимание, что выполнение этого в IRB приведет к зависанию вашего сеанса IRB, в то время как он ожидает в течение неопределенного времени ответа, который не приходит, поэтому вам придется убить его с помощью ctrl-c.

Я проверял это с ruby-2.4.1. Обратите внимание, что для проверки этого вам понадобится работающий сервер SSH и учетная запись пользователя с разрешениями SSH.

def wait_forever_for_ssh_response()
   command_response = "" # initialize command_response to empty string to keep its scope local

   # SSH to @remote_server and run "sleep infinity"
   begin
      Net::SSH.start(@remote_server, @remote_user, :verify_host_key => :never, :timeout => 15) do |ssh_session|
         ssh_session.open_channel do |channel|
            channel.exec("sleep infinity") do |ch, command_executed|

               # report command results with return code 0 (command completed and exited normally)
               channel.on_data do |ch, command_return|
                  puts command_return
               end

               # report command results with all other return codes (command exited with error)
               channel.on_extended_data do |ch, type, command_error|
                  puts command_error
               end

               # report SSH channel closed
               channel.on_close do |ch|
                  puts "closing connection to #{@remote_server}"
               end
            end      # end channel.exec

            # report SSH failed to open channel
            channel.on_open_failed do |ch, failure_reason_code, failure_description|
               puts " SSH connection to #{@remote_server} failed: #{failure_reason_code} #{failure_description}"
            end
         end         # end ssh_session.open_channel

         ssh_session.loop    # this registers the above channel callback handlers (channel.on_[event])
      end    # end Net::SSH.start

   # report any other errors
   rescue => error
      puts "command failed: #{error.inspect} #{error.backtrace.inspect}"
   end       # end begin

   puts command_response
end          # end method wait_forever_for_ssh_response()


# load net/ssh library, set server and user names, and connect to @remote_server
require 'net/ssh'
@remote_server = "your SSH server name or IP address goes here"
@remote_user = "your SSH user name goes here"
wait_forever_for_ssh_response

Желаемый результат заключается в том, что Ruby будет ждать 10 секунд, и, если нет ответа от удаленного сервера, закройте сеанс SSH и продолжите выполнение после этой строки:

end    # end Net::SSH.start

Фактический результат заключается в том, что Ruby бесконечно ждет ответа от канала SSH.

...