1つのシェルから複数のSSHセッションを同時に実行するツール3種類を試す

 リモートマシンへのログインやファイルのコピーなど、システム管理作業全般でSSHを利用する機会は多い。SSHでの作業を効率よく進めるには、複数のリモートマシンに対してコマンドを同時に実行できるツールを使ってみるとよいだろう。この記事では、Parallel ssh、Cluster SSH、ClusterItという3つのツールを紹介する。いずれも、単一のターミナルウィンドウで入力したコマンドを、対象となる複数のリモートマシンに対して一斉に送ることができる。

 こうしたツールを使わずに、openSSHで複数ホストに対する処理を行うことも不可能ではない。たとえば、実行するコマンドを記述したファイルを作成し、bashのforループを使って、複数のリモートホストに対して順次実行していけばよい。だが、ツールを使うことにはメリットがある。その1つは、複数のホストで並行してコマンドを実行できる点だ。短時間で完了するタスクならたいした違いはないが、たとえば完了までに1時間を要するタスクを20台のホストに対して実行するとなると、順次実行するより並行処理で進める方がはるかに短時間で済むことになる。あるいは、複数のマシンで同じファイルをインタラクティブに編集するとしたら、並行処理型のSSHツールを使い、すべてのノードを対象としてviでファイルを編集する方が、同じ編集を行うスクリプトをわざわざ作るよりも短時間で済むと考えられる。

 並行処理型のSSHツールの多くには、多数のホストに対してまとめてコピーを行う機能(scpコマンドの並行処理版)や、まとめてrsyncを行う機能がある。また、対象となる全ホストを把握していることから、負荷分散を用いて、いずれかのホストで各コマンドを実行する機能を持つツールもある。さらに、「バリア」機能を持つツールもある。これは、一連のコマンド処理を複数の部分に分けておき、ある部分の処理から次の部分へ移るときには全ノードがそろって進むようにする機能だ。早く処理が完了したノードは、他の全ノードが完了するまでは次の部分へ進まず待機する。

Parallel ssh(pssh)

 Parallel sshプロジェクトは、並行処理版のシェル(pssh)と、scp、rsync、killのそれぞれの並行処理版(pscp、prsync、pnuke)で構成されている。

 psshは、openSUSE用の1クリックインストールパッケージがあるほか、Ubuntu Hardy UniverseおよびFedora 9のリポジトリにも登録されている。私はFedora 9のリポジトリの64ビット版パッケージを利用した。

 Parallel sshのコマンドは「 command -h hosts-file options 」という形式で指定する。hosts-fileは、コマンドを実行する全ホストを記述したファイルだ。たとえば、次の実行例の最初のpsshコマンドでは、ホストp1とp2で、dateコマンドをユーザbenで実行する。オプションの-lは、リモート・マシンへのログインに使用するユーザ名を指定するパラメータだ。

# cat hosts-file
p1
p2

# pssh -h hosts-file -l ben date
[1] 21:12:55 [SUCCESS] p2 22
[2] 21:12:55 [SUCCESS] p1 22

# pssh -h hosts-file -l ben -P date
p2: Thu Oct 16 21:14:02 EST 2008
p2: [1] 21:13:00 [SUCCESS] p2 22
p1: Thu Sep 25 15:44:36 EST 2008
p1: [2] 21:13:00 [SUCCESS] p1 22

 通常は、リモートホストの標準出力の内容は画面に表示されない。最後の例のように-Pオプションを指定すると、リモートホストの出力と終了ステータスの両方が表示される。複雑なコマンドを実行する場合は、代わりに-iオプションを指定するとよい。そうすると各リモートホストからの出力がホスト名ごとにグループ分けして表示される。各ホストからの出力が混在する形でずらずらと表示されるより見やすいはずだ。psshでは、各リモートホストの出力内容を保存するディレクトリパスを--outdirオプションで指定することもできる。出力はホストごとに別々のファイルに保存され、リモートマシンのホスト名がファイル名として付けられる。

 --timeoutオプションでは、コマンドの完了を待機する時間を指定できる。デフォルトは60秒だ。ホスト上でコマンドの実行が60秒以内に終了しなかった場合、エラーとみなされ、その旨が次の例のように表示される。この動作で問題があるようなら、タイムアウト値を十分な長さ(たとえば24時間)に設定すればよい。

