週末には少しPerlを。

Perlスクリプトの学習日記です。

Net::OpenSSH 0.60 のタイムアウト時の動作を眺める

Net::OpenSSH

Kansai.pm 第15回ミーティングでの @shiba_yu36 さんの Cinnamon の紹介を聞いて、Cinnamonの中で使われているという Net::OpenSSH に興味を持ちました。

リモートのサーバーにSSH接続してコマンドを実行するための モジュールですが、気になるのは異常系の動作です。 たとえば

  • リモートのサーバーが重くて返事が返ってこないとき
  • 通信経路上にトラブルがあったとき

どのように振る舞うか気がかりです。 簡単な実験をしてみました。

タイムアウトを設定してみる

サーバーA からサーバーB に ssh するとして、まずサーバーB に 以下のスクリプトを準備します。

#!/bin/sh
echo 'hoge'
touch hoge
sleep 60
echo 'fuga'
touch fuga

これをサーバーA から実行します。

my $ssh = Net::OpenSSH->new( );
$ssh->error and
    die "Couldn't establish SSH connection: ". $ssh->error;

my $cmd = '~/hogefuga.sh';

my ($out, $err) = $ssh->capture2($cmd);
$ssh->error and die "remote command failed:".$ssh->error;

print "out :\n$out\n";
print "err :\n$err\n";

コマンドを叩いてじっと1分待つと以下の出力が得られます。

[user@servera ~]$ ./ex.pl
out :
hoge
fuga

err :

サーバーB で ls -l すると確かにファイル hoge, fuga が 1分の間をおいて作成されています。

サーバーB のファイル hoge, fuga を削除し、今度は タイムアウトを指定して実行してみます。

my $timeout = 3;

my ($out, $err) = $ssh->capture2(
    {timeout => $timeout},
    $cmd,
);
$ssh->error and die "remote command failed:".$ssh->error;
print "out :\n$out\n";
print "err :\n$err\n";

出力はこうなります。

[user@servera ~]$ ./ex2.pl
remote command failed:ssh slave failed: timed out at ./ex2.pl line **.

ブログにこのように書くと上手くタイムアウトが働いたように見えますが、 実はコマンドを叩いてからエラーメッセージが出るまで1分かかっています。 また、サーバーB には前回同様にファイル hoge, fuga の両方が作成 されており、コマンドは最後まで実行されているようです。

途中でファイアーウォールで通信を遮ってみる

サーバーB の iptables の設定ファイルにサーバーA からの パケットをすべて DROP するルールを書いておきます。 サーバーA で前記のタイムアウト指定なしのスクリプトを 実行してからサーバーB の iptables を起動して 通信を遮断するとどうなるでしょうか。

[user@servera ~]$ ./ex.pl
Timeout, server not responding.
remote command failed:child exited with code 255 at ./ex.pl line **.

今度はたっぷり3分待たされました。 今回もサーバーB のファイル hoge, fuga の両方が作成されています。

kill_ssh_on_timeout

本当にタイムアウトを指定通りに実現するには Net::OpenSSH->new するときに kill_ssh_on_timeout を指定するしか方法がないようです。 これを指定して前記の ex2.pl を実行すると3秒でエラーが表示され、 サーバーB では hoge のみが作成され fuga は作成されなくなります。

ただし perldoc Net::OpenSSH の文書は次の警告をしています。

This action may leave the remote process running, creating a remote orphan
so Net::OpenSSH does not use it unless the construction option kill_ssh_on_timeout is set.

TO DO として次の記載がありますのでタイムアウト時の振る舞いは将来の バージョンで変わっていくかもしれません。

better timeout handling in system and capture methods

おまけ

Net::OpenSSH->new でパスワードも指定するときは IO::Pty をインストールしておくこと。