Stellaqua - TOMの技術日記 このページをアンテナに追加 RSSフィード

2010年08月08日

[]Amazon EC2上のHadoopMeCabを使えるようにしてみた…い

最近すっかり"Webサービスを作ってみた"系の記事ばっかりでしたが、今回は久々にEC2上でのHadoopのお話。

以前の記事EC2上でHadoopを動かした時は、MeCabデフォルトで入っていなかったので、事前に自宅サーバ上で分かち書きしたデータを使っていました。

ただ、日本語処理するならMeCabはほぼ必須だろうし、せっかくならMeCabが使える状態でMapReduceしたいのが人情ってもんです。

という訳で、EC2上のHadoopを使って、MeCabを利用した日本語文章の単語カウントMapReduceに挑戦してみました。

基本的な方向性としては以下の通りです。

  1. EC2上でインスタンス起動。
  2. 起動したインスタンスMeCabインストール
  3. AMIとして保存。
  4. Hadoopを起動する時にこのAMIが使われるようにする。

で、実際にやってみた訳なんですが、結論から言うとうまくいきませんでした…。

AMIを保存して、そのAMIが使われるようにするところまではできたんですが、そのインスタンスログインできない状態になってしまって、実際にMeCabを利用したMapReduceを動かすところまでできていません。

という事で途中までではありますが、やってみた手順を書き残しておきたいと思います。

以下、"[local]$"がローカルのサーバ上、"[ec2]$"がEC2上でのプロンプトを表す事とします。

EC2上でHadoopを起動してログインする

まずはEC2上にインスタンスを1つ起動させてログインします。

[local]$ hadoop-ec2 launch-master hadoop-test 1
[local]$ scp -i stellaqua.id *.pem root@ec2-XXX-XXX-XXX-XXX.compute-1.amazonaws.com:/mnt
[local]$ hadoop-ec2 login hadoop-test

タイムゾーンを変更する

今回(というか毎度)参考にさせて頂いているid:rx7さんの記事によるとタイムゾーンの設定をしておいた方がよいとの事なので、ついでにここでタイムゾーンの変更をしておきます。

