Parallel Distributed Shell(pdsh)を使って複数ホストでコマンドを同時実行する

「複数のリモートホストで同じコマンドを一斉実行したい」と思うことはよくありますが、これらのツールを使うことで実現出来ます。

この中でも Parallel Distributed Shell(以下、pdsh)は学習コストが低く、簡単に使い始めることが出来ます。今回はこの pdsh を使ってみます。前提条件として、ssh で公開鍵認証方式を利用するにはなどを参考に、操作元となるホストから操作対象となる複数のリモートホストssh の公開鍵認証方式でログイン出来るよう、予め設定しておきます。

インストール

RPMforge で pdsh のパッケージが配布されていたので、今回はそちらを利用してインストールしました。

$ sudo yum -y install pdsh

"-V" オプションでバージョン情報を表示させることが出来ます。

$ pdsh -V
pdsh-2.23 (+readline)
rcmd modules: xcpu,ssh,exec (default: ssh)
misc modules: machines,dshgroup (*conflicting: nodeattr,netgroup,genders)
[* To force-load a conflicting module, use the -M <name> option]

グループを指定して実行する

pdsh では予めグループを定義しておき、そのグループに対して同じコマンドを実行することが出来ます。pdsh で指定したグループ名は以下の順序で検索されるようです(ホームディレクトリ配下を優先するようです)。

  1. ~/.dsh/group/
  2. /etc/dsh/group/

グループの定義ファイルを保存するディレクトリを作成します。ここにグループを定義したファイルを保存することになります。

$ mkdir -p ~/.dsh/group

今回は "TEST-HOSTS" というファイルを以下の内容で作成しました。これは "TEST-HOSTS" というグループを定義し、そのグループには "192.168.1.201, 202, 203 を含める、という意味になります。

$ cat ~/.dsh/group/TEST-HOSTS 
192.168.1.201
192.168.1.202
192.168.1.203

pdsh に "-g グループ名" オプションを付与し、グループを指定した上で、実行したいコマンドを記載します。実行例は以下の通りです。

$ pdsh -g TEST-HOSTS uname -a
192.168.1.202: Linux centos-02.local 2.6.18-194.26.1.el5.centos.plus #1 SMP Wed Nov 10 12:04:55 EST 2010 x86_64 x86_64 x86_64 GNU/Linux
192.168.1.203: Linux centos-03.local 2.6.18-194.26.1.el5 #1 SMP Tue Nov 9 12:54:20 EST 2010 x86_64 x86_64 x86_64 GNU/Linux
192.168.1.201: Linux centos-01 2.6.36.1 #2 SMP Tue Nov 30 00:12:54 JST 2010 x86_64 x86_64 x86_64 GNU/Linux

ホストを指定して実行する

"-w" オプションに続けてアドレス(もしくは、名前解決可能なホスト名)を指定することで、そのホストに対してコマンドを実行することが出来ます。正規表現を用いることが可能で、以下のように指定することで 192.168.1.201, 202, 203 の三台に対して同時にコマンドを実行することが出来ます。

$ pdsh -w 192.168.1.20[1-3] uptime
192.168.1.201:  11:26:36 up 14:50,  3 users,  load average: 0.05, 0.02, 0.00
192.168.1.203:  20:26:37 up 14:49,  0 users,  load average: 0.00, 0.00, 0.00
192.168.1.202:  20:26:49 up 14:49,  0 users,  load average: 0.21, 0.06, 0.01

dshbak で出力結果を整形する

pdsh の実行結果を dshbak にパイプすると結果を整形することが出来ます。dshbak を利用しない場合、pdsh の実行結果は以下のようになります。各行の先頭に対象の IP アドレスが記載されている為、あまり見やすいとは言えません。

