Я использую 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.