[ec2]$ cp /usr/share/zoneinfo/Japan /etc/localtime
cp: `/etc/localtime' を上書きしてもよろしいですか(yes/no)? y

MeCabインストールする

続いて本命のMeCabインストールします。

[ec2]$ yum -y update
[ec2]$ yum -y install mecab mecab-devel mecab-ipadic
[ec2]$ echo 'こんにちは' | mecab
こんにちは      感動詞,*,*,*,*,*,こんにちは,コンニチハ,コンニチワ
EOS

ちゃんとMeCabが使えるようになりましたね!

EC2root権限が与えられるので、yumで必要なものをガシガシ入れてしまうとよいですね。

AMIを作成してS3にアップロードする

AMI作成の手順については、毎度お世話になっているid:rx7さんの記事を参考にさせて頂きました。

Amazon EC2/S3を使ってみた - 3.EC2起動後〜AMI作成 - 元RX-7乗りの適当な日々

[ec2]$ cd /mnt/
[ec2]$ ec2-bundle-vol -d /mnt --privatekey pk-XXXX.pem --cert cert-XXXX.pem --user XXXX-XXXX-XXXX -p hadoop-0.17.0-i386 -r i386
[ec2]$ ec2-upload-bundle --bucket stellaqua/ec2_images/hadoop_mecab --manifest hadoop-0.17.0-i386.manifest.xml --access-key XXXX --secret-key XXXX
[ec2]$ exit
[local]$ hadoop-ec2 terminate-cluster hadoop-test
[local]$ ec2-register stellaqua/ec2_images/hadoop_mecab/hadoop-0.17.0-i386.manifest.xml
IMAGE   ami-XXXX
[local]$ vi hadoop-ec2-env.sh
→"S3_BUCKET=hadoop-ec2-images"となっているところを、上記で指定したバケットに変える

ポイントは、manifestファイルをHadoopのバージョンとアーキテクチャの名前を付けて保存する事と、設定ファイルでAMIを保存したバケットを設定しておく事ですね。

これで、hadoop-ec2コマンドでHadoopを起動する時に、登録したAMIが使われるようになるはずです。

早速、クラスタを起動してログインしてみます。

[local]$ hadoop-ec2 launch-cluster hadoop-test 1
[local]$ hadoop-ec2 login hadoop-test

これでログインできるはずなんですが、なぜかログインしに行ったままウンともスンとも言わない状態になってしまいます。

"Permission denied"とも言われないし、netstatで見るとESTABLISHEDにはなっているので、接続自体はできているとは思うのですが…。その後も色々試してはみたんですが、現時点でお手上げ状態になっています…。

また折を見て試してみて、うまくいったら記事にしようかなと思います。

2010年03月28日

[][]第51回PHP勉強会参加&BEARについてプレゼンしました

ブログに書くまでが(ry」…ではなく、「トラックバックするまでが勉強会」との事なので、早速。

第51回PHP勉強会に行って参りました。今回は発表者として、BEARに関するプレゼンをさせて頂きました。

今まで何度もPHP勉強会には参加させてもらいましたが、発表するのは初めてでした。

とりあえず、プレゼンする上で気を付けようと思っていた事はちゃんと気を付けてプレゼンできたかなとは思ってますが、後でUSTREAMの録画を見直して一人反省会をしようと思います。

今回の発表の一番の目的はBEARの事を一人でも多く知ってもらう事と思っていて、新米芸人のごとく、「今日は名前だけでも覚えて帰って下さいね。」という感じと思っていたので、そういう意味では目的は果たせたかなと。*1

会場提供頂いたトライコーン株式会社さん、ありがとうございました。幹事のgusagiさん始め、参加者の皆様、どうもお疲れ様&ありがとうございました。

*1:もう一つの重要なミッションである、BEAR作者のkoriymさんに直接お会いして話をさせて頂くという目的も無事果たす事ができました。(笑)

2010年01月20日

[][][][]BEARで始めるWebアプリケーション開発 その1「インストール〜HelloWorld」

今回から、ちょっとずつBEARを触っていってみようと思います。今回は、とりあえず基本に従って、インストールからHelloWorldまで。

早速インストール

インストールの手順は本家Wikiのインストールのページを参照しました。

Google Code Archive - Long-term storage for Google Code Project Hosting.

PEARパッケージなので、"pear install"で一発インストールできて楽ちんですね。

$ sudo pear channel-discover pear.bear-project.net
$ sudo pear install -a bear/BEAR-beta

結構依存パッケージが多いみたいで、色々と追加でインストールされました。

ちゃんとインストールできたかどうか確認する為、バージョンを表示させてみます。

$ bear --version
bear version 0.8.11.

オッケーですね。

こんにちは、Worldさん

さくっとインストールできたので、早速最初の一歩、HelloWorldしてみます。

お試し環境は以下の通りとします。

ドキュメントルート
/home/www/
アプリ配置ディレクトリ
/home/bear/

Saffold的なものがあるので、さくっとコマンドを叩いてみます。

$ bear init-app /home/bear/helloworld
BEAR App files are successfully made at '/home/bear/helloworld'.
Thank you for using BEAR.Ok.

簡単ですね!*1

ドキュメントルートから辿れるように、シンボリックリンクを張ります。

$ cd /home/www
$ ln -s /home/bear/helloworld/htdocs helloworld

これで準備オッケー。で、HelloWorldを作るんですが、何もしなくてもサンプルとしてindex.phpに、ExciteニュースのRSSを取得するページが設定されるようなので、早速アクセスしてみます。

が…何か、エラーが…。

Fatal error: Class 'BEAR_Util' not found in /home/bear/helloworld/App.php on line 65

App.phpの該当の部分近辺を見ると、

<?php
        // 開発モード(キャッシュクリア)
        if ($mode == 1) {
            BEAR_Util::clearAllCache(false);
        }
        BEAR::init($app);
?>

こうなっています。ソースを辿って調べてみると、BEAR_Utilクラスは、BEAR::initした後じゃないと呼び出せないような気がします…。

仕方が無いので、先にBEAR::initが来るように、以下のようにしてみました。

<?php
        BEAR::init($app);
        // 開発モード(キャッシュクリア)
        if ($mode == 1) {
            BEAR_Util::clearAllCache(false);
        }
?>

これでとりあえずは、サンプルページの表示まではできました。

でも、CSSが読み込まれてないっぽい…。

生成されたHTMLを見てみると、CSSやJSはドキュメントルート直下に読みに行くようになっているようです。ただ、今回作った環境は、ドキュメントルートから一つディレクトリを掘ったところにCSSが置かれているので、このままではCSSを読み込む事ができません。

どうしようか悩んだんですが、結局、ドキュメントルートに.htaccessを置いて、mod_rewriteのルールを設定する事で対応する事にしました。*2

RewriteCond %{HTTP_REFERER} helloworld/
RewriteRule ^css/(.*) /helloworld/css/$1 [P,L]
RewriteCond %{HTTP_REFERER} helloworld/
RewriteRule ^js/(.*) /helloworld/js/$1 [P,L]
RewriteCond %{HTTP_REFERER} helloworld/
RewriteRule ^__bear/(.*) /helloworld/__bear/$1 [R,L]
RewriteCond %{HTTP_REFERER} helloworld/
RewriteRule ^__panda/(.*) /helloworld/__panda/$1 [R,L]

また別途記事で触れようと思いますが、BEARにはかなり強力なデバッグツールが用意されていて、htdocsにデバッグツール用のシンボリックリンクがあったので、そちらも使えるようにルールを定義しておきました。

ちょっとイレギュラーな方法ではありますが、元々、init-appで作成されたhtdocsディレクトリをドキュメントルートに設定する事が想定されていると思うので、まぁこのくらい設定が必要なのは仕方ないところですかね。

今度こそ、こんにちはWorldさん!

前置きが長くなりましたが、やっとHelloWorldです。手順は、本家Wikiのチュートリアルページを参照しました。

Google Code Archive - Long-term storage for Google Code Project Hosting.

まず、htdocs配下に、helloworld.phpというページファイルを作成します。

<?php
require_once('App.php');

/**
 * HelloWorldページ
 *
 */
class Helloworld extends App_Page
{
    function onInit()
    {
        $this->set('message', 'Hello World');
    }

    function onOutput()
    {
        $this->display();
    }
}

BEAR_Main::run('Helloworld');
?>

チュートリアルでは、クラス名が"Page_Hello_World"になっていましたが、"Page_"の接頭語はなくても大丈夫なようです。

あと、テンプレートのパスが"templates/hello/world.tpl"になっていましたが、"App/views/pages/hello/world.tpl"の間違いと思われます。

上記で作ったサンプルでは、アンダースコアを無しにしたので、テンプレートのファイルは、"App/views/pages/helloworld.tpl"に作成しました。

<html>
<head>
<title>{$message}</title>
</head>
<body>
{$message}
</body>
</html>

これで、無事に画面に"Hello World"と表示されました。めでたし、めでたし…。

余談

BEARでは、bearmodeというサーバ環境変数を変更する事で、簡単に動作モードを切り替える事ができます。デフォルトでは、.htaccessに以下のような記載があり、デバッグモードで動作するようになっています。

SetEnv bearmode 1

本番環境にあたるライブモードだとどうなるのか見てみようと、bearmodeを0に設定してみたところ、なぜだかレスポンスコード503が返るようになってしまいました。

エラーを見てみたところ、どうやらerror_reportingの設定のところで問題があったようでした。(PEARディレクトリ配下のPanda.phpの338行目)

<?php
            ini_set('display_errors', 0);
            error_reporting(self::$_config[self::CONFIG_ERROR_LEVEL_LIVE]);
            assert_options(ASSERT_ACTIVE, 0);
?>

$_configの内容を読むようになっていますが、CONFIG_ERROR_LEVEL_LIVEに既にエラーレベルが定義されているので、そのままerror_reportingに渡すようにすればよいようです。

<?php
            ini_set('display_errors', 0);
            error_reporting(self::CONFIG_ERROR_LEVEL_LIVE);
            assert_options(ASSERT_ACTIVE, 0);
?>

HelloWorldでこの調子だと、まだ結構バグがあったりするのかなぁ…。まぁ、また記事を書きながら気付いたら、随時記事に追記していくようにしようかなと思います。

次回は?

これでやっとスタート地点に立てたところな訳ですが、次回は実際にWebアプリケーションを作っていく過程に入っていく予定です。

という訳で、次回からはBEARの魅力たっぷりにお送りしたいと思います!(多分…。)

*1:自動的に作成される.htaccessの中でアプリケーションのディレクトリをinclude_pathに設定しているようで、init-appの引数をフルパスにしておかないとindex.phpからApp.phpが見つけられなくなるので注意。

*2: (2010/01/27)ルールの書き方がおかしかったので修正

2009年07月13日

[]MeCabとマルコフ連鎖で文章生成をやってみた

何となく"マルコフ連鎖"という単語に魅かれて、ちょっとやってみました。

mecab でマルコフ連鎖をためしてみる。 - コードを舐める日々

まずスクレイピング

まぁ、当然(?)、PHPでやる訳ですが、まずスクレイピングをどうしようかなと思ったんですが、HTMLScrapingという素晴らしいPHPクラスがあるので、ありがたく使わせて頂く事にしました。

404 Not Found

<?php
try {
    $hs = new HTMLScraping();
    $url = 'http://www.asahi.com';
    $xml = $hs->getXmlObject($url);
    $li = $xml->xpath('//div[@id="HeadLine"]/ul[@class="Lnk FstMod"]/li[1]/a');
    $url_news = $url.$li[0]['href'];
    $news = $hs->getXmlObject($url_news);
    $texts = $news->xpath('//div[@class="BodyTxt"]/*');
} catch ( Exception $e ) {
    return false;
}
$text = implode('', $texts);
$text = preg_replace('/(\n| |\s)/', '', $text);
?>

こんな程度で、XPathで自由に欲しい部分が抜き出せてしまいます。素晴らしい。

MeCabによる分かち書き

これはもう、mecabコマンドに任せちゃえって事で、次のような1行だけで。

<?php
$words = explode(' ', `echo '${text}' | mecab -Owakati`);
?>

そしてマルコフ連鎖

そして、肝心のマルコフ連鎖の部分について。

まずは分かち書きで得られた単語リストを、頭から3単語ずつのかたまりにしたマルコフ連鎖用テーブルを作ります。

<?php
$table = array();
for ( $i = 0; $i < count($words) - 2; $i++ ) {
    $table[] = array($words[$i], $words[$i + 1], $words[$i + 2]);
}
?>

こんな感じですね。

あとはこのテーブルを使って、3単語の前2つをキーにして次の単語になる候補を探して、見つかった候補から乱数で選びながら連鎖を続けていけばOKですね。

<?php
$key[0] = $table_markov[0][0];
$key[1] = $table_markov[0][1];

$result = implode('', $key);
while ( true ) {
    $values = $this->_searchAvailableValues($table_markov, $key);
    $value = $values[array_rand($values)];
    if ( $value === '' ) {
        break;
    }
    $result .= $value;
    $key[0] = $key[1];
    $key[1] = $value;
}
?>

次の候補を探す_searchAvailableValuesメソッドは、次のような感じで。

<?php
function _searchAvailableValues ( $table, $key )
{
    $values = array();
    foreach ( $table as $row ) {
        if ( $row[0] === $key[0] && $row[1] === $key[1] ) {
            $values[] = $row[2];
        }
    }
    return $values;
}
?>

実際に試してみる

それぞれの処理をメソッドに分割してクラス化して、次のように順番に呼び出すようにしてやれば、マルコフ連鎖の出来上がりです。

<?php
require_once(dirname(__FILE__).'/lib/Markovchain.php');
$mc = new Lib_Markovchain();

// asahi.comからヘッドラインニュースの本文を取得する
$newstext = $mc->getNewsTextFromAsahicom();

// MeCabの分かち書きで単語に分解する
$words = $mc->wakatiText($newstext);

// 単語リストからマルコフ連鎖用単語テーブルを作る
$table = $mc->buildTable($words);

// マルコフ連鎖により文書を生成する
$sentense = $mc->buildSentense($table);

echo $newstext."\n";
echo ''."\n";
echo $sentense."\n";
?>

実際動かしてみたら、現時点で次のような感じでした。

民主、共産、社民、国民新の野党4党は13日、麻生首相と自公連立政権による国政運営は限界に来ているとして、衆院に内閣不信任決議案を、参院に首相問責決議案をそれぞれ提出した。内閣不信任案は14日の衆院本会議で否決されるが、首相問責決議案は参院で野党が多数を占めているため、近く本会議で可決の見通し。参院での首相問責可決は昨年の福田首相に次いで2例目になる。

民主、共産、社民、国民新の野党4党は13日、麻生首相と自公連立政権による国政運営は限界に来ているため、近く本会議で否決されるが、首相問責決議案を、参院に首相問責決議案を、参院に首相問責決議案をそれぞれ提出した。内閣不信任案は14日の衆院本会議で否決されるが、首相問責決議案は参院で野党が多数を占めているため、近く本会議で可決の見通し。参院で野党が多数を占めているとして、衆院に内閣不信任案は参院での首相問責決議案を、参院に首相問責決議案を、参院に首相問責可決は昨年の福田首相に次いで2例目になる。

ちなみに元記事で、"マルコフ連鎖で要約"という事が書かれているんですが、マルコフ連鎖って原理的に要約になるとは限らないんじゃないかなと。特に長い文章が元になっていると、ループが発生する可能性が増えるので、上記みたいに、むしろ文章が伸びる方が多い気がします。

…と思って、リンクを辿っていっていたら、マルコフ連鎖でちゃんと要約っぽくやっているブックマークレットを作っている方がいました。

選択範囲を要約する人工無能ブックマークレット(680バイト) | うえぽんSW局(旧)

これがうまく要約になっているのは、短い文章のそれぞれの終りが終了条件になっているから、割と短く文章が終わる可能性が高いからでしょうね。まぁ、マルコフ連鎖が一番威力を発揮するのは、チャットのログなんかを元にして文章を生成するタイプの人工無脳とかなんではないかなと思います。

そういえば、人工無脳は昔、結構興味があって、まだあんまりプログラムがまともに書けない頃に試行錯誤していたんですが、結局思ったモノができなくてあきらめてしまった…なんて事を思い出しました…。*1

TDDのススメ

今回、テストファーストでテストを書きながら実装したんですが、やっぱりTDDだと安心して実装に取り組めて良いですね。

特にDocTestでやっていると、

  • メソッド名と引数を決める
  • コメントで簡単な説明を書く
  • テストを書く
  • 実装する

っていう一連の流れが、ウィンドウ切り替えとかが一切無しで同じファイル内でできるので、非常にスムーズになりますね。

もし「TDD? 何それ、こわい…」という方がいらっしゃったら、テスト講座をぜひ見てみて下さいね〜。(と、何となく宣伝してみるテスト。)

最後に、せっかくなので、完成したクラスのソースを、テスト付きで載せておきたいと思います。↓

今回作ったクラスのソース

<?php
require_once(dirname(__FILE__).'/HTMLScraping.class.php');

class Lib_Markovchain
{
    /**
     * getNewsTextFromAsahicom
     *
     * asahi.comからヘッドラインニュースの本文を取得する
     *
     * #test 返り値がfalseやnullでない事
     * <code>
     * $result = #f();
     * #ne(false, $result);
     * #ne(null, $result);
     * </code>
     * #test HTMLタグを含まず改行・空白の無い1行のテキストである事
     * <code>
     * $result = #f();
     * #true(preg_match('/<.*>/', $result) !== 1);
     * #true(preg_match('/\n/', $result) !== 1);
     * #true(preg_match('/(\s| )/', $result) !== 1);
     * </code>
     *
     * @access public
     * @return string ニュース本文
     */
    function getNewsTextFromAsahicom ( )
    {
        try {
            $hs = new HTMLScraping();
            $url = 'http://www.asahi.com';
            $xml = $hs->getXmlObject($url);
            $url_news = $url.$li[0]['href'];
            $news = $hs->getXmlObject($url_news);
            $texts = $news->xpath('//div[@class="BodyTxt"]/*');
        } catch ( Exception $e ) {
            return false;
        }
        $text = implode('', $texts);
        $text = preg_replace('/(\n| |\s)/', '', $text);
        return $text;
    }

    /**
     * wakatiText
     *
     * MeCabを使って文章を単語に分解する
     * 文章の終りを示す為、最後の要素は空文字列とする
     *
     * #test 正しく分かち書きされた単語リストが得られる事
     * <code>
     * #eq(array('これ','は','ペン','です','。',''), #f('これはペンです。'));
     * </code>
     * #test 引数が空文字列の時は空文字列を1つだけ含む配列を返す事
     * <code>
     * #eq(array(''), #f(''));
     * </code>
     *
     * @param string $text
     * @access public
     * @return array 分かち書きした単語リスト
     */
    function wakatiText ( $text )
    {
        $words = explode(' ', `echo '${text}' | mecab -Owakati`);
        $words[count($words)-1] = '';
        return $words;
    }

    /**
     * buildTable
     *
     * 単語リストからマルコフ連鎖用単語テーブルを作る
     *
     * #test 単語リストが単語3つ+終端の場合
     * <code>
     * $expects = array(
     *     array('私', 'は', 'カモメ'),
     *     array('は', 'カモメ', ''),
     * );
     * #eq($expects, #f(array('私', 'は', 'カモメ', '')));
     * </code>
     *
     * @param array $words 単語リスト
     * @access public
     * @return array マルコフ連鎖用単語テーブル
     */
    function buildTable ( $words )
    {
        $table = array();
        for ( $i = 0; $i < count($words) - 2; $i++ ) {
            $table[] = array($words[$i], $words[$i + 1], $words[$i + 2]);
        }
        return $table;
    }

    /**
     * buildSentense
     *
     * 単語テーブルを元にマルコフ連鎖で文字列を構築する
     *
     * #test 単語の組み合わせが1通りだけの場合
     * <code>
     * $table_markov = array(
     *     array('私', 'は', 'カモメ'),
     *     array('は', 'カモメ', ''),
     * );
     * #eq('私はカモメ', #f($table_markov));
     * $table_markov = array(
     *     array('私', 'は', 'カモメ'),
     *     array('は', 'カモメ', 'を'),
     *     array('カモメ', 'を', '見た'),
     *     array('を', '見た', ''),
     * );
     * #eq('私はカモメを見た', #f($table_markov));
     * </code>
     * #test 単語の組み合わせが2通りの場合
     * <code>
     * $table_markov = array(
     *     array('私', 'は', 'カモメ'),
     *     array('私', 'は', 'ウミドリ'),
     *     array('は', 'カモメ', ''),
     *     array('は', 'ウミドリ', ''),
     * );
     * $result = #f($table_markov);
     * #true($result === '私はカモメ' || $result === '私はウミドリ');
     * </code>
     *
     * @param array $table_markov マルコフ連鎖用単語テーブル
     * @access public
     * @return string マルコフ連鎖で構築された文字列
     */
    function buildSentense ( $table_markov )
    {
        $key[0] = $table_markov[0][0];
        $key[1] = $table_markov[0][1];

        $result = implode('', $key);
        while ( true ) {
            $values = $this->_searchAvailableValues($table_markov, $key);
            $value = $values[array_rand($values)];
            if ( $value === '' ) {
                break;
            }
            $result .= $value;
            $key[0] = $key[1];
            $key[1] = $value;
        }
        return $result;
    }

    /**
     * _searchAvailableValues
     *
     * マルコフ連鎖で次の値となる候補を検索する
     *
     * #test 候補が1つだけの場合
     * <code>
     * $table = array(
     *     array('私', 'は', 'カモメ'),
     *     array('は', 'カモメ', ''),
     * );
     * $expects = array('カモメ');
     * #eq($expects, #f($table, array('私', 'は')));
     * </code>
     * #test 候補が2つの場合
     * <code>
     * $table = array(
     *     array('私', 'は', 'カモメ'),
     *     array('私', 'は', 'ウミドリ'),
     *     array('は', 'カモメ', ''),
     *     array('は', 'ウミドリ', ''),
     * );
     * $expects = array('カモメ', 'ウミドリ');
     * #eq($expects, #f($table, array('私', 'は')));
     *
     * @param array $table マルコフ連鎖用単語テーブル
     * @param array $key 検索キー
     * @access private
     * @return array マルコフ連鎖の値候補リスト
     */
    function _searchAvailableValues ( $table, $key )
    {
        $values = array();
        foreach ( $table as $row ) {
            if ( $row[0] === $key[0] && $row[1] === $key[1] ) {
                $values[] = $row[2];
            }
        }
        return $values;
    }
}
?>

*1:当時、チャットのログからマルコフ連鎖用単語テーブルを作ってDBに放り込んで、SQLでゴニョゴニョしながら文書生成する実装を、LLではなく、シェルスクリプトで書いた事があったような覚えがおぼろげながら…。

2009年03月26日

[][]Redmineをインストールしてみた

我らが中継職人、id:i_ogiさんの動画公開ブログ(笑)で、先日の第41回PHP勉強会の動画を見させて頂きました。

第41回PHP勉強会@関東のムービー - おぎろぐはてな

id:yandodさんの発表のRedmineがちょっと気になって、試しにインストールだけしてみました。以下、手順の備忘録…。

RubyとRailsはインストール済みなので、その辺りの導入手順は省略。バージョンはそれぞれ、Rubyが1.8.4、Railsが2.1.2です。

手順は、Redmine.JPのインストール手順ページを参考にしました。まずはRedmineのインストールから。

$ cd ~/htdocs
$ svn checkout http://redmine.rubyforge.org/svn/branches/0.8-stable redmine

続いて、database.ymlの設定をして、DBのMigrateと初期データ投入を行います。

$ cd redmine
$ cp -i config/database.yml.example config/database.yml
$ vi config/database.yml
 :
production:
  adapter: postgresql
  database: redmine_production
  host: localhost
  username: user
  password: pass
  encoding: utf8
 :
$ createdb -U user -E utf8 redmine_production
$ rake db:migrate RAILS_ENV=production
$ rake load_default_data RAILS_ENV=production
Select language: bg, ca, cs, da, de, en, es, fi, fr, he, hu, it, ja, ko, lt, nl, no, pl, pt, pt-br, ro, ru, sk, sr, sv, th, tr, uk, vn, zh, zh-tw [en] ja

次に、メール周りの設定。

$ cp -i config/email.yml.example config/email.yml
$ vi config/email.yml
 :
production:
  delivery_method: :smtp
  smtp_settings:
    address: localhost
    port: 25
    domain: example.com
 :

後はscript/serverで起動すればいいんですが、手順に、”Apache上でRuby on Railsアプリケーションを動かす/Passenger(mod_rails for Apache)の利用”というのがあったので、そっちを試してみました。

$ gem install passenger
$ passenger-install-apache2-module
$ vi /etc/apache2/httpd.conf
 :
LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-2.1.2/ext/apache2/mod_passenger.so
PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-2.1.2
PassengerRuby /usr/bin/ruby1.8
 :
$ cp -i dispatch.cgi.example dispatch.cgi
$ vi config/environment.rb
 :
ENV['RAILS_ENV'] ||= 'production'
 :

本当はバーチャルホストを切って、DocumentRootを設定するべきですが、ちょっと試してみるだけなので、今回はDocumentRoot直下に直接redmineディレクトリを突っ込んでいます。

ここまで設定したところでブラウザからアクセスしてみたら、無事にHome画面が表示されたので、とりあえず導入はうまくいったようです。

ちなみに、Passengerを使わずに、Mongrelで動かしてApache経由でアクセスする場合には、以下のようにします。

$ /usr/bin/ruby1.8 /usr/bin/mongrel_rails start -p 3001 -e production -P ./tmp/pids/mongrel.pid -d --prefix /redmine
$ cd ~/htdocs/redmine
$ vi .htaccess
RewriteEngine On
RewriteRule ^(.*)$ http://localhost:3001/redmine/$1 [P]

"script/server"だとprefixが指定できないので、Mongrelを直接実行しています。こうしてやると、ディレクトリ毎に違うポート番号でRailsを動かす事ができるので、なかなかに便利です。

少しだけ触ってみた感想

Passenger上で動く設定でちょっと触ってみたところ、レスポンスが非常に重たくてまともに使えそうになかったので、mongrel上で動かすようにして色々と触ってみました。*1

ざっと設定周りを見てみただけでちゃんと使ってはいないですが、かなり柔軟に色々設定できて、なかなか使い勝手は良さそうですね。

とりあえず個人的なBTSとして使ってみて、良さそうだったら本格的にバグ管理用に使ってみようかなと思います。

あとがき

ちょこちょこと手順は省いている部分もあるのであっさり導入できたように見えますが、実際にはところどころ躓きながらなので、導入完了まで結構な時間が掛かってしまいました。

id:yandodさんが発表の中で、「インストールは気合」とおっしゃってましたが、正にその通りだと思いました…。

バージョンがらみの問題で一旦ruby自体の入れ直しなんてしてしまったせいもあるんですが、「面白そうだからちょっと試してみよ。」と言うにはちょっと手間が掛かり過ぎてしまった感じがします。(-_-;)

最初にRailsを入れた時にやたらと苦労した事もあって、RailsとRuby周りに対してはどうもあまりいい印象が無いんですよね…。まぁ、素人がおいそれと入ってはいけない聖域なんでしょうね、きっと…。

*1:Rails上のログだと処理にほとんど時間が掛かっていなかったので、Passengerが重たいんだと思いますが、何か設定が良くないのか、それともPassengerっていうのはこういうもんなんでしょうか…?