Catalyst::Manual::Cookbook::Deployment

Cookbook長いので分割。
デプロイについてのレシピ。Webサーバーエンジンとアプリケーションの効率化も含む。
http://search.cpan.org/~jrockway/Catalyst-Manual-5.700701/lib/Catalyst/Manual/Cookbook.pod#Deployment

mod_perl Deployment

mod_perlは多くのアプリケーションに対しての最適解だけど利点と欠点を述べる。他の方法としてはFastCGIがある。

Pros
  • Speed

mod_perlはとても高速で、それぞれのApacheプロセスのメモリにアプリケーションをロードすることによって恩恵を受けられる。

  • Shared memory for multiple apps

同じサーバーで複数のCatalystアプリケーションをする必要がある場合、mo_perlは共通のモジュールを共有する。

Cons
  • Memory usage

アプリケーションはすべてメモリにロードされるのでそれぞれのApacheプロセスが大きくなってしまう。これは静的ファイルや大きいファイル、速度の遅いクライアントなどに束縛され得るということを意味する。そのため、構造を分割して軽量なフロントエンドが動的なリクエストをバックエンドのmod_perlサーバーに渡すようにした方がいい。

  • Reloading

アプリケーションを変更するとApacheを再起動する必要がある。CatalystApache::ReloadやStatINCをサポートしていない。これはフロントにWebサーバーを置くいい理由になり、そこにErrorDocument 502を設定してメンテナンスによってダウンしていることを通知できる。

  • Cannot run multiple versions of the same app

名前空間が衝突するので同じアプリケーションの違うバージョンを同じApacheインスタンスで実行できない。

  • Setup

Catalystアプリケーションを実行するためにmod_perlをセットアップしよう。
1. Install Catalyst::Engine::Apache
最新バージョンのCatalystCatalyst::Engine::Apacheをインストールする。バージョン5.50からApacheエンジンがCatalystから分割されて個別に更新できるようになった。
2. Install Apache with mod_perl
Apache 1.3と2を両方ともサポートしてるけど2を強く推奨する。Apache2ではworker MPMではなくpreform MPMを使用できる。これは多くのPerlモジュールがスレッドセーフじゃないので、スレッドワーカー環境で問題が起きる可能性があるため。でも、Catalystはスレッドセーフなのでちゃんとわかってる人なら別にいい。
Debianでは次のコマンドでインストールできる。

    apt-get install apache2-mpm-prefork
    apt-get install libapache2-mod-perl2

3. Configure your application
すべてのCatalystアプリケーションはmod_perlで実行すると自動的にmod_perlハンドラになる。だから設定はラクチン。Apache2での基本的な設定はこうなる。

    PerlSwitches -I/var/www/MyApp/lib
    PerlModule MyApp
    
    <Location />
        SetHandler          modperl
        PerlResponseHandler MyApp
    </Location>

一番大事な行はPerlModule MyAppだ。これはmod_perlにアプリケーション(コントローラ、モデル、ビュークラスと設定を含む)を共有メモリにpreloadするように指示する。-Debugモードが有効になってると最初にapacheを起動したときに起動メッセージが表示されるだろう。
Apache1.3の例はCatalyst::Engine::Apache::MP13を見てください。

Test It

これでおk。http://your.server.com/にアクセスして確認してみよう。

Other Options
  • Non-root location

サーバーやバーチャルホストのルートで実行するとは限らない。

    <Location /myapp>
        SetHandler          modperl
        PerlResponseHandler MyApp
    </Location>

この方法で起動するとuri_forメソッドが役に立ち、正しいリンクを作ってくれる。

  • Static file handling

静的ファイルをApacheで直接受ける場合。

    DocumentRoot /var/www/MyApp/root
    <Location /static>
        SetHandler default-handler
    </Location>

これはroot/staticのすべてのファイルをApacheが直接処理するようにする。2段構成の場合はフロントのサーバーが静的ファイルを処理する。

Catalyst on shared hosting

FastCGIとシェルが使える共有サーバーがあれば、Catalystアプリケーションをローカルディレクトリにインストールできる。まずは

perl -MCPAN -e shell

