perlでデーモン書く時は素直にMoose化しておくのもいいかもな
今更Mooseの話題かよ、と思われることでしょうが、自分は常に流行の3年遅れぐらいを全力で追いかけるタイプなので、自分にとっては今が旬。というわけで、Moose的なはなしを書きます。
突然ですが、現役バリバリのperl使いのみなさんに質問です。
POEやらAnyEventやらでちょっとしたアプリケーションサーバを書く場合、みなさんはどうやって「デーモン化」してますか?
自分はもう何回もこの手のものを書いてきたつもりですが、実は未だにベストな手法を編み出せてません。。。
App::DaemonやDaemon::Genericあたりでなんとなく自分流な形を模索した時期もありましたが、結局どれも面倒くさくなって、最近では「もう nohup perl hoge_server.pl & でいいじゃん」みたいな。「止める時は pkill -f hoge_server でいいじゃん、文句アッカ」みたいな。そんなダメダメな運用を繰り返す今日この頃であります。
daemontoolsじゃないヤツで
そんな話をすると「最近はdaemontoolsがいいみたいよ」という流れになるわけですが、自分はどうもあの雰囲気になじめないんですよ。
「/serviceとか大胆な配置場所がちょっと鼻につくぜ」とか「みんながイイっていってるのは本当はイクないんだぜ。」とか訳の分からない理由をつけては新しいものの導入を拒否してしまいます。
新しいものを拒否するという現象はいよいよエンジニアとしての終わりが近いナ、と自分でも感じる今日この頃なんですが、流行の3年あとを追いかけるオールドタイプにとっては新しすぎるのは無理なんです。はい。
まぁ本当はただの食わず嫌いなんですが、とにかくdaemontoolsはちょっと矢田。できれば外部ツールにたよらずperlだけでシンプルに解決したいな、と。
そこで考えてみたんですが、MooseX::DaemonizeとMooseX::LogDispatch、あとMooseX::SimpleConfigをうまく組み合わせたものでデーモン化の下地をつくってやって、そこにアプリケーションサーバ本体を載せるような構成はどうだろうか、と。
構成イメージ
イメージはこんな感じです。
libの下にAppというネームスペースをつくってあげて、その中で「MooseXを組み合わせたデーモン化の下地」を書いておきます。そしてscript/my_server.plからはApp::MyServerを、App::MyServerからはMyserverを、という順に呼び出します。
. |-- lib | |-- App | | `-- MyServer.pm | |-- MyServer | | |-- Fuga.pm | | `-- Hoge.pm | `-- MyServer.pm `-- script `-- my_server.pl
つまりデーモン化の足回り的なコードはすべてApp::MyServerに閉じ込めてしまします。ここでMooseXをモリモリ使います。それ以外のサーバ本体のコードとはきれいに分離できるようにしています。
実はこのアイデア、牧さんが2年くらい前に書いていたHamakiを見て盗作させていただきました。ゴチです。
で、そのデーモン化の足回りとなるApp::Myserverのコードはこんな感じになります。
適当に書いているのでがちゃがちゃしてますが。
package App::MyServer use Moose; use MyServer; with qw(MooseX::Daemonize MooseX::LogDispatch MooseX::SimpleConfig); has '+configfile' => ( default => "/etc/daemon.yaml", ); has config => ( is => 'ro', isa => 'HashRef', required => 1, ); has log_dispatch_conf => ( is => 'ro', isa => 'HashRef', lazy => 1, required => 1, default => sub { my $self = shift; if ( $self->foreground ) { { class => 'Log::Dispatch::Screen', min_level => 'debug', stderr => 1, DatePattern => 'yyyy-MM-dd', format => '[%d] [%p] %m at %F line %L%n' }; } else { { class => 'Log::Dispatch::FileRotate', min_level => 'debug', filename => '/tmp/app.log', mode => 'append', DatePattern => 'yyyy-MM-dd', max => 50, format => '[%d] [%p] %m at %F line %L%n' }; } }, ); has pidbase => ( is => 'rw', isa => 'Str', default => "/tmp/", ); after "start" => sub { my $self = shift; return unless $self->is_daemon(); my $server = MyServer->new( config => $self->config, logger => $self->logger, ); try { $server->start; } catch { warn "an error occuerd: $_"; } }; no Moose; __PACKAGE__->meta->make_immutable; 1
ちなみにこっちが起動スクリプト。
# my_server.pl #!/usr/local/bin/perl -w use strict; use warnings; use App::MyServer; my $daemon = App::MyServer->new_with_options(); my ($command) = @{ $daemon->extra_argv }; $daemon->$command if $daemon->can($command); warn( $daemon->status_message ); exit( $daemon->exit_code );
まず言えることとして、MooseX::Daemonizeのおかげでstart, stop, restartなどが使えるようになっています。
またMooseX::Daemonizeは内部でMooseX::Getoptを使ってるので、これだけで引数の処理を面倒みてくれるしMooseX::SimpleConfigとの食い合わせもよいようで、configファイルでの指定と引数での指定をミックスして使うことも可能になります。
# 起動イメージ perl -Mblib script/my_server.pl --configfile ./config.yaml start
さらにさらに、MooseX::Daemonizeの機能で -f オプションをつけるとフォアグラウンド処理になるので、MooseX::LogDispatchもこれに連動するようにしてます。-f をつけて起動すると自動的にスクリーンに、そうでない時はapp.logというファイルにロギングする、とかね。気が利いてるでしょ。
あとは$configと$loggerをオブジェクトとしてそのままサーバ本体のコンストラクタに渡している点もこだわりポイントです。
こうすることで、サーバ本体側では$loggerや$configをそのまま使えばよい。
つまり今まで動かしていたようなサーバプログラムに最低限の改修をくわえるだけでこのウツワの上に載せることができるかなと。無理に全体をMoose化しなくてもよい、という状態にしたかったのです。
まとめ
自分は今までかたくなに「Mooseは嫌いだ」と言ってきました。自分とこの開発チームでのMoose導入もガッチリ阻止してきました。
なのでこのブログを読んでくれる仕事関係の方々は「はぁ?このおっさん今更なに言ってんのさ!」と思うことでしょう。
ごめんね。B型だから言うことがコロコロ変わるのさ。勘弁してつかあさい。
いや、正直いうと、今でも「何でもかんでもMooseを導入するのはよろしくない」と思ってます。
特に起動にかかる時間は間違いなく長くなるので、何度も呼び出すようなプログラムはMooseを使わない方が良いと思います。
でもサーバスクリプトなんかは起動に少しくらい時間がかかってもたいして問題にならないし、部分的にMoose化するのは悪くないかなと。MooseXなRoleたちを上手につかってやると自分流の定番サーバスタイルができあがるかな、と思った次第です。
いま仕事でアプリケーションサーバを再構築しているものがあるので、まずそいつを上記のような構成にして試してみようかなと考えてます。
おわり。