# pssh -h hosts-file -l ben -i "sleep 65; date"
[1] 21:19:26 [FAILURE] p1 22 Timeout
[2] 21:19:26 [FAILURE] p2 22 (4, 'Interrupted system call')

 pscpコマンドにも、-h-l--timeoutの各オプションを同様に指定できる。さらに、--recursiveというオプションもある。サブディレクトリを再帰的にコピーするオプションだ。コマンドの末尾には、コピーの対象となるローカルパスとリモートパスを指定する。次の例を見てほしい。最初のpscpコマンドでは、単一のファイルを2つのリモートホストに対して同時にコピーしている。その次のsshコマンドでは、コピーしたファイルがリモートホストp1に存在することを確認している。その次のpscpコマンドはエラーになっている。長いエラーメッセージが表示されているが、これを見てもエラーの理由は不明だ。だがここでは、ディレクトリを上書きコピーしようとしたことが原因と考えられるので、--recursiveオプションを追加してコマンドを再度実行したところ、今度はうまくいった。最後のsshコマンドは、このディレクトリがリモートホストp1に存在することを確認している。

$ mkdir  example-tree
$ date > example-tree/df1.txt
$ date > example-tree/df2.txt
$ mkdir  example-tree/subdir1
$ date > example-tree/subdir1/df3.txt

$ pscp -h hosts-file -l ben example-tree/df1.txt /tmp/df1.txt
[1] 21:28:36 [SUCCESS] p1 22
[2] 21:28:36 [SUCCESS] p2 22

$ ssh p1 "cat /tmp/df1.txt"
Thu Oct 16 21:27:25 EST 2008

