设置:
我有一个 PHP 脚本(当前为 PHP5.5 编写,但运行它的服务器有 7.4),该脚本处理包含 Linux 服务器列表的文件,然后通过 ssh 在该远程服务器上按以下方式运行 bash 或 perl 脚本:
exec("ssh -o StrictHostKeyChecking=no -p $connectivity_port $user@$server \"bash -s\" -- < $file $scriptargs 2>&1", $result, $exit_code);
这一切都工作得很好,但需要一段时间,具体取决于正在运行的代码。由于在本地,除了脚本的输出之外,几乎没有任何需要处理的内容(有大量日志记录和一些脚本输出到运行 PHP 的服务器的本地文件)。
目标
我想知道最好/最简单的方法/工具是什么,从 bash 运行,并行运行 PHP 脚本,确保所有输出都按照列表中服务器的顺序排列(例如,一次 x 个服务器,也许10、降低执行时间)
从我的研究和版本限制来看,PHP 本身似乎不是可行的方法,bash 似乎也不符合要求,但我愿意接受错误,并愿意学习其他方法。
答案1
在bash
你会做这样的事情:
declare -r MAX_PARALLEL='5' WAITSEC='0.1'
i=0
server[i]=...
port[i]=...
user[i]=...
command_file[i]=...
scriptargs[i]=...
((i++))
server[i]=...
port[i]=...
user[i]=...
command_file[i]=...
scriptargs[i]=...
((i++))
count=$i
for((i=0;i<count;i++)); do
while [ $(jobs -r | wc -l) -gt "$MAX_PARALLEL" ]; do
sleep "$WAITSEC"
done
( ssh -o StrictHostKeyChecking=no -p "${port[i]}" "${user[i]}@${server[i]}" \
"bash -s" <"${file[i]}" "${scriptargs[i]}" >output_file.$i 2>&1
echo $? >exit_code.$i ) &
done
不幸的是,似乎没有一种简单的方法可以获取正确的作业数量,因此只有在没有命令行包含换行符的情况下才能正确工作。
答案2
我在 Synology DS218 上运行了类似的东西。
就我而言,PHP 脚本使用各种命令准备 bash 脚本,然后执行该脚本。
这可以这样工作,因为在我的案件
- 所有服务器都是分开的(我不会让任何服务器超载)
- 服务器 12 中的错误并不意味着停止并跳过 12 之后的服务器
如果这些要求没有得到满足,我就必须采取不同的做法。
但只要他们是,
#!/bin/bash
ssh server1 "command1" > output1 2> error1 &
ssh server2 "command2" > output2 2> error2 &
...
ssh serverN "commandN" > outputN 2> errorN &
# wait for all SSHs to complete
wait
最后,所有输出文件都按数字顺序收获并删除。
答案3
你可以使用 Perl并行::ForkManager和IPC::打开2。
用法:
cat list_of_servers.txt | perl para.pl /path/to/script.sh ARG1 ARG2
代码para.pl
:
#!/usr/bin/env perl
use v5.20;
use IPC::Open2 qw(open2);
use Parallel::ForkManager qw();
sub run_script_on_server {
my ( $server, $script, @args ) = @_;
say "$$ running script: $script on server: $server with args: @args";
# TODO: replace with ssh invocation
my $pid = open2( my $chld_out, my $chld_in, "bash", $script, @args );
local $/ = undef;
return <$chld_out>;
}
my $pm = Parallel::ForkManager->new(10);
while ( my $server = <STDIN> ) {
$pm->start and next;
chomp $server;
my $result = run_script_on_server( $server, @ARGV );
say "$$ result from $server: $result";
$pm->finish;
}
答案4
我可以提供两种方法来做到这一点。
参数
假设您有一个文件,其中包含由换行符分隔的主机名列表,并且user
对于port
所有连接,您可以使用xargs
.
xargs -I '{}' -P <max-procs> --arg-file <INPUTFILE> bash -c "ssh -o StrictHostKeyChecking=no -p $connectivity_port $user@{} 'bash -s' < $file $scriptargs > $OUT_FOLDER/{}.log 2>&1"
or
cat <INPUTFILE> | xargs -I '{}' -P <max-procs> bash -c "ssh -o StrictHostKeyChecking=no -p $connectivity_port $user@{} 'bash -s' < $file $scriptargs > $OUT_FOLDER/{}.log 2>&1"
您可以使用该标志设置并发-P
。
--max-procs=max-procs
-P max-procs
Run up to max-procs processes at a time; the default is 1. If
max-procs is 0, xargs will run as many processes as possible at
a time. Use the -n option with -P; otherwise chances are that
only one exec will be done.
它将把每个命令的输出写入$OUT_FOLDER/$HOST.log
.
如果您有不同的user
并且port
对于每台机器您仍然可以使用xargs
,但这会更复杂一些。
PDSH
另一种选择是使用pdsh
它可以“并行地向主机组发出命令”。
pdsh -R exec -w^<INPUT FILE> -f <max-procs> bash -c "ssh -o StrictHostKeyChecking=no -p $connectivity_port %u@%h 'bash -s' < $file $scriptargs 2>&1"
这里和xargs中的flag-f
类似。-P
exec Executes an arbitrary command for each target host. The first of the pdsh remote arguments is the local command
to execute, followed by any further arguments. Some simple parameters are substitued on the command line,
including %h for the target hostname, %u for the remote username, and %n for the remote rank [0-n] (To get a
literal % use %%). For example, the following would duplicate using the ssh module to run hostname(1) across
the hosts foo[0-10]:
pdsh -R exec -w foo[0-10] ssh -x -l %u %h hostname
and this command line would run grep(1) in parallel across the files console.foo[0-10]:
pdsh -R exec -w foo[0-10] grep BUG console.%h
-f number
Set the maximum number of simultaneous remote commands to number. The default is 32.
如果将转储前缀为的命令的输出HOSTNAME:
这是一个例子。
$ pdsh -R exec -w host1,host2 bash -c "ssh -o StrictHostKeyChecking=no -p 22 %u@%h 'bash -s' <<< 'echo Running script on %h with arguments: \${@}' arg1 arg2 arg3"
host1: Running script on host1 with arguments: arg1 arg2 arg3
host2: Running script on host2 with arguments: arg1 arg2 arg3