App::Monitor::Simpleというモジュール書いた

https://github.com/toritori0318/p5-App-Monitor-Simple


シンプルに「リトライ数だけ指定して全部エラーなら何かする」
ということがしたくて探してみたけど見つからず。
コードボリュームからしてもわざわざモジュールにする程でもないかもしれませんが、
あったらあったでまあまあ嬉しいかも、と思い書いてみました。

仕様

やることは至極単純で
「コマンド実行した戻り値が0 or 0以外でOKかNGを判定するだけ」
です。
オプションとしてリトライ数とリトライ間隔を指定することが出来ます。

コマンドラインツール

monitor-simpleというのがついてきます。
詳しくは--help参照ですが、例を説明してみます。

httpサーバの監視
monitor-simple 'curl http://hogehoge.co.jp/'

curl が正常終了するかどうかを判定しています。

pingの監視とオプション
monitor-simple -r 5 -i 10 -q 'ping -c 1 fugafuga.co.jp'

ping のエラー判定を行なっています。
オプションでリトライ回数5、インターバル10秒に設定しています。

応用編

monitor-simple 'curl http://hogehoge.co.jp/' && '正常時の処理'
monitor-simple 'curl http://hogehoge.co.jp/' || 'エラー時の処理'

このようにするとcronなどで使えるのではないでしょうか。
またコマンドの部分をシェルやLLで書いてももちろんOKで、
むしろそういった応用を利かす使い方が多いかもしれません。

Perlモジュールとして使う

もちろんPerlからも使えます。
以下はSYNOPSISのコピペ。なんとなくわかるはず。

my $ret = run(
    {
        command => 'ping -c 1 blahhhhhhhhhhhhhhhh.jp',
        interval => 10,
        retry => 5,
        quiet => 1,
    }
);

example

いくつか例を書いてみます。*1

複数サーバのステータスを独自のデータストアに保存したい。

例えばですが、
「HTTP監視をした結果、それが成功しているサーバのリストを保持しておきたい」
という要望があったとしましょう。
以下のようなコードをcronで回すとよいです。

use strict;
use warnings;
use Data::Dumper;
use Parallel::ForkManager;
use Redis;
use App::Monitor::Simple qw/run/;

my $redis = Redis->new(server => 'localhost:6379');

my @hosts = ('xxx.xxx.xxx.xxx', 'yyy.yyy.yyy.yyy');

my $workers = scalar @hosts;
$workers = 10 if $workers > 10;

my $pm = Parallel::ForkManager->new($workers);
do {
    print "parallel: $workers process\n";
    $pm->run_on_start(
        sub {
            my ($pid, $ident) = @_;
            printf "[%s] start\n", $ident;
        }
    );
    $pm->run_on_finish(
        sub {
            my ($pid, $exit, $ident) = @_;
            printf "[%s] completes.\n", $ident;
        }
    );
};

# run
foreach my $host (@hosts) {
    $pm->start($host) and next;

    my $cmd = sprintf('curl --max-time 3 http://%s/', $host);

    my $status = run(
        {
            command     => $cmd,
            retry       => 3,
            interval    => 5,
            quiet       => 1,
        }
    );
    if($status == 0) {
        warn "success!";
        $redis->set($host, 1);
    } else {
        warn "error!";
        $redis->set($host, 0);
    }
    $pm->finish;
}

# wait...
$pm->wait_all_children;
Redisをフェイルオーバーしたい

Redisでは自動フェイルオーバーの機能は提供されていない…ですよね?
AWS上にマスター/スレーブ構成のRedisがあるとします。
マスターにはElasticIPがついていて、
WebアプリケーションからはそのIPを参照しています。


そのような構成になっていると仮定し、
App::Monitor::Simpleで定期的に死活監視しつつ
マスターが落ちたらスレーブを昇格させて
ElasticIPを自動的に付け替える、ということを実現してみたいと思います。
AWS デザインパターンのこれですね)
http://aws.clouddesignpattern.org/index.php/CDP:Floating_IP%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3

use strict;
use warnings;
use Clone qw(clone);
use App::Monitor::Simple qw/run/;
use VM::EC2;

my $ec2 = VM::EC2->new(-access_key=>$id,-secret_key=>$key,-endpoint=>$url);

# マスターサーバ情報
my $master = {
    host => 'xxx.xxx.xxx.xxx',
    instance_id => 'aaaaaaaaaaaaa',
    is_master => 1,
};
# スレーブサーバ情報
my $slave = {
    host => 'yyy.yyy.yyy.yyy',
    instance_id => 'bbbbbbbbbbbbb',
    is_master => 0,
};

my $elastic_id='zzz.zzz.zzz.zzz';

for my $server ($master, $slave) {
    my $host = $server->{host};

    warn "[$host] start!";

    # redisの死活監視
    my $ret = run(
        {
            command     => "redis-cli -h $host -p 6379 dbsize",
            interval    => 5,
            retry       => 5,
            quiet       => 1,
        }
    );
    if($ret == 0) {
        warn "[$host] ok!";
    } else {
        warn "[$host] ng!";

        # マスターが落ちている場合
        if($server->{is_master}) {
            # スレーブをマスターに昇格
            my $change_host = $slave->{host};
            system("redis-cli -h $change_host -p 6379 slaveof no one");

            # Elastic IP 付け替え
            my @addr = $ec2->describe_addresses($elastic_ip);
            my $result = $addr[0]->associate_address($elastic_addr => $slave->{instance_id});

            # 情報swap
            my $tmp = clone($master);
            $master = clone($slave);
            $slave  = clone($tmp);
            $master->{is_master} = 1;
            $slave->{is_master} = 0;
        }

        # 自動でスレーブ作り直し?
        # ...
        # もしくは 通知して exit?
        return;
    }
}

Todo

  • タイムアウト処理もこのモジュールで出来たほうがいいのかなー
  • Warningも別判定にしたほうがいいのかなー

最後に

もちろんNagiosなどツールを使えば同じことが出来ますが、
シンプルな監視をしたいときには有用かと思うのですがいかがでしょうか。

*1:全てフィクションです!!!!