$ pscp -h hosts-file -l ben example-tree /tmp/example-tree
...
python: Python/ceval.c:2918: set_exc_info: Assertion `frame != ((void *)0)' failed.
Aborted

$ pscp -h hosts-file -l ben --recursive  example-tree /tmp/example-tree
[1] 21:29:57 [SUCCESS] p1 22
[2] 21:29:57 [SUCCESS] p2 22

$ ssh p1 "ls -l /tmp/example-tree"
total 24
-rw-r--r-- 1 ben ben   29 2008-09-25 16:01 df1.txt
-rw-r--r-- 1 ben ben   29 2008-09-25 16:01 df2.txt
drwxr-xr-x 2 ben ben 4096 2008-09-25 16:01 subdir1

 prsyncコマンドでは、rsyncのコマンドラインオプションの一部しか使用できない。特に、verboseオプションやdry-runオプションで詳細な処理内容を確認できない点は要注意だ。次の例では、example-treeをリモートホストの/tmp/example-treeに対しrsyncで同期している。先ほどのpscpの最後の実行例と似た形式だ。

$ prsync -h hosts-file -l ben -a --recursive  example-tree /tmp

 prsyncコマンドと同様の処理は、psshコマンドと通常のrsyncコマンドを組み合わせるやり方でも可能だが、prsyncコマンドの方が大きなメリットがある。コマンドラインが簡単である点と、ローカルマシンからリモートホストに直接同期できる点だ。psshとrsyncを組み合わせるやり方では、rsyncは各リモートホストで実行することになるので、同期のためには、リモートホストからローカルマシンへの接続が必要になる。

 pslurpは、pscpのちょうど反対のようなコマンドだ。すべてのリモートマシンからファイルやディレクトリを取得し、ローカルマシンにコピーする。次の例では、リモートマシンp1とp2からexample-treeディレクトリを取得し、/tmp/outdirに保存している。-rオプションは--recursiveの短縮形だ。この実行例からわかるように、各リモートホスト名と同じ名前のディレクトリがそれぞれ新規作成され、リモートのexample-treeディレクトリのコピーが、pslurpの最後の引数で指定したローカルディレクトリ名で作成される。

# mkdir /tmp/outdir
# pslurp -h hosts-file -L /tmp/outdir -l ben -r /tmp/example-tree example-tree

# l /tmp/outdir
drwxr-xr-x 3 root root 4.0K 2008-10-16 21:47 p1/
drwxr-xr-x 3 root root 4.0K 2008-10-16 21:47 p2/
# l /tmp/outdir/p2
drwxr-xr-x 3 root root 4.0K 2008-10-16 21:47 example-tree/
# l /tmp/outdir/p2/example-tree/
-rw-r--r-- 1 root root   29 2008-10-16 21:47 df10.txt
-rw-r--r-- 1 root root   29 2008-10-16 21:47 df1.txt
...
drwxr-xr-x 2 root root 4.0K 2008-10-16 21:47 subdir1/

 Parallel sshでは、環境変数を使って処理を簡素化することもできる。PSSH_HOSTSという変数では、-hオプションの代わりにホストファイルを指定でき、PSSH_USERという変数では、psshの-lオプションと同様に、ログインに使用するユーザ名を指定できる。

Cluster SSH

 Cluster SSHは、Parallel sshとは使い方が少々異なる。単一のターミナルから複数のマシンに対してコマンドを実行するのではなく、クラスタのノードの分だけ別々のxtermが立ち上がり、制御用のウィンドウで入力した内容をすべてのノードに送信できるという仕組みだ。

 Cluster SSHは、Ubuntu Hardy UniverseとFedora 9のリポジトリに登録されているほか、openSUSE 11では1クリックインストールで利用できる。私はFedora 9のリポジトリの64ビット版パッケージを使用した。

 コマンドラインで接続先のマシンをいくつか指定すると、その数の分だけxtermウィンドウが開く。あわせて、Tkウィンドウが1つ開き、Cluster SSHの制御や、各xtermウィンドウへの入力の送信を行うことができる。たとえば、「cssh p1 p2」と指定すると、リモートマシンp1とp2のxtermウィンドウと、操作用のウィンドウが開く。各xtermウィンドウは最初は並んで表示される。

 Cluster SSHの[File]メニューでは、それまでの入力内容の履歴を確認できる。Cluster SSHウィンドウが拡張され、入力内容のみを示すテキストエリアが現れる形だ。また、[File]メニューからは、セッションの終了も行える。Cluster SSHのウィンドウを閉じた場合は、セッションも終了され、対応するxtermウィンドウがすべて閉じる。

 [Hosts]メニューではウィンドウの再整列を行うことができ、各xtermウィンドウとメインの制御ウィンドウを起動時と同じ表示状態に戻すことができる。また、[Hosts]メニューには、新たなホストへの接続を確立して現在のセッションに加えたり、現在のセッションのいずれかのホストに対する操作の有効と無効を切り替えたりする機能もある。操作の有効と無効を切り替える機能は、複数のマシンに対して管理作業を行うときに、いずれか1つのコマンド実行で特定のマシンを対象外としたい場合に便利だ。たとえば、クライアントマシンに対するコマンド実行でiptablesのルールを操作する際にのみ、ファイアウォールマシンを一時的に処理対象から除外することができる。

 Cluster SSHには、マシン全体に対して有効な設定ファイルが2つある。/etc/clustersと/etc/csshrcだ。前者はクラスタを構成するマシン(つまり接続先のホストのグループ)を指定するファイル、後者は一般的な設定ファイルだ。また、ユーザごとの設定を~/.csshrcというファイルに指定することもできる。これと対になる~/.clustersというファイルはないが、~/.csshrcファイル内のextra_cluster_fileという設定ディレクティブで、ユーザ固有の/etc/clustersの場所を指定することが可能だ。

 clustersファイルでは、「groupname user1@host1 user2@host2 ...」という形式でグループ名、ユーザ名、ホスト名を指定する。ユーザ名は省略可能だ。そのうえで、コマンドラインで「cssh groupname」と指定すれば、指定したグループのすべてのホストに接続できる。次の例では、ユーザ固有のclustersファイルを使用し、そのファイルを参照するよう~/.csshrcファイルで指定している。最後の行のコマンドでは、指定のグループの各ホストに各ユーザで接続している。

# cat /root/.cssh-clusters
rougenet p1 ben@p2

# cat /root/.csshrc
extra_cluster_file = ~/.cssh-clusters

# cssh rougenet

 ホットキーは4種類が定義されている。csshrcファイルでキーの割り当てを変更することも可能だ。標準では、Ctrl-q(現在のセッションを閉じる)、Ctrl-プラス(+)(現在のセッションに新しいホストを追加する)、Alt-n(メインのCluster SSHウィンドウがアクティブな状態で各xtermにリモートホスト名を貼り付ける)、Alt-r(xtermウィンドウを再整列する)という4種類がある。たとえばAlt-nの場合は、CSSHウィンドウで「echo Alt-n」と入力すると、1つのxtermにはecho vfedora864prx1、もう1つのxtermにはecho vfedora864prx2といった形で、それぞれのホスト名が挿入される。ただし、Cluster SSHを実行しているマシン自身のホスト名を貼り付けるためのホットキーはない。その機能もあれば便利だったろうと思う。実行元のマシンからセッションの全ノードに対してrsyncを行う場合などに使えるからだ。

 リモートホストへのログインに使用するユーザは-lオプションで指定できる。このオプションで指定したユーザ名の方が、clustersファイルで指定したユーザ名より優先順位が高くなる。

 Cluster SSHでは、xterm以外のターミナルを利用することもできる。csshrcファイルのパラメータでターミナルの設定が可能だ。ただあいにく、gnome-terminalやkonsoleをターミナルとして使うのは、単なる「terminal = konsole」といった指定ではうまくいかない。Cluster SSHからターミナルに対し、xtermという前提でオプションが渡されるからだ。私が試したところ、「terminal = Eterm」という指定は、代用ターミナルとしてそのままうまくいった。だが、gnome-terminalはそのままでは使えなかった。ターミナルの起動時にCluster SSHから必ず-fontオプションが渡されるといった問題からだ。そのままの指定でうまくいかないターミナルを利用するには、ラッパーとなるスクリプトを作成し、問題なく受け入れられる引数のみを渡すようにするといった対処が必要だ。

ClusterIt

 3つ目に紹介するアプリケーションはClusterItだ。多数のSSHセッションを単一のコンソールからまとめて制御できるのはもちろん、他の機能も備えている。具体的には、cp、rm、top、dfの並行処理版(名前はそれぞれpcp、prm、dtop、pdf)のほか、クラスタのいずれかのノードでコマンドを実行するための命令(run)、クラスタの複数のノードに対して一連のコマンドを発行する命令(seq)、各ノードでジョブを1つずつ確実に進めていくための命令(jsh)が用意されている。

 ClusterItは、openSUSE、Ubuntu Hardy、Fedora 9のいずれのパッケージもない。私は64ビットのFedora 9マシンで「./configure && make && sudo make install」を実行し、ClusterIt 2.5をソースからビルドした。

 ClusterItでクラスタのすべてのノードに対してターミナルを開くには、dvtコマンドを使用する。Cluster SSHとは違い、開いたウィンドウを並べて表示してくれる機能はない。ウィンドウマネージャがデフォルトできれいに配置してくれない限りは、手動で並べ替える必要がある。また、制御用のウィンドウが備えるオプションもCluster SSHより少ない。クラスタのうちでターミナルを立ち上げるノードを指定するには、-wオプションを使用し、次のようにホスト名をコンマ区切りで指定する。すると、各ターミナルウィンドウと制御用のウィンドウが表示される。制御用のウィンドウ(タイトルにdvtとあるウィンドウ)を閉じても、各ターミナルウィンドウは閉じない。セッションの全ターミナルを閉じるには、制御用ウィンドウの[Quit]ボタンを押す必要がある。

$ dvt -w p1,p2

 ClusterItの大半のコマンドでは、接続先のグループを-gオプションで、ノードを-wオプションで、除外するノードを-xオプションで、それぞれ指定する。

 クラスタのすべてのノードにファイルをコピーするにはpcpコマンドを使用する。デフォルトでは順番にコピーされるが、-cオプションを指定すると並行処理でコピーされる。すべてのノードに一斉に送信できるネットワーク帯域幅がある場合なら、並行処理でコピーする方が短時間で完了する。並行処理の場合は、同時に処理するノード数を-fオプションで指定できる。デフォルトは64だ。つまり、64のノードに対して並行処理でコピーが行われる。

 pcpには、cpと同じオプションもいくつかある。残念ながら、cpでよく使う-aオプション(-cdpRのエイリアス)は使用できない。また、-cオプションはcpと意味が異なるし、-dオプションはない。一方、-pオプションと-Rオプションは、cpと同じように使用できる。

 ClusterItの他のコマンドでも、よく使うオプションが欠けていることがある。たとえば、通常のdfコマンドにある-hオプションが、ClusterItのpdfコマンドにはない。ディスク容量を1Kブロック単位で表示するのは、コンピュータにとっては好都合かもしれないが、人間としては、正確なブロック数がわかることより、たとえば/varに140MBの空き領域があるかどうかがわかる方が重要だと思う。

 次の実行例では、p1とp2の両ホストに対し、まずディレクトリツリーを/tmpに再帰的にコピーしている。最大処理ノード数まで並行処理でコピーを行う場合は、pcpコマンドに-cオプションを指定すればよい。その次のsshコマンドでは、example-tree配下のファイルがホストp1に存在することを確認している。その次のpdfコマンドの出力からは、df -hと比べてわかりにくいことが見てとれる。その後のprmコマンドでは、両方のホストからディレクトリツリーを削除している。

# pcp -pr -w p1,p2 example-tree /tmp/
df10.txt  ... 100%   29     0.0KB/s   00:00
df1.txt   ... 100%   29     0.0KB/s   00:00
...
# ssh p1 cat /tmp/example-tree/df10.txt
Thu Oct 16 21:45:50 EST 2008

# pdf -w p1,p2 /tmp
Node      Filesystem            1K-Blks     Used    Avail  Cap Mounted On
p1      : /dev/mapper/VolGrou  15997880  3958792 11213336  27% /
p2      : /dev/mapper/VolGrou  15997880  3536648 11635480  24% /

# prm -rf -w p1,p2 /tmp/example-tree

 ClusterItの大きな特徴が、ジョブスケジューリングとバリア機能だ。バリア機能とは、複数のノードで動作するスクリプトを作成するときに、バリア(境界)となる部分をスクリプト内に定めておくと、すべてのノードの処理がそのバリアに達した時点で初めて次の部分に処理が進むという同期機能だ。この機能を使えば、処理の後でマージを行うといった典型的なスクリプトを作成するときに、ノード間の連携に苦労せずに済む。並行処理するスクリプトの中でbarrierコマンドを使っておくだけで、すべてのノードが指定の場所に達するのを待機できる。

 jshコマンドでは、クラスタのいずれかのノードでコマンドを実行できる。ただし、各ノードで複数のコマンドが同時に処理されることはないという制限がある。この制限のおかげで、複数のコマンド処理でノードの処理が停滞するという事態を防げる。つまり、ClusterItを使用するスクリプトからクラスタに対し、前のコマンドがまだ途中の段階で次のコマンドがどんどん発行されるという心配がない。たとえば、ClusterItを使ってコードをコンパイルする場合に、他と比べてコンパイルにかなり長い時間を要するソースファイルがいくつかあるとする。インテリジェントなスケジューリング機能や、1ノードに1ジョブのみという制限がなかったら、コンパイルの並行処理の際に、大物のファイルのコンパイルがまだ終わっていない段階で、次に処理するジョブがノードに渡されるということが起こり得る。jshなら、長い処理時間を要するジョブも、次のジョブを渡されることなく進められる。

 jshの使い方は簡単だ。制御元のホスト(たとえばユーザ自身のデスクトップマシン)でデーモンjsdを立ち上げてから、jshを使ってコマンドを発行する。クラスタを構成するノードは、-g-w-xの各オプションで指定できる。jsh自体は特にオプションなしで呼び出せる。次の使用例では、まずデーモンを起動し、jshでコマンドを実行している。その後、jshをバックグラウンドで2回実行している。これで両方のホストにジョブが送られる。最後には、その出力が示されている。

# jsd -w p1,p2
jsd started with pid 24615

# jsh hostname
p2: vfedora864prx2

# jsh "sleep 10; hostname" &

[1] 24648

# jsh "sleep 10; hostname" &

[2] 24650

p2: vfedora864prx2
p1: vfedora864prx1

 -g-w-xの各オプションの代わりに環境変数を使うこともできる。CLUSTER変数では、ホスト名を改行区切りで記述したファイルの名前を指定する。FANOUT変数では、ジョブを並行処理するノードの数を指定する。

まとめ

 本稿で紹介した3つのアプリケーションはそれぞれに長所がある。クラスタに対するコマンドの発行や負荷分散を行いたい場合はClusterItがよいだろう。複数のxtermをグループ化して単一のウィンドウから制御したい場合は、Cluster SSHが一番使いやすいだろう。複数のターミナルを使えるし、特定のノードを一時的に無効にしたり、単一のホストのみを直接利用した後で全体の制御に戻るといったことも可能だ。Parallel sshには、並行処理版のrsyncコマンドがあり、複数のホストを単一のターミナルから制御できる。入力内容が複数のxtermに反映されるようすは表示されないが、さまざまな種類のマシンを集めた環境で、すべてのマシンに対して同じコマンドを実行する機会が多い場合には、単一のインタラクティブターミナルからすべてを操作できて便利だ。ノードごとにxtermウィンドウが個別に開いて画面上が混雑する心配がない。

Ben Martinは10年越しでファイルシステムと取り組んできた。博士課程終了。現在、libferris、ファイルシステム、サーチソリューションを中心にコンサルタント業務を展開している。

Linux.com 原文(2008年10月30日)