Запуск AnyEvent под приложением Dancer

Я хотел бы сделать несколько неблокирующих SSH для пары тысяч машин, которые я отслеживаю (мои собственные машины), у меня запущено и запущено приложение Dancer, и я хочу использовать AnyEvent::timer для выполнения SSH команды асинхронны (каждая машина имеет свой собственный интервал опроса, и я не хочу, чтобы одна машина ожидала, пока другая завершит свою работу SSH).

Мне интересно, как лучше всего действовать асинхронно в синхронной среде?

3 ответа

Я не мог быть хорошей идеей, но это возможно. У меня есть большое приложение 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;

Не очень хорошая идея запускать какие-либо внешние команды из ваших веб-скриптов. Во-первых, если ваш внешний вызов заблокирован или по какой-либо причине завершится сбоем, это создаст плохой опыт для пользователя (даже если этот пользователь - только вы). Затем выполнение внешних команд в качестве веб-пользователя может иметь много последствий для безопасности - я думаю, что ваш веб-пользователь, скорее всего, настроил ssh без пароля, не так ли? Что если кто-то обнаружит какую-то дыру в безопасности в вашем скрипте и сможет использовать ее для ssh на ваших серверах?

Вместо этого вам следует создать отдельный сервис или процесс, который будет регулярно опрашивать состояние ваших серверов, используя ssh (или что-то еще), и сохранять результаты этого сканирования в базе данных - Postgres или MySQL.

Затем измените приложение Dancer, чтобы отображать собранные результаты из базы данных, а не выполнять живой ssh-запрос. Таким образом, это будет очень быстро и безопасно.

Net::SSH2 может использоваться асинхронно, но он довольно глючный и часто дает сбой. Забудьте об использовании его для параллельного запуска тысяч (или просто сотен) соединений в одном и том же процессе. Это может быть хорошо, если вы используете его в новых процессах, как рекомендует @Johandry, но тогда вы можете просто запустить ssh использование команды AnyEvent::Util::run_cmd,

Net::OpenSSH - это еще один модуль Perl, который можно использовать асинхронно. Не должно быть слишком сложно интегрировать его в AnyEvent.

Другие вопросы по тегам