« 2006年08月 | メイン | 2006年10月 »

2006年09月28日

Deployツール

会社でYAMLでDeployのPlanを書いて、実行できるDeployツールを作成中

common:
    proxies:
      - proxy1
      - proxy2
plans:
  - name: mod_proxy
    concurrency: 2
    base: ModProxy
    method:
      - rsync
      - httpd_graceful
    rsync_exclude:
      - .svn/
    rsync_src: /mod_proxy
    rsync_dest: /var/proxy
    hosts: c{proxies}

  - name: mod_perl
    concurrency: 1
    base: ModPerl
    method:
      - proxy_drop
      - httpd_stop
      - rsync
      - httpd_start
      - proxy_add
    balancers: c{proxies}
    balancer_name: balancer
    hosts:
      - application1
      - application2

  - name: etc
    concurrency: 1
    base: ModPerl
    method:
      - rsync
    hosts:
      - util


というようなplanを書いて、

# ./deploy -c sample.yaml

と実行。

なにげにconfを書くのが面倒なので、includeができたり共通部分をDRYに書けるようにとしている。
あと、codeがplaggerをかなり参考にしてる。

2006年09月23日

CatalystでText::VimColorが動かない

FemoのText::Hatenaをアップデートしようと確認していたら、Text::VimColorがserver.plでうまく動かない。

試しに適当なアプリケーション作って、

sub test : Local {
    my ( $self, $c ) = @_;
    my $test = <<'EOF';
#!/usr/bin/perl
use strict;
use warnings;
print "Hello World\n";
EOF
    my $html = Text::VimColor->new(
        string   => $test,
        filetype => 'perl',
        html_full_page => 1,
    )->html;
    $c->res->body($html);
}


# ./script/myapp_server.pl -r

で起動して、ブラウザアクセスするとレスポンスが返ってこなくなる。
Terminalの方で、Ctrl+Cするとレスポンスが返ってくる。ただしコンテンツは空。
ちなみに、

# ./script/myapp_server.pl -r -f

としてforkすると動く。

selectかexecのあたりかが原因なんだろうけど、よくわからなかったので

package Text::VimColor;
use IPC::Run qw//;
sub _run {
   my ($self, $prog, @args) = @_;
   local $|=1;
   IPC::Run::run([$prog, @args], \my $in,\my $out,\my $err);
   return 1;
}

にした。

あと、これだと、FastCGIで動かないようだったので、とりあえずFemoの本番サーバをmod_perlに変更した。
あとで、もう少し深追いする。

2006年09月21日

Load Balancer ManagerにアクセスするPerl Module

mod_proxy_balancerのLoad Balancer ManagerにアクセスするPerl Moduleなんかも作っていたりするので、簡略版を載せてみる。

my $manager = BalancerManager->new(
    manager => 'http://proxy/lbman',
    balancer => 'test',   # balancer://testの設定
);
$manager->enable('http://foo:8000'); #balancermanagerに登録してあるuri
$manager->disable('http://foo:8000');


enable/disableの戻り値は画面のまんまで、Ok or Dis or Err。
該当しない場合は、「-」になる。

↓Moduleのコード

package BalancerManager;

use strict;
use warnings;
use base qw/Class::Data::Accessor/;
use LWP::UserAgent;
use HTML::TableExtract;

__PACKAGE__->mk_classaccessors( qw/manager ua balancer/ );

sub new {
    my $class = shift;
    my $self = {
        ua => LWP::UserAgent->new,
        @_,
    };
    bless $self, $class;
}

sub enable {
    my $self = shift;
    my $host = shift;
    $self->_run( $host, 'Enable' );
}

sub disable {
    my $self = shift;
    my $host = shift;
    $self->_run( $host, 'Disable' );
}

sub _run {
    my $self = shift;
    my ( $host, $method ) = @_;
    my $manager = URI->new( $self->manager );
    $manager->query_form(
        dw => $method,
        b  => $self->balancer,
        w  => $host,
    );

    my $response = $self->ua->get($manager);
    die $response->status_line unless $response->is_sucess;

    my $report = $self->html_parser( $response->content );
    return $report->{$host} || '-';
}

sub html_parser {
    my ( $self, $html ) = @_;
    my %report;

    my $balancer = $self->balancer;
    if ( $html =~ m!<h3>.*?balancer://$balancer</a></h3>(.*?)<hr />!s ) {
        $html = $1;
    }

    my $te = HTML::TableExtract->new( headers => ['Worker URL','Route','RouteRedir','Factor','Status'] );
    $te->parse($html);
    my $table = $te->first_table_found;
    foreach my $row ( $table->rows ) {
        my $url = $row->[0];
        my $status = $row->[4];
        $url =~ s!<[^>]+?>!!gi;
        $report{$url} = $status;
    }
    return \%report;
}

1;

mod_proxy_balancerのretry

mod_proxy_balancer(mod_proxy)のretry設定は、

コネクションをプーリングするための、リトライのタイムアウトを秒で 指定します。バックエンドサーバへのコネクションプーリングが失敗した場合は、 タイムアウトの期間が過ぎるまで、そのサーバにリクエストをフォワードしません。

というもので、

BalancerMember http://1.2.3.6:8000 retry=60 loadfactor=10

こんなように書ける。

アプリケーションサーバにデプロイするときに、

  1. httpdの停止
  2. rsync
  3. httpdの開始

という順番で行うのが通常だと思うけど、デフォルトのretry間隔が60秒になっているため、httpdを開始してからアクセスがバックエンドに届くには、最大1分間待たなければならない。
バックエンドのサーバが十台以上あるようなところだったらいいけれど、2~3台のサービスで間をあけずにデプロイしてしまうと下手するとサービスが数秒間、全落ちになってしまう可能性がある。