$ pdsh -g TEST-HOSTS cat /proc/cpuinfo
192.168.1.203: processor	: 0
192.168.1.203: vendor_id	: GenuineIntel
192.168.1.203: cpu family	: 6
192.168.1.203: model		: 23
192.168.1.203: model name	: Intel(R) Core(TM)2 Duo CPU     P8800  @ 2.66GHz
192.168.1.203: stepping	: 10
192.168.1.203: cpu MHz		: 2654.594
192.168.1.203: cache size	: 3072 KB
192.168.1.203: fpu		: yes
192.168.1.203: fpu_exception	: yes
192.168.1.203: cpuid level	: 13
192.168.1.203: wp		: yes
192.168.1.203: flags		: fpu ... sse4_1 lahf_lm
192.168.1.203: bogomips	: 5309.18
192.168.1.203: clflush size	: 64
192.168.1.203: cache_alignment	: 64
192.168.1.203: address sizes	: 40 bits physical, 48 bits virtual
192.168.1.203: power management:
192.168.1.203: 
192.168.1.202: processor	: 0
192.168.1.202: vendor_id	: GenuineIntel
192.168.1.202: cpu family	: 6
192.168.1.202: model		: 23
192.168.1.202: model name	: Intel(R) Core(TM)2 Duo CPU     P8800  @ 2.66GHz
192.168.1.202: stepping	: 10
192.168.1.202: cpu MHz		: 2654.594
192.168.1.202: cache size	: 3072 KB
192.168.1.202: fpu		: yes
192.168.1.202: fpu_exception	: yes
192.168.1.202: cpuid level	: 13
192.168.1.202: wp		: yes
192.168.1.202: flags		: fpu ... sse4_1 lahf_lm
192.168.1.202: bogomips	: 5309.18
192.168.1.202: clflush size	: 64
192.168.1.202: cache_alignment	: 64
192.168.1.202: address sizes	: 40 bits physical, 48 bits virtual
192.168.1.202: power management:
192.168.1.202: 
192.168.1.201: processor	: 0
192.168.1.201: vendor_id	: GenuineIntel
192.168.1.201: cpu family	: 6
192.168.1.201: model		: 23
192.168.1.201: model name	: Intel(R) Core(TM)2 Duo CPU     P8800  @ 2.66GHz
192.168.1.201: stepping	: 10
192.168.1.201: cpu MHz		: 2654.594
192.168.1.201: cache size	: 3072 KB
192.168.1.201: fpu		: yes
192.168.1.201: fpu_exception	: yes
192.168.1.201: cpuid level	: 13
192.168.1.201: wp		: yes
192.168.1.201: flags		: fpu ... hypervisor lahf_lm dts
192.168.1.201: bogomips	: 5309.18
192.168.1.201: clflush size	: 64
192.168.1.201: cache_alignment	: 64
192.168.1.201: address sizes	: 40 bits physical, 48 bits virtual
192.168.1.201: power management:
192.168.1.201:

pdsh の実行結果を dshbak へパイプすると、各ノードの出力結果ごとに見やすくまとめてくれます。

$ pdsh -g TEST-HOSTS cat /proc/cpuinfo | dshbak
----------------
192.168.1.201
----------------
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 23
model name	: Intel(R) Core(TM)2 Duo CPU     P8800  @ 2.66GHz
stepping	: 10
cpu MHz		: 2654.594
cache size	: 3072 KB
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu ... hypervisor lahf_lm dts
bogomips	: 5309.18
clflush size	: 64
cache_alignment	: 64
address sizes	: 40 bits physical, 48 bits virtual
power management:

----------------
192.168.1.202
----------------
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 23
model name	: Intel(R) Core(TM)2 Duo CPU     P8800  @ 2.66GHz
stepping	: 10
cpu MHz		: 2654.594
cache size	: 3072 KB
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu ... sse4_1 lahf_lm
bogomips	: 5309.18
clflush size	: 64
cache_alignment	: 64
address sizes	: 40 bits physical, 48 bits virtual
power management:

----------------
192.168.1.203
----------------
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 23
model name	: Intel(R) Core(TM)2 Duo CPU     P8800  @ 2.66GHz
stepping	: 10
cpu MHz		: 2654.594
cache size	: 3072 KB
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu ... sse4_1 lahf_lm
bogomips	: 5309.18
clflush size	: 64
cache_alignment	: 64
address sizes	: 40 bits physical, 48 bits virtual
power management:

更に、dshbak に "-c" オプションを指定すると、出力結果が重複する(=全く同じ)部分は二重に表示せず、まとめてくれるようになるので出力結果はかなりスッキリします。

$ pdsh -g TEST-HOSTS cat /proc/cpuinfo | dshbak -c
----------------
192.168.1.201
----------------
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 23
model name	: Intel(R) Core(TM)2 Duo CPU     P8800  @ 2.66GHz
stepping	: 10
cpu MHz		: 2654.594
cache size	: 3072 KB
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu ... hypervisor lahf_lm dts
bogomips	: 5309.18
clflush size	: 64
cache_alignment	: 64
address sizes	: 40 bits physical, 48 bits virtual
power management:

----------------
192.168.1.[202-203]
----------------
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 23
model name	: Intel(R) Core(TM)2 Duo CPU     P8800  @ 2.66GHz
stepping	: 10
cpu MHz		: 2654.594
cache size	: 3072 KB
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu ... sse4_1 lahf_lm
bogomips	: 5309.18
clflush size	: 64
cache_alignment	: 64
address sizes	: 40 bits physical, 48 bits virtual
power management:

pdsh から sudo し、管理者権限でコマンドを実行する

pdsh からリモートホストで "sudo" してからコマンドを実行しようとすると、以下のようなエラーになります。

$ pdsh -w 192.168.1.203 'sudo uptime'
192.168.1.203: sudo: sorry, you must have a tty to run sudo
pdsh@centos-01: 192.168.1.203: ssh exited with exit code 1