を実行し、CPANの設定プロセスを行ってから何もインストールしないで終了する。次に.bashrcを開いて

    export PATH=$HOME/local/bin:$HOME/local/script:$PATH
    perlversion=`perl -v | grep 'built for' | awk '{print $4}' | sed -e 's/v//;'`
    export PERL5LIB=$HOME/local/share/perl/$perlversion:$HOME/local/lib/perl/$perlversion:$HOME/local/lib:$PERL5LIB

を追加し、一旦ログアウトしてから再度ログインする。最後に.cpan/CPAN/MyConfig.pmを開いて以下を追加する。

    'make_install_arg' => qq[SITEPREFIX=$ENV{HOME}/local],
    'makepl_arg' => qq[INSTALLDIRS=site install_base=$ENV{HOME}/local],

これでいつも通りCPANモジュールをインストールできる。モジュールはローカルディレクトリにインストールされ、Perlはそれを参照してくれる。最後に、あなたほバーチャルホストのルートに移動してアプリケーションのスクリプトディレクトリへのシンボリックリンクを作る。

    cd path/to/mydomain.com
    ln -s ~/lib/MyApp/script script

で、.htaccessに以下の設定を追加する(これはサーバーが.plをfcgiとして扱うようになっている場合。そうじゃない場合はスクリプト名をmyapp_fastcgi.fcgiなどにリネームするか、SetHandlerで設定する必要がある)。

  RewriteEngine On
  RewriteCond %{REQUEST_URI} !^/?script/myapp_fastcgi.pl
  RewriteRule ^(.*)$ script/myapp_fastcgi.pl/$1 [PT,L]

これでhttp://mydomain.com/はちゃんと動く。

FastCGI Deployment

FastCGICGIに対する拡張で高いパフォーマンスを発揮する。製品環境(production environments)に最適だ。

Pros
  • Speed

FastCGImod_perlと同等の性能を発揮する。CGIという言葉に惑わされてはいけない。アプリケーションは複数の永続プロセスとしてWebサーバーからの接続を待機する。

  • App Server

外部FastCGIサーバーを使用する場合、アプリケーションはスタンドアローンアプリケーションサーバーとして動作する。これはWebサーバーとは別々に再起動できる。そのためより堅牢な環境となり、アプリケーションの変更を素早く適用できる。フロントのサーバーではアプリケーションが再起動している間にメンテナンスページを表示することもできる。

  • Load-balancing

アプリケーションを複数のバックエンドサーバーで起動してフロントのWebサーバーからロードバランスすることもできる。この場合は1つが落ちてもアプリケーションは無事だ。

  • Multiple versions of the same app

それぞれのFastCGIアプリケーションは異なるプロセスなので1つのサーバーで同じアプリケーションの違うバージョンを実行できる。

  • Can run with threaded Apache

アプリケーションはApacheの内部で実行されないので、スレッドセーフかどうかを気にせずに、より高速なmpm_workerを使用できる。

Cons
  • More complex environment

FastCGIでは監視するものが増え、mod_perlを使用する場合より多くのプロセスが実行される。

Setup

1. Install Apache with mod_fastcgi
mod_fastcgiサードパーティのモジュールでhttp://www.fastcgi.com/にある。多くのディストリビューションではパケージがあるだろう。Debianではlibapache2-mod-fastcgiだ。
2. Configure your application

    # Serve static content directly
    DocumentRoot  /var/www/MyApp/root
    Alias /static /var/www/MyApp/root/static

    FastCgiServer /var/www/MyApp/script/myapp_fastcgi.pl -processes 3
    Alias /myapp/ /var/www/MyApp/script/myapp_fastcgi.pl/
    
    # Or, run at the root
    Alias / /var/www/MyApp/script/myapp_fastcgi.pl/

この設定は3つのアプリケーションプロセスを起動し、/myapp/でアプリケーションにアクセスする。

Standalone server mode

さきほどの例ほど簡単ではないが、アプリケーションをexternal serverで実行するともっと柔軟になる。
最初に、アプリケーションをソケットをリッスンするスタンドアローンのサーバーとして起動する。

script/myapp_fastcgi.pl -l /tmp/myapp.socket -n 5 -p /tmp/myapp.pid -d

または、Webサーバーを同じマシンで実行していない場合はTCPポートをリッスンすることもできる。

script/myapp_fastcgi.pl -l :8080 -n 5 -p /tmp/myapp.pid -d

