用于网络集群上重复命令的 SSH Bash 脚本

用于网络集群上重复命令的 SSH Bash 脚本

我有一个大约 100 台机器的网络集群、一个软件和一份该软件的大量不同参数列表。

我想使用集群来加速计算,因此每台机器都应该使用列表中不同的一组参数来运行软件。当一台机器完成计算时,它应该接收下一组参数并再次运行软件,直到所有参数都已使用。

有没有办法通过 ssh 在 bash 脚本中执行此操作?我想 pssh 是可行的方法,但我不知道该怎么做。

答案1

我编写了一个 bash 脚本,它执行类似的事情,耗尽本地 CPU 核心。当核心释放时,它会调用新的计算,直到计算完成。我也有一些使用 ssh 编写 bash 脚本的经验(如果您愿意承担这种安全风险,则需要无密码的 ssh 密钥)。这是一个脱离上下文的个人示例,但其理念是它是一个根据处理时间动态循环并改变参数的 bash 脚本。在您的情况下,$CORES 变量需要填充可用的服务器,我们需要找到一种方法来跟踪它们以知道接下来要调用哪个。

    Loop () {
      # looping function over all runs with the same header, multi-threaded per core.
      CMDINIT="$CMD"
      for i in "$TREEIN"/"$NAME"*.root  # loop over all the existing raw runs of that name
      do
        # name of the output file and path, eg Tree/30s_production/30s_production-1001.root
        OUTPUT=`echo $i | sed "s#$TREEIN/##" |sed "s#$NAME#$TREEOUT/$NAME/$NAME-#"  `
        INFILE=`echo $i | sed 's$.*/$$'` # name of the input file name, eg 30s_production1001.root
        if [ ! -e  $OUTPUT ];then # only run if the output file does not exist (won't overwrite existing data)
          if [ ! $Cflag ];then # only call the program if we aren't cleaning 
            echo "Outputting to $OUTPUT..."
            RUNNO=`echo $INFILE | sed "s/$NAME//" | sed 's/\..*//'` # get the run number
            # there is a way to do this using the Run function?  Seems trickier with backgrounding, getting PID, and so on
            CMD="$CMDINIT -R $RUNNO"
            printf "Executing run with the command:\n\t$CMD\n"
            $CMD & PIDS="$PIDS $!" # call run on the run number in background w/o renice
            #$CMD & PIDS="$PIDS $!" && sudo renice -n 0 -p $! # call run on the run number in background, renice to -10
            while [ `jobs | wc -l` -eq $CORES ] # only run one run command per core
            do
                    jobs > /dev/null # without this the while loop doesn't seem to refresh?
                    sleep 1 # keep waiting until run is not running on a core
            done
          fi
        else # the output file exists -- should never happen as we check NeedClean first, but anyway, safer
          echo "$OUTPUT exists, please run clean!"
          exit 1
        fi
      done
    }

这里有两个“聪明”的部分(或者如果你愿意的话,也可以叫它“黑客”)。一个是 while 循环,它检查数量jobs并等待直到有空闲。(我现在的 do 循环基于一个参数,但这很容易调整。)这些jobs是 bash 脚本中的参数,这就是它如何实现在作业完成的条件下再次循环的概念;请记住,无论作业是本地的还是远程的,这都是相同的:调用 SSH 命令的命令将与本地作业相同(尽管我们可能需要稍后从所有服务器收集所有结果,或者让服务器根据需要在本地写回数据等)。对我来说另一个关键方面是,当调用 $CMD 时,它还会将进程号添加到名为 $PIDS 的计数器上。如果您决定提前终止,这允许本地 bash 脚本上的 control_c 陷阱能够杀死所有子进程,其中包括您所有 100 台服务器上生成的进程;不跟踪这一点的后果是您可以想象的最可怕的!

如果您想检查主要脚本,它在这里:https://github.com/goatface/crabat/blob/master/crabat

我们需要修改变量 $CMD 的定义,使其类似于

CMD=ssh '$USER@$HOST' /path/to/executable

此后,我们应该动态地将标志添加到可执行文件中以控制不同的参数(我们也可以通过 scp 将这些参数以文本文件的形式推送到每个服务器,但最终我们还是需要跟踪它们,而且对我来说,差异并不大)。我的情况是将大多数参数设置一次,但没有理由我们不能在每次打开服务器时调用它。它看起来像这样,我使用标志,但这对于设置带有参数的文本文件集来说很简单。按顺序为每个字段在 awk 字段上增加一个计数变量,直到用尽为止,等等,并在每次下一个计数变量增加时在循环函数中重置它,按顺序通过所有参数排列。

    SetFlags () {
      # base command
      CMD="./run"
      # add options based on final flags
      [ $Rflag ] && CMD="$CMD -R $Rval"
      [ $Bflag ] && CMD="$CMD -B"
      [ $Hflag ] && CMD="$CMD -H $Hval"
      [ $Iflag ] && CMD="$CMD -I $Ival"
      [ $rflag ] && CMD="$CMD -r"
      [ $dflag ] && CMD="$CMD -d"
      [ $sflag ] && CMD="$CMD -s"
      [ $xflag ] && CMD="$CMD -x"
      [ $pflag ] && CMD="$CMD -p"
      [ $tflag ] && CMD="$CMD -t"
    }

我已启用回复后接收电子邮件的功能,因为我觉得这是一个有趣的问题,但我需要更多时间来考虑在主机释放时跟踪它们。抱歉,我还没有回答整个问题!

相关内容