我想对我正在跟踪的几千台机器(我自己的机器)做一些非阻塞SSH ,我有一个Dancer应用程序启动并运行,我愿意使用AnyEvent::timer来执行SSH异步命令(每台机器都有自己的轮询间隔,我不希望一台机器等待另一台机器完成其SSH工作)。
我想知道,在同步环境中异步操作的最佳方式是什么?
我想对我正在跟踪的几千台机器(我自己的机器)做一些非阻塞SSH ,我有一个Dancer应用程序启动并运行,我愿意使用AnyEvent::timer来执行SSH异步命令(每台机器都有自己的轮询间隔,我不希望一台机器等待另一台机器完成其SSH工作)。
我想知道,在同步环境中异步操作的最佳方式是什么?
从 Web 脚本中运行任何外部命令并不是一个好主意。一方面,如果您的外部调用因任何原因阻塞或崩溃,它将给用户带来糟糕的体验(即使那个用户只是你)。然后,以网络用户身份运行外部命令可能会带来很多安全隐患——我认为您的网络用户很可能设置了无密码 ssh,不是吗?如果有人在您的脚本中发现了一些安全漏洞并设法使用它来 ssh 进入您的服务器怎么办?
相反,您应该创建单独的服务或进程,该服务或进程将使用 ssh(或其他)定期轮询您的服务器状态,并将该扫描的结果保存到数据库 - Postgres 或 MySQL。
然后,更改您的 Dancer 应用程序以显示从数据库收集的结果,而不是执行实时 ssh 请求。这样,它将非常快速和安全。
我不是一个好主意,但这是可能的。我有一个大型的 Dancer 应用程序可以远程执行脚本,我正在使用 fork 和 Net::SSH2 来执行此操作。我尝试使用线程,但有些模块不是线程安全的,所以我建议使用 fork。
我在我的博客http://perlondancer.blogspot.mx/2014/04/executing-remote-commands-from-dancer.html中有一些评论,这个要点是下面的代码示例:https://gist.github。 com/johandry/11197516
#!/usr/bin/env perl
use strict;
use warnings;
use Dancer;
use Net::SSH2;
sub execCommand ($$) {
my ( $ssh2, $cmd ) = @_;
my %args=(
timeout => 1_000, # polling timeout
bufsize => 10_240, # read buffer size when polling
);
$ssh2->blocking(1); #needed for ssh->channel
my $chan=$ssh2->channel(); # create SSH2 channel
if ($ssh2->error()) {
return (undef, undef, 100);
}
# exec $cmd (caveat: only one command line can be executed over this channel. No "ls -l;whoami" combo. Use ssh->shell instead.
unless ($chan->exec($cmd)) {
return (undef, undef, 500);
}
# defin polling context: will poll stdout (in) and stderr (ext)
my @poll = ( { handle => $chan, events => ['in','ext'] } );
my %std=(); # hash of strings. store stdout/stderr results
$ssh2->blocking( 0 ); # needed for channel->poll
while(!$chan->eof) { # there still something to read from channel
$ssh2->poll( $args{'timeout'}, [ @poll ] ); # if any event, it will be store into $poll;
my( $n, $buf ); # number of bytes read (n) into buffer (buf)
foreach my $poll ( @poll ) { # for each event
foreach my $ev ( qw( in ext ) ) { #for each stdout/stderr
next unless $poll->{revents}{$ev};
#there are something to read here, into $std{$ev} hash
if( $n = $chan->read( $buf, $args{'bufsize'}, $ev eq 'ext' ) ) { #got n byte into buf for stdout ($ev='in') or stderr ($ev='ext')
$std{$ev}.=$buf;
}
} #done foreach
}
}
$chan->wait_closed(); #not really needed but cleaner
my $exit_code=$chan->exit_status();
$chan->close(); #not really needed but cleaner
$ssh2->blocking(1); # set it back for sanity (future calls)
return ($std{'in'},$std{'ext'},$exit_code);
}
sub execute ($$$$) {
my ($ip, $username, $password, $cmd) = @_;
my $pid = fork();
if ($pid) {
# This is the parent (DANCER)
debug "Process started with PID $pid\n";
} elsif ( $pid == 0 ) {
# This is the child
my $ssh2 = Net::SSH2->new();
$ssh2->connect( $ip ) or debug("Cannot connect to $ip");
my $publicKeyFile = './id_rsa.pub'; # path(setting('appdir'), 'db', 'id_rsa.pub'); # I prefer to copy the public key in your app dir due to permissions issues
my $privateKeyFile = './id_rsa'; # path(setting('appdir'), 'db', 'id_rsa'); # I prefer to copy the private key in your app dir due to permissions issues
if ( $ssh2->auth_publickey( $username, $publicKeyFile, $privateKeyFile, $password ) ) {
my ($stdout, $stderr, $exitcode) = execCommand($ssh2, $cmd);
} else {
debug "Could not authenticate to $ip with $username";
}
$ssh2->disconnect();
} else {
debug "Could not fork: $!\n";
}
}
set logger => "console";
set log => "core";
set show_errors => 1;
get '/uptime/:ip' => sub {
my $username = "the username";
my $password = "the password";
execute(param('ip'), $username, $password, "uptime > /tmp/dancer_example.txt");
return 'uptime is running';
};
dance;
true;
Net::SSH2 可以异步使用,但它有很多错误并且经常崩溃。忘记使用它在同一进程上并行运行数千个(或数百个)连接。如果您按照@Johandry 的建议在新进程中使用它可能没问题,但是您可以ssh
使用AnyEvent::Util::run_cmd
.
Net::OpenSSH 是另一个可以异步使用的 Perl 模块。将它集成到 AnyEvent 中应该不会太难。