ということで、普通にretryを短く(数秒)にすればいいんじゃね。

な訳だが、ほんとにバックエンドが落ちたという場合に「connection refused」のログがいっぱいになるし、確認してないけど負荷もちょっとあがりそうなので、retryの秒数を2段階にできればいいなとか、Apacheのmoduleの人に相談中。

それともtimeoutとかmaxattemptsとか工夫すればできるんかなぁ。

http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypass

2006年09月20日

qw// -> qw()

id:typesterのとこで気になったんで、今日会社で聞いて回った。

結果、みんな

qw(foo bar baz)

なので僕もそうする事にする。

ちなみに、マッチングは最近

m![a-z]!

と書くようにしている。
slashのマッチングが多いって言うのもあるけどね。
社内にはカッコ派の方もいました。

2006年09月18日

Stacktrace付きdie

ログをもう少し詳しくしたいなぁと思って調べていて、Error.pmのソースで気がついたんけど、

eval {
    die Foo->new;
}
warn ref $@; #Foo


が使えるんですね。
Sledge::Plugin::DebugScreenとか、CGI::Applicationだと$self->{__stacktrace}で情報を保存しているけど、$@を利用してstacktrace情報を受け渡すことができるよな、と考えたので書いてみた

package MyApp::Exception;

use strict;
use warnings;
use base qw/Class::Accessor::Fast Exporter/;
use Devel::StackTrace;
use overload 
    '""' => 'as_string';

__PACKAGE__->mk_accessors(qw/frames message debug/);
our @EXPORT_OK = qw/throw/;
our $IGNOREPKG = [];

sub throw {
    my $exception = __PACKAGE__->new(@_);
    die $exception;
}

sub new {
    my $class = shift;
    my $message = join '', @_;
    $message =~ s/[\n\r]*$//gi;

    my $self = bless {
        frames => [],
        message => $message,
    }, $class;

    my $stack = Devel::StackTrace->new(
        ignore_package => $IGNOREPKG,
        no_refs          => 1,
        respect_overload => 1,
    );
    $self->frames([$stack->frames]);
    $self;
}

sub as_string {
    my $self = shift;
    unless ( grep { $_->package eq 'MyApp::Framework' } @{$self->frames} ) {#frameworkとの連携
        return $self->stacktrace;
    }
    my $caller = $self->frames->[0];
    sprintf("%s at %s line %s\n", $self->message, $caller->filename, $caller->line);
}

sub stacktrace {
    my $self = shift;
    my @trace;
    foreach my $frame ( @{$self->frames} ) {
        push @trace,
          sprintf( "%s line %s",
            $frame->filename, $frame->line );
    }
    sprintf( "%s at %s\n", $self->message, join( ", ", @trace ) );
}

1;


基本、dieの代わりにthrowを使うようにする。そうすっとSTDERRにstacktrace付きのメッセージがでる。

#!/usr/bin/perl
use MyApp::Exception qw/throw/;
DBI->connect(...) or throw 'connection failed';#stacktraceが付く


フレームワークを離礁しているところでは、フレームワークで、

package MyApp::Framework;
sub run {
    ....
    eval {
        local $SIG{__DIE__} = MyApp::Exeption->can('throw');
        $self->prepare;
        $self->execute;
        $self->finilize;
    };
    if ( $@ && ref $@ && $self->debug) {
    	$@->framesをtemplateに渡してdebugscreenなど
    }
    else {
    	die $@;
    }
}


と書いてあるので、普通にdieした時もtraceするし、throwを使っていてもいい具合に出力を調整することができる。

ただ、車輪の再発明をした気がすごくするなぁ。。

2006年09月10日

開発環境のこととか

今は、運用の中の人をやっているので、開発環境カンファレンスでしたが、運用に使えそうなネタが結構あってよかった。
自社開発でのサーバ監視、ldap。buildに5時間かかるrpmなど、参考にさせて頂きます。

個人的な開発(運用)環境ですが、
ハードは
・Mac(家MacBook Pro+mac mini、会社iMac)
・緊急対応用のWindows Note
な感じ。

社内で開発の方と連携ととりながら進めて行きたい時は、Windows Noteを持って開発陣の机の方へ行き、VNC(OSXVnc)でiMacを使ってます。全画面表示するとそこそこ使える。
緊急対応時もこの方法でやってます。たぶん夜中に会社のMacが勝手に動き出してます。

ソフトウェアの方で言えば、エディタとかにこだわる方ではなくて、jtermの付属品だった頃からずっとJeditというエディタを使ってきました。
最近alpha geekな人はEmacsを使っているという事を知りまして、ようやくEmacsを使い始めているところです。まだまだ初心者です。
shellも同じ理由でzsh。bashは小学生までらしいので。

Macに入れるソフトは
・QuickSilver ( ランチャーとしてのみ使用
・SSHKeyChain
・iTerm (TABが使える=screenはつかったことない
・wget(最近curlを使う
・Subversion
・Parallels( CentOS入れてる
・Jedit
あたりですかね。
会社のMacだとメモ用エディタとしてCotEditorを使用してます。


次回、運用環境カンファレンスとかキボンヌ。

2006年09月03日

Macbook Proのバッテリー交換

ここ最近、バッテリー駆動時に急に落ちることがあって、よくよく調べたら、

15 インチ MacBook Pro バッテリー交換プログラム
https://support.apple.com/macbookpro15/batteryexchange/index.html

見事にこれだった。

この話が出たときに調べたんだけど、本体のシリアル番号と勘違いしてた。
福山の運転手さんと交換できるのは来週だろうから、それまでワイアードなノートパソコンで我慢だ