本サイトはプロモーションを含みます。

複数サーバーで効率よくコマンドを実行する方法

こんにちは。面倒くさがり屋エンジニアのための”楽をしようよ”コーナーです。

コネクションが張れるか確認するためにtelnetコマンドで接続できるかテストする事はよくあると思います。

たとえばFW(ファイアウォール)で通信許可あるいは通信拒否設定のポリシーを追加した場合ですね。

1台のサーバーからひとつの宛先への通信確認する程度だったら面倒くさがりのわたしでも耐えられるのですが、複数の宛先あるいは複数のサーバーから、もしくはその両方だったら手動作業なんて面倒でやってられません。

そんな時の対処法についての解説です。

for文で回す

複数台のサーバーで実行する場合は基本的にfor文で回します。この方法は各サーバーへssh鍵認証&パスワードなしでログインできる状態でないとパスワード入力が毎回必要になるので面倒です。

# 接続先の登録
Target="
server-1
server-2
server-3
"

# 登録確認
for j in ${Target}; do echo ------- $j -------; done
$ for j in ${Target}; do echo ------- $j -------; done
------- server-1 -------
------- server-2 -------
------- server-3 -------
$

このコマンドを張り付けるとこのように表示されます。

for文で回しながらsshで各サーバー接続しコマンドを実行するには次のようにします。

for j in ${Target}; do
echo ------- $j -------
ssh $j '接続先で実行したいコマンド'
done

コマンドをシングルクォーテーション(‘)で囲むかダブルクォーテーション(“)で囲むかで動作が変わりますので、ご注意を。それについては後述します。

複数台のサーバーからinfrapod.netの443/tcpに接続する

接続確認をするとき、昔はtelnetを利用する方法が一般的でした。*BSDでは今でもそうでしょう。

ところが最近のLinuxはtelnetがデフォルトでインストールされないものが多くなっています。

その場合の対処としてはnc(netcat)が最もお勧めです。netcatを単独でインストールするか、あるいはnmapをインストールすると勝手にnc(ncat)がインストールされます。

telnetで接続テストする

telnetの場合は接続先のホスト名やIPに続けてポート番号を指定します。

$ telnet infrapod.net 443
Trying 183.181.84.86...
Connected to infrapod.net.
Escape character is '^]'.

問題は接続できた場合、上記の例のようにCtrl-]を入力するまで待機状態になるのでループで回す事ができません。そのため次のようにechoコマンドを利用する必要があります。

$ echo | telnet infrapod.net 443
Trying 183.181.84.86...
Connected to infrapod.net.
Escape character is '^]'.
Connection closed by foreign host.
$

echoをパイプで渡してやればこのようにコネクションを閉じる事ができます。for文で回す場合は次のようにします。

# 接続先の登録
Target="
server-1
server-2
server-3
"

# 登録確認
for j in ${Target}; do echo ------- $j -------; done

# telnet実行
for j in ${Target}; do
  ssh $j 'echo | telnet infrapod.net 443'
done

telnetの問題点は接続できない場合にタイムアウトするまで時間がかかる事です。

nc(netcat)で接続テストする

ncの良いところはタイムアウトを設定できる点です。

ncはオリジナル版、GNU版、 Nmap版があって微妙に使えるオプションが異なるので注意が必要です。ですから、どの版のncでも動作するようにしなければなりません。

Nmap版のncには「–send-only」オプションがあるのですが、その他の版にはありませんからtelnetと同様にechoをパイプで渡してやる必要があります。また「-v」オプションを付けないと接続できたのか分かりません。

# 接続先の登録
Target="
server-1
server-2
server-3
"

# 登録確認
for j in ${Target}; do
echo ------- $j -------
done

# nc実行
for j in ${Target}; do
  ssh $j 'echo | nc -q 1 -w 3 -v infrapod.net 443'
done

「-w」オプションでタイムアウトまでの時間を設定し「-q」オプションで標準入力を送信してから終了するまでの時間を設定しています。

$ echo | nc -q 1 -w 3 -v infrapod.net 443
infrapod.net [183.181.84.86] 443 (https) open
$

接続に成功した場合は「open」と表示されます。

$ echo | nc -q 1 -w 3 -v infrapod.net 443
infrapod.net [183.181.84.86] 443 (https) : Connection refused
$

ポートが閉じていて接続に失敗した場合は「Connection refused」と表示されます。

$ echo | nc -q 1 -w 3 -v infrapod.net 443
infrapod.net [183.181.84.86] 443 (https) : Connection timed out
$

パケットがドロップしたりルーティングに問題があるなど応答がなくてタイムアウトした場合は「Connection timed out」と表示されます。

「’」と「”」での動作の違い

「’」と「”」の使い分けをしないと想定通りに動きませんから注意が必要です。

たとえば次のようなコマンドを実行する場合を考えます。

ssh infrapod.net "echo $(hostname)"

この場合、hostnameコマンドはどこで実行されるか?

答えはsshコマンドを実行しているマシンです。infrapod.netでhostnameコマンドを実行させるためには「”」ではなくて「’」を使う必要があります。

ssh infrapod.net 'echo $(hostname)'

これでinfrapod.netでhostnameコマンドを実行した結果が表示されます。

ssh infrapod.net 'hostname' > /tmp/hostname.out

リダイレクトも注意が必要です。上記のようなコマンドを実行したとき、リダイレクトされたファイルはどこに保存されるでしょう?

答えはsshコマンドを実行しているマシンの/tmp/hostname.outに保存されます。infrapod.netの/tmp以下に保存するには次のようにします。

ssh infrapod.net 'hostname > /tmp/hostname.out'

コマンドは基本的に「’」で囲むと間違いないでしょう。あとは囲む範囲ですね。特にリダイレクトやパイプを使う場合は囲む範囲で実行結果が変わりますから注意が必要です。

まとめ

複数のサーバーで並列的にコマンドを実行する場合の最適解は「pdsh」だと思いますが、通信テストのような事をする場合は今回のような方法の方が向いているのではないでしょうか。

pdshについては別の機会にご紹介します。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)