これは pdsh ではなく、ssh に原因があります。

$ ssh admin@192.168.1.203 sudo uptime
sudo: sorry, you must have a tty to run sudo

例えば「ssh admin@192.168.1.203」のように ssh で対話的にログインすれば TTY が割り当てられますが、「ssh admin@192.168.1.203 uptime」のように、ワンライナーでコマンドを実行させる(この場合は 192.168.1.203 へ admin ユーザでログインし、"uptime" コマンドを実行させる)と ssh が TTY を割り当てません。この「TTY を割り当てない」実装の理由は推測の域を出ませんが、おそらくワンライナーの実行は一瞬で終わるにもかかわらず、都度、TTY を割り当てていてはリソースが無駄だから TTY を割当てない・・・ような気がします。ここまでを整理すると、以下のようになります。

  1. ssh は、対話ログイン時は TTY を割り当てる
  2. ssh は、ワンライナーでの実行時は TTY を割り当てない

話は変わって、sudo はデフォルトでは TTY を割り当てられていないスクリプトなどから実行された場合は sudo を許可しない、という動作をします。これを上記と組み合わせると、このように整理できます。

  1. ssh は、対話ログイン時は TTY を割り当てる → sudo 出来る
  2. ssh は、ワンライナーでの実行時は TTY を割り当てない → sudo 出来ない

ssh は "-t" オプションを指定することで強制的に TTY を割り当てることが出来ますので、"-t" オプションを使えば「リモートホストで sudo 出来ない」問題は解決出来ます。

$ ssh -t admin@192.168.1.203 sudo uptime
 20:58:53 up 15:21,  1 user,  load average: 0.32, 0.07, 0.02
Connection to 192.168.1.203 closed.

pdsh は ssh の挙動を以下の環境変数で制御することが出来ます。

PDSH_SSH_ARGS
pdsh から ssh する際の、デフォルトのオプションを指定する。何も指定しなければ "-a -2 -x -lユーザ名 ホスト名" を指定したことになる
PDSH_SSH_ARGS_APPEND
上記へ追加したい ssh のオプションを指定する

これら環境変数の処理は pdsh-2.22 の場合、"src/modules/sshcmd.c" で以下のように実装されています。

263 static int sshcmd_args_init (void)
264 {
265     char *val = NULL;
266     char *str = NULL;
267     List args_list = NULL;
268 
269     if (!(val = getenv ("PDSH_SSH_ARGS")))
270         val = "-2 -a -x -l%u %h";
271 
272     str = Strdup (val);
273     args_list = list_split (" ", str);
274     Free ((void **) &str);
275 
276     if ((val = getenv ("PDSH_SSH_ARGS_APPEND"))) {
277         str = Strdup (val);
278         args_list = list_split_append (args_list, " ", str);
279         Free ((void **) &str);
280     }
281 
282     ssh_args_list = args_list;
283 
284     return (0);
285 }

しかし、PDSH_SSH_ARGS_APPEND 環境変数を "-t" と定義しても、やはり pdsh から sudo 出来ません・・・仕方がありませんので、リモートホスト側の /etc/sudoers 中、以下のように定義されている箇所をコメントアウトし、TTY が無くても sudo 出来るよう、sudo 側を変更します。この変更を行うにあたってはセキュリティリスクを十分に理解してから設定変更を行う必要があります。

Defaults    requiretty

上記箇所を以下のようにコメントアウトします。

#Defaults    requiretty

これで pdsh から sudo 出来るようになりました。

$ pdsh -w 192.168.1.20[1-3] sudo uptime
192.168.1.201:  12:30:07 up 15:53,  2 users,  load average: 0.00, 0.00, 0.00
192.168.1.203:  21:30:07 up 15:53,  0 users,  load average: 0.00, 0.00, 0.00
192.168.1.202:  21:30:20 up 15:53,  0 users,  load average: 0.00, 0.00, 0.00

リモートサイトとファイルのやりとりをする

pdsh をインストールすると "pdcp" と "rpdcp" というコマンドがインストールされますが、これらを使うと複数のリモートホストへファイルをコピーしたり、逆にリモートホストからファイルをコピーしたり出来ます。リモートホストへファイルをコピーするには "pdcp" を使います。

$ pdcp -w 192.168.1.20[1-3] TEST ~

リモートホストからファイルをコピーするには "rpdcp" を使います。

$ rpdcp -w 192.168.1.20[1-3] TEST ./
$ ls -al TEST*
-rw-rw-r-- 1 admin admin 0 Dec 19 13:07 TEST.192.168.1.201
-rw-rw-r-- 1 admin admin 0 Dec 19 13:07 TEST.192.168.1.202
-rw-rw-r-- 1 admin admin 0 Dec 19 13:07 TEST.192.168.1.203

ファイル名にはサフィックスにホスト名が付与されます。