pidファイルを使って起動/停止を行うinitスクリプトを書いたほうがいいかもしれない。
で、Apacheを実行中のサーバーに接続する設定をする。

    # 502 is a Bad Gateway error, and will occur if the backend server is down
    # This allows us to display a friendly static page that says "down for
    # maintenance"
    Alias /_errors /var/www/MyApp/root/error-pages
    ErrorDocument 502 /_errors/502.html

    FastCgiExternalServer /tmp/myapp.fcgi -socket /tmp/myapp.socket
    Alias /myapp/ /tmp/myapp.fcgi/
    
    # Or, run at the root
    Alias / /tmp/myapp.fcgi/

Development server deployment

開発用のサーバーはPerlで書かれたミニWebサーバーです。もしアクセス数が少なくmod_perlFastCGIを必要としないなら、開発用のサーバーをフロントの軽量なプロキシWebサーバーと一緒にアプリケーションサーバーとして使用できます。でも、特にInternet Explorerで既知の問題があるので注意すること。多くの問題はサーバーを-k(keepalive)オプションで実行した場合に発生するが、より複雑なアプリケーションには当てはまらないので注意すること。Catalyst::Engine::HTTP::POEを使うことを検討すること。このレシピはPOEにも適用できる。

Pros

速度を除いてFastCGIの場合と同じ。

  • Simple

開発用のサーバーで動いたなら製品環境でだって動くはずだ!

Cons
  • Speed

mod_perlFastCGIのように速くはない。リクエストごとにフォークする必要がある。静的ファイルはWebサーバーで扱うようにすること。

Setup
  • Start up the development server
script/myapp_server.pl -p 8080 -k  -f -pidfile=/tmp/myapp.pid -daemon

initスクリプトを書いた方がいいでしょう。

mod_proxyを有効にして以下を追加する。

    # Serve static content directly
    DocumentRoot /var/www/MyApp/root
    Alias /static /var/www/MyApp/root/static

    ProxyRequests Off
    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>
    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/

同じホストで異なるアプリケーションを実行したい場合はこの設定をVitualHostで行えばいい。

Quick deployment: Building PAR Packages

開発環境で動くアプリケーションはあるが、デモ/デプロイ/テストなどのためにそれと同じものを素早く作りたい場合。
PARパッケージで多くのトラブルを回避できる。これはblibツリーを含んだZipファイルだ。フラグを指定してprereqとperlインタープリタを含めることもできる。

Follow these few points to try it out!

1. CatalystとPARの0.89以降をインストールする。

    % perl -MCPAN -e 'install Catalyst'
    ...
    % perl -MCPAN -e 'install PAR'
    ...

2. アプリケーションを作る

    % catalyst.pl MyApp
    ...
    % cd MyApp

Catalystの5.62以降はModule::Install::Catalystを含んでいるので、プロセスを大幅に簡単にできる。アプリケーションディレクトリで

% perl Makefile.PL
% make catalyst_par

これでmyapp.parが出来上がる。あとのステップはオプション。

3. "par"(typoじゃないよ)でPARパッケージをテストする。

    % parl myapp.par
    Usage:
        [parl] myapp[.par] [script] [arguments]

      Examples:
        parl myapp.par myapp_server.pl -r
        myapp myapp_cgi.pl

      Available scripts:
        myapp_cgi.pl
        myapp_create.pl
        myapp_fastcgi.pl
        myapp_server.pl
        myapp_test.pl

    % parl myapp.par myapp_server.pl
    You can connect to your server at http://localhost:3000

4. Perlインタープリタを含んだバイナリを作成するには?

    % pp -o myapp myapp.par
    % ./myapp myapp_server.pl
    You can connect to your server at http://localhost:3000

Serving static content

Catalystで静的コンテンツを扱うのは少しトリッキーだ。Catalyst::Plugin::Static::Simpleはすべてを簡単にしてくれる。このプラグインは開発中に静的コンテンツを自動的に扱い、製品環境では簡単にApacheなどに切り替えることができる。

Introduction to Static::Simple

Static::Simpleはアプリケーションで静的コンテンツを扱うためのプラグインだ。デフォルトで、rootディレクトリの外のTemplate Toolkitの拡張子を除いたほとんどのタイプのファイルを扱える。すべてのファイルはパスで受け付けるので、images/me.jpgがリクエストされるとroot/images/me.jpgが返される。

Usage

このプラグインを使うにはMyApp.pmのuseで以下のようにすればいい。

use Catalyst qw/Static::Simple/;
Configuring

