Иногда длительные команды ssh прекращают печать на стандартный вывод
Я использую Perl::Net::SSH для автоматизации запуска некоторых скриптов на своих удаленных компьютерах. Однако для выполнения некоторых из этих сценариев требуется очень много времени (час или два), а иногда я прекращаю получать от них данные, фактически не теряя соединения.
Вот код, который я использую:
sub run_regression_tests {
for(my $i = 0; $i < @servers; $i++){
my $inner = $users[$i];
foreach(@$inner){
my $user = $_;
my $server = $servers[$i];
my $outFile;
open($outFile, ">" . $outputDir . $user . "@" . $server . ".log.txt");
print $outFile "Opening connection to $user at $server on " . localtime() . "\n\n";
close($outFile);
my $pid = $pm->start and next;
print "Connecting to $user@" . "$server...\n";
my $hasWentToDownloadYet = 0;
my $ssh = Net::SSH::Perl->new($server, %sshParams);
$ssh->login($user, $password);
$ssh->register_handler("stdout", sub {
my($channel, $buffer) = @_;
my $outFile;
open($outFile, ">>", $outputDir . $user . "@" . $server . ".log.txt");
print $outFile $buffer->bytes;
close($outFile);
my @lines = split("\n", $buffer->bytes);
foreach(@lines){
if($_ =~ m/REGRESSION TEST IS COMPLETE/){
$ssh->_disconnect();
if(!$hasWentToDownloadYet){
$hasWentToDownloadYet = 1;
print "Caught exit signal.\n";
print("Regression tests for ${user}\@${server} finised.\n");
download_regression_results($user, $server);
$pm->finish;
}
}
}
});
$ssh->register_handler("stderr", sub {
my($channel, $buffer) = @_;
my $outFile;
open($outFile, ">>", $outputDir . $user . "@" . $server . ".log.txt");
print $outFile $buffer->bytes;
close($outFile);
});
if($debug){
$ssh->cmd('tail -fn 40 /GDS/gds/gdstest/t-gds-master/bin/comp.reg');
}else{
my ($stdout, $stderr, $exit) = $ssh->cmd('. ./.profile && cleanall && my.comp.reg');
if(!$exit){
print "SSH connection failed for ${user}\@${server} finised.\n";
}
}
#$ssh->cmd('. ./.profile');
if(!$hasWentToDownloadYet){
$hasWentToDownloadYet = 1;
print("Regression tests for ${user}\@${server} finised.\n");
download_regression_results($user, $server);
}
$pm->finish;
}
}
sleep(1);
print "\n\n\nAll tests started. Tests typically take 1 hour to complete.\n";
print "If they take significantly less time, there could be an error.\n";
print "\n\nNo output will be printed until all commands have executed and finished.\n";
print "If you wish to watch the progress tail -f one of the logs this script produces.\n Example:\n\t" . 'tail -f ./gds1@tdgds10.log.txt' . "\n";
$pm->wait_all_children;
print "\n\nAll Tests are Finished. \n";
}
А вот мой%sshParams:
my %sshParams = (
protocol => '2',
port => '22',
options => [
"TCPKeepAlive yes",
"ConenctTimeout 10",
"BatchMode yes"
]
);
Иногда случайно одна из длительных команд просто останавливает печать / запуск событий stdout или stderr и никогда не завершается. Соединение ssh не умирает (насколько я знаю), потому что $ssh->cmd
все еще блокирует
Есть идеи, как исправить это поведение?
2 ответа
Сбой, вероятно, из-за того, как вы смотрите на вывод для REGRESSION TEST IS COMPLETE
отметка. Он может быть разделен на два разных пакета SSH, поэтому ваш обратный вызов никогда его не найдет.
Лучше использовать удаленную команду, которая завершается, когда это делается в виде одной строки:
perl -pe 'BEGIN {$p = open STDIN, "my.comp.reg |" or die $!}; kill TERM => -$p if /REGRESSION TEST IS COMPLETE/}'
В противном случае вы закрываете удаленное соединение, но не останавливаете удаленный процесс, который останется в живых.
Кроме того, вы должны попробовать использовать Net::OpenSSH или Net::OpenSSH::Parallel вместо Net:: SSH::Perl:
use Net::OpenSSH::Parallel;
my $pssh = Net::OpenSSH::Parallel->new;
for my $i (0..$#server) {
my $server = $server[$i];
for my $user (@{$users[$ix]}) {
$pssh->add_host("$user\@$server", password => $password);
}
}
if ($debug) {
$pssh->all(cmd => { stdout_file => "$outputDir%USER%\@%HOST%.log.txt",
stderr_to_stdout => 1 },
'fail -fn 40 /GDS/gds/gdstest/t-gds-master/bin/comp.reg');
}
else {
$pssh->all(cmd => { stdout_file => "$outputDir%USER%\@%HOST%.log.txt",
stderr_to_stdout => 1 },
'. ./.profile && cleanall && my.comp.reg');
}
$pssh->all(scp_get => $remote_regression_results_path, "regression_results/%USER%\@%HOST%/");
$pssh->run;
В вашем хеше%sshParams вам может понадобиться добавить "TCPKeepAlive yes" к вашим опциям:
$sshParams{'options'} = ["BatchMode yes", "TCPKeepAlive yes"];
Эти параметры могут быть или не быть подходящими для вас, но я рекомендую установить TCPKeepAlive для любого долго работающего SSH-соединения. Если на вашем пути есть какой-либо брандмауэр с отслеживанием состояния, он может сбросить состояние, если он не передавал трафик через соединение в течение длительного периода времени.