静的コンテンツはrootディレクトリ内のディレクトリの1つから供給される。root/cssやroot/imagesなどを使用していると追加設定が必要となり、root/jsなどのディレクトリを追加することになったときにめんどくさいので、root/staticにサブディレクトリを作ってこうするのがいい。

    root/
    root/content.tt
    root/controller/stuff.tt
    root/header.tt
    root/static/
    root/static/css/main.css
    root/static/images/logo.jpg
    root/static/js/code.js

静的ファイルはroot/staticに配置し、それ以外にTemplate Toolkitのファイルを置く。

  • Include Path

デフォルトのロケーションを変更する場合にはStatic::Simpleがそこを見れるように設定する。

 MyApp->config->{static}->{include_path} = [
  MyApp->config->{root},
  '/path/to/my/files' 
 ];

include_pathを上書きすると通常のrootパスは自動的に追加されないので自分で追加する必要がある。サーチパスは並んだ順番になり、最初に見つかったファイルが使われる。

  • Static directories

ディレクトリをstatic専用にする場合は相対パス正規表現を使ってこうする。

 MyApp->config->{static}->{dirs} = [
   'static',
   qr/^(images|css)/,
 ];
  • File extensions

デフォルトでは以下の拡張子は供給されない(これらはCatalystが処理する)。このリストは簡単に置き換え可能である。

 MyApp->config->{static}->{ignore_extensions} = [
    qw/tmpl tt tt2 html xhtml/ 
 ];
  • Ignoring directories

ディレクトリを無視するようにできる。include_pathを使ってる場合、include_pathへの相対ディレクトリが無視される。

MyApp->config->{static}->{ignore_dirs} = [ qw/tmpl css/ ];
Serving manually with the Static plugin with HTTP::Daemon (myapp_server.pl)

Catalyst::Plugin::Staticを使ってもっと直接的に制御したい場合。
メインのアプリケーションクラス(MyApp.pm)でプラグインをロードする。

use Catalyst qw/-Debug FormValidator Static OtherPlugin/;

endメソッドで静的コンテンツをビューにフォワードしないようにする必要がある場合。

    sub end : Private {
        my ( $self, $c ) = @_;

        $c->forward( 'MyApp::View::TT' ) 
          unless ( $c->res->body || !$c->stash->{template} );
    }

このコードはテンプレートがコントローラによってセットされている場合と、$c->res->{body}にデータがない場合にのみ、ビューにフォワードする。
次に、/staticパスのリクエストをハンドルするコントローラを作成する。ヘルパーを使ってlib/MyApp/Controller/Static.pmを作成する。

$ script/myapp_create.pl controller Static

ファイルを編集して次のコードを追加する。

    # serve all files under /static as static files
    sub default : Path('/static') {
        my ( $self, $c ) = @_;

        # Optional, allow the browser to cache the content
        $c->res->headers->header( 'Cache-Control' => 'max-age=86400' );

        $c->serve_static; # from Catalyst::Plugin::Static
    }

    # also handle requests for /favicon.ico
    sub favicon : Path('/favicon.ico') {
        my ( $self, $c ) = @_;

        $c->serve_static;
    }

以下のコードをHTMLのヘッダに書いてfavicon.ico以外のアイコンを使用することもできる。

<link rel="icon" href="/static/myapp.ico" type="image/x-icon" />
Common problems with the Static plugin

StaticプラグインMIMEタイプを自動判定するのにshared-mime-infoパッケージを使用している。このパッケージは特にwin32とOS Xでインストールするのが難しいことで有名だ。OS Xの場合はFinkをインストールしてapt-get install shared-mime-infoを使うことができる。
最適な結果を得るために最新バージョン(0.16以上)を使用していることを確認しよう。CSSファイルの取り扱いにエラーが発生したり、text/cssじゃなくてtext/plainになってしまう場合はshared-mime-infoが古いかもしれない。Staticコントローラに以下のコードを追加することもできる。

    if ($c->req->path =~ /css$/i) {
        $c->serve_static( "text/css" );
    } else {
        $c->serve_static;
    }
Serving Static Files with Apache

Apacheを使ってるならroot/staticへのリクエストをサーバーレベルで処理してCatalystとStaticプラグイン、コントローラをバイパスできる。必要なのはDocumentRootを定義して静的コンテンツのために別々のLocationブロックを使用することだ。これはmod_perl 1.xでの完全な例。

    <Perl>
        use lib qw(/var/www/MyApp/lib);
    </Perl>
    PerlModule MyApp

    <VirtualHost *>
        ServerName myapp.example.com
        DocumentRoot /var/www/MyApp/root
        <Location />
            SetHandler perl-script
            PerlHandler MyApp
        </Location>
        <LocationMatch "/(static|favicon.ico)">
            SetHandler default-handler
        </LocationMatch>
    </VirtualHost>

簡単な例はこちら。

    Alias /static/ "/my/static/files/"
    <Location "/static">
        SetHandler none
    </Location>

(ていうか、さっきデプロイのところでこの話でてきた気がするな・・・)

Caching

Catalystはアプリケーションの高速かのためにいろんな種類のキャッシングを簡単に使用できる。

Cache Plugins

CPANのキャッシュモジュール(Cache::FastMmap, Cache::FileCache, Cache::Memcached)をラップする3つのプラグインがある。遅い処理の結果をキャッシュするのに使用できる。
CPANのページはPODドキュメントのXHTMLバージョンをキャッシュするのにFileCacheを使用できる。これは元のドキュメントはあまり更新されないが参照は多いからため、キャッシュを使用するのに典型的な例となる。

    use Catalyst qw/Cache::FileCache/;
    
    ...
    
    use File::stat;
    sub render_pod : Local {
        my ( self, $c ) = @_;
        
        # the cache is keyed on the filename and the modification time
        # to check for updates to the file.
        my $file  = $c->path_to( 'root', '2005', '11.pod' );
        my $mtime = ( stat $file )->mtime;
        
        my $cached_pod = $c->cache->get("$file $mtime");
        if ( !$cached_pod ) {
            $cached_pod = do_slow_pod_rendering();
            # cache the result for 12 hours
            $c->cache->set( "$file $mtime", $cached_pod, '12h' );
        }
        $c->stash->{pod} = $cached_pod;
    }

永久にキャッシュすることもできるけど、必要なくなったときに自動的に破棄されるようにしている。

Page Caching

キャッシングのもう1つの方法はHTMLページ全体をキャッシュすることだ。これはSquidのようなフロントエンドのプロキシサーバーが昔からやってることであり、CatalystのPageCacheプラグインを使って頻繁に参照される遅いページ全体を簡単にキャッシュできる。
多くのサイトでこのようなコンテンツがいっぱい詰め込まれたページをもってるだろう。これは処理に時間がかかり、すべてのユーザーに対して同じことをやっている。

    sub front_page : Path('/') {
        my ( $self, $c ) = @_;
        
        $c->forward( 'get_news_articles' );
        $c->forward( 'build_lots_of_boxes' );
        $c->forward( 'more_slow_stuff' );
        
        $c->stash->{template} = 'index.tt';
    }

速度を改善するためにPageCacheを追加する。

    use Catalyst qw/Cache::FileCache PageCache/;
    
    sub front_page : Path ('/') {
        my ( $self, $c ) = @_;
        
        $c->cache_page( 300 );
        
        # same processing as above
    }

これでフロントページ全体が5分間キャッシュされ、5分経ったらページを新しく作ってまたキャッシュする。
ページキャッシュはページのURIとすべてのパラメータをキーにするので、/へのリクエストと/?foo=barは別々のキャッシュになる。また、プラグインではGETリクエストのみがキャッシュされる。
キャッシュされたページのHTTPヘッダに追加することでフロントエンドのSquidを使うこともできる。

    MyApp->config->{page_cache}->{set_http_headers} = 1;

これで次のヘッダがセットされ、プロキシとブラウザはコンテンツを自分自身でキャッシュする。

    Cache-Control: max-age=($expire_time - time)
    Expires: $expire_time
    Last-Modified: $cache_created_time
Template Caching

Template Toolkitコンパイルされたテンプレートをキャッシュすることができる。Catalystでこれを有効にするには次の設定を行う。TTはファイルの更新時間をキーにしてコンパイルされたテンプレートをキャッシュするので、変更は自動的に検出される。

    package MyApp::View::TT;
    
    use strict;
    use warnings;
    use base 'Catalyst::View::TT';
    
    __PACKAGE__->config(
        COMPILE_DIR => '/tmp/template_cache',
    